Skip to content

Commit

Permalink
mint - fees for nut2 changes
Browse files Browse the repository at this point in the history
  • Loading branch information
elnosh committed Jul 16, 2024
1 parent d961a2d commit c1a53ce
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 43 deletions.
2 changes: 2 additions & 0 deletions .env.mint.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
MINT_PRIVATE_KEY="mykey"
# use only if setting up mint with one unit (defaults to sat)
MINT_DERIVATION_PATH="0/0/0"
# fee to charge per input (in parts per thousand)
INPUT_FEE_PPK=100

# mint info
MINT_NAME="a cashu mint"
Expand Down
2 changes: 1 addition & 1 deletion cashu/cashu.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ var (
EmptyInputsErr = Error{Detail: "inputs cannot be empty", Code: ProofsErrCode}
QuoteNotExistErr = Error{Detail: "quote does not exist", Code: QuoteErrCode}
QuoteAlreadyPaid = Error{Detail: "quote already paid", Code: QuoteErrCode}
InsufficientProofsAmount = Error{Detail: "insufficient amount in proofs", Code: ProofsErrCode}
InsufficientProofsAmount = Error{Detail: "amount of input proofs is below amount needed for transaction", Code: ProofsErrCode}
InvalidKeysetProof = Error{Detail: "proof from an invalid keyset", Code: ProofsErrCode}
InvalidSignatureRequest = Error{Detail: "requested signature from non-active keyset", Code: KeysetErrCode}
)
Expand Down
7 changes: 4 additions & 3 deletions cashu/nuts/nut02/nut02.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ type GetKeysetsResponse struct {
}

type Keyset struct {
Id string `json:"id"`
Unit string `json:"unit"`
Active bool `json:"active"`
Id string `json:"id"`
Unit string `json:"unit"`
Active bool `json:"active"`
InputFeePpk uint `json:"input_fee_ppk"`
}
60 changes: 35 additions & 25 deletions crypto/keyset.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,41 @@ import (
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)

const maxOrder = 64
const MAX_ORDER = 64

type Keyset struct {
Id string
Unit string
Active bool
Keys map[uint64]KeyPair
Id string
Unit string
Active bool
Keys map[uint64]KeyPair
InputFeePpk uint
}

type KeyPair struct {
PrivateKey *secp256k1.PrivateKey
PublicKey *secp256k1.PublicKey
}

// KeysetsMap maps a mint url to map of string keyset id to keyset
type KeysetsMap map[string]map[string]WalletKeyset

type WalletKeyset struct {
Id string
MintURL string
Unit string
Active bool
PublicKeys map[uint64]*secp256k1.PublicKey
Counter uint32
}

func GenerateKeyset(seed, derivationPath string) *Keyset {
keys := make(map[uint64]KeyPair, maxOrder)
func GenerateKeyset(seed, derivationPath string, inputFeePpk uint) *Keyset {
keys := make(map[uint64]KeyPair, MAX_ORDER)

pks := make(map[uint64]*secp256k1.PublicKey)
for i := 0; i < maxOrder; i++ {
for i := 0; i < MAX_ORDER; i++ {
amount := uint64(math.Pow(2, float64(i)))
hash := sha256.Sum256([]byte(seed + derivationPath + strconv.FormatUint(amount, 10)))
privKey, pubKey := btcec.PrivKeyFromBytes(hash[:])
keys[amount] = KeyPair{PrivateKey: privKey, PublicKey: pubKey}
pks[amount] = pubKey
}
keysetId := DeriveKeysetId(pks)
return &Keyset{Id: keysetId, Unit: "sat", Active: true, Keys: keys}

return &Keyset{
Id: keysetId,
Unit: "sat",
Active: true,
Keys: keys,
InputFeePpk: inputFeePpk,
}
}

// DeriveKeysetId returns the string ID derived from the map keyset
Expand Down Expand Up @@ -97,10 +93,11 @@ func (ks *Keyset) DerivePublic() map[uint64]string {
}

type KeysetTemp struct {
Id string
Unit string
Active bool
Keys map[uint64]json.RawMessage
Id string
Unit string
Active bool
Keys map[uint64]json.RawMessage
InputFeePpk uint
}

func (ks *Keyset) MarshalJSON() ([]byte, error) {
Expand All @@ -116,6 +113,7 @@ func (ks *Keyset) MarshalJSON() ([]byte, error) {
}
return m
}(),
InputFeePpk: ks.InputFeePpk,
}

return json.Marshal(temp)
Expand Down Expand Up @@ -180,6 +178,18 @@ func (kp *KeyPair) UnmarshalJSON(data []byte) error {
return nil
}

// KeysetsMap maps a mint url to map of string keyset id to keyset
type KeysetsMap map[string]map[string]WalletKeyset

type WalletKeyset struct {
Id string
MintURL string
Unit string
Active bool
PublicKeys map[uint64]*secp256k1.PublicKey
Counter uint32
}

type WalletKeysetTemp struct {
Id string
MintURL string
Expand Down
13 changes: 13 additions & 0 deletions mint/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"log"
"os"
"strconv"

"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/elnosh/gonuts/cashu/nuts/nut06"
Expand All @@ -15,14 +17,25 @@ type Config struct {
DerivationPath string
Port string
DBPath string
InputFeePpk uint
}

func GetConfig() Config {
var inputFeePpk uint = 0
if len(os.Getenv("INPUT_FEE_PPK")) > 0 {
fee, err := strconv.ParseUint(os.Getenv("INPUT_FEE_PPK"), 10, 16)
if err != nil {
log.Fatalf("unable to parse INPUT_FEE_PPK: %v", err)
}
inputFeePpk = uint(fee)
}

return Config{
PrivateKey: os.Getenv("MINT_PRIVATE_KEY"),
DerivationPath: os.Getenv("MINT_DERIVATION_PATH"),
Port: os.Getenv("MINT_PORT"),
DBPath: os.Getenv("MINT_DB_PATH"),
InputFeePpk: inputFeePpk,
}
}

Expand Down
32 changes: 21 additions & 11 deletions mint/mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func LoadMint(config Config) (*Mint, error) {
log.Fatalf("error starting mint: %v", err)
}

activeKeyset := crypto.GenerateKeyset(config.PrivateKey, config.DerivationPath)
activeKeyset := crypto.GenerateKeyset(config.PrivateKey, config.DerivationPath, config.InputFeePpk)
mint := &Mint{db: db, ActiveKeysets: map[string]crypto.Keyset{activeKeyset.Id: *activeKeyset}}

mint.db.SaveKeyset(activeKeyset)
Expand Down Expand Up @@ -240,9 +240,9 @@ func (m *Mint) Swap(proofs cashu.Proofs, blindedMessages cashu.BlindedMessages)
}
}
}

if proofsAmount < blindedMessagesAmount {
return nil, cashu.InputsBelowOutputs
fees := m.transactionFees(proofs)
if proofsAmount-uint64(fees) < blindedMessagesAmount {
return nil, cashu.InsufficientProofsAmount
}

err := m.verifyProofs(proofs)
Expand Down Expand Up @@ -351,18 +351,18 @@ func (m *Mint) MeltTokens(method, quoteId string, proofs cashu.Proofs) (MeltQuot
return MeltQuote{}, cashu.QuoteAlreadyPaid
}

proofsAmount := proofs.Amount()

// checks if amount in proofs is enough
if proofsAmount < meltQuote.Amount+meltQuote.FeeReserve {
return MeltQuote{}, cashu.InsufficientProofsAmount
}

err := m.verifyProofs(proofs)
if err != nil {
return MeltQuote{}, err
}

proofsAmount := proofs.Amount()
fees := m.transactionFees(proofs)
// checks if amount in proofs is enough
if proofsAmount < meltQuote.Amount+meltQuote.FeeReserve+uint64(fees) {
return MeltQuote{}, cashu.InsufficientProofsAmount
}

// if proofs are valid, ask the lightning backend
// to make the payment
preimage, err := m.LightningClient.SendPayment(meltQuote.InvoiceRequest, meltQuote.Amount)
Expand Down Expand Up @@ -482,3 +482,13 @@ func (m *Mint) requestInvoice(amount uint64) (*lightning.Invoice, error) {

return &invoice, nil
}

func (m *Mint) transactionFees(inputs cashu.Proofs) uint {
var fees uint = 0
for _, proof := range inputs {
// note: not checking that proof id is from valid keyset
// because already doing that in call to verifyProofs
fees += m.Keysets[proof.Id].InputFeePpk
}
return (fees + 999) / 1000
}
4 changes: 2 additions & 2 deletions mint/mint_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ func TestMintTokens(t *testing.T) {
}

// test with invalid keyset in blinded messages
invalidKeyset := crypto.GenerateKeyset("seed", "path")
invalidKeyset := crypto.GenerateKeyset("seed", "path", 0)
invalidKeysetMessages, _, _, err := testutils.CreateBlindedMessages(mintAmount, *invalidKeyset)
_, err = testMint.MintTokens(testutils.BOLT11_METHOD, mintQuoteResponse.Quote, invalidKeysetMessages)
if !errors.Is(err, cashu.InvalidSignatureRequest) {
Expand Down Expand Up @@ -274,7 +274,7 @@ func TestSwap(t *testing.T) {

// test blinded messages over proofs amount
_, err = testMint.Swap(proofs, overBlindedMessages)
if !errors.Is(err, cashu.InputsBelowOutputs) {
if !errors.Is(err, cashu.InsufficientProofsAmount) {
t.Fatalf("expected error '%v' but got '%v' instead", cashu.OutputsOverInvoiceErr, err)
}

Expand Down
7 changes: 6 additions & 1 deletion mint/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,12 @@ func (ms *MintServer) buildAllKeysetsResponse() nut02.GetKeysetsResponse {
keysetsResponse := nut02.GetKeysetsResponse{}

for _, keyset := range ms.mint.Keysets {
keysetRes := nut02.Keyset{Id: keyset.Id, Unit: keyset.Unit, Active: keyset.Active}
keysetRes := nut02.Keyset{
Id: keyset.Id,
Unit: keyset.Unit,
Active: keyset.Active,
InputFeePpk: keyset.InputFeePpk,
}
keysetsResponse.Keysets = append(keysetsResponse.Keysets, keysetRes)
}

Expand Down

0 comments on commit c1a53ce

Please sign in to comment.