From 5ac32a287d605543397777964aa192e3cb0728e3 Mon Sep 17 00:00:00 2001 From: gohumble Date: Sun, 16 Jul 2023 21:56:12 +0200 Subject: [PATCH 1/2] remove split amount --- api/server.go | 5 +- cashu/cashu.go | 8 ++- cmd/cashu/feni/wallet.go | 91 +++++++++++++++++--------------- mint/mint.go | 110 ++++++++++++--------------------------- 4 files changed, 86 insertions(+), 128 deletions(-) diff --git a/api/server.go b/api/server.go index 3a7abfd..e0e654a 100644 --- a/api/server.go +++ b/api/server.go @@ -435,19 +435,18 @@ func (api Api) split(w http.ResponseWriter, r *http.Request) { return } proofs := payload.Proofs - amount := payload.Amount outputs := payload.Outputs keyset, err := api.Mint.LoadKeySet(api.Mint.KeySetId) if err != nil { responseError(w, cashu.NewErrorResponse(err)) return } - fstPromise, sendPromise, err := api.Mint.Split(proofs, amount, outputs, keyset) + promises, err := api.Mint.Split(proofs, outputs, keyset) if err != nil { responseError(w, cashu.NewErrorResponse(err)) return } - response := cashu.SplitResponse{Fst: fstPromise, Snd: sendPromise} + response := cashu.SplitResponse{Promises: promises} res, err := json.Marshal(response) if err != nil { responseError(w, cashu.NewErrorResponse(err)) diff --git a/cashu/cashu.go b/cashu/cashu.go index 48190a5..137657d 100644 --- a/cashu/cashu.go +++ b/cashu/cashu.go @@ -132,10 +132,6 @@ type MeltResponse struct { Change []BlindedSignature `json:"change,omitempty"` } type GetKeysResponse map[int]string -type SplitResponse struct { - Fst []BlindedSignature `json:"fst"` - Snd []BlindedSignature `json:"snd"` -} type GetKeySetsResponse struct { KeySets []string `json:"keysets"` } @@ -165,6 +161,8 @@ type CheckFeesRequest struct { type SplitRequest struct { Proofs Proofs `json:"proofs"` - Amount uint64 `json:"amount"` Outputs []BlindedMessage `json:"outputs"` } +type SplitResponse struct { + Promises []BlindedSignature `json:"promises"` +} diff --git a/cmd/cashu/feni/wallet.go b/cmd/cashu/feni/wallet.go index 68eb042..481775a 100644 --- a/cmd/cashu/feni/wallet.go +++ b/cmd/cashu/feni/wallet.go @@ -48,7 +48,7 @@ var Wallet MintWallet // constructOutputs takes in a slice of amounts and a slice of secrets, and // constructs a MintRequest with blinded messages and a slice of private keys // corresponding to the given amounts and secrets. -func constructOutputs(amounts []uint64, secrets []string) (cashu.MintRequest, []*secp256k1.PrivateKey) { +func constructOutputs(amounts []uint64, secrets []string) (cashu.BlindedMessages, []*secp256k1.PrivateKey) { // Create a new empty MintRequest with a slice of blinded messages. payloads := cashu.MintRequest{Outputs: make(cashu.BlindedMessages, 0)} // Create an empty slice of private keys. @@ -70,7 +70,7 @@ func constructOutputs(amounts []uint64, secrets []string) (cashu.MintRequest, [] cashu.BlindedMessage{Amount: pair.Second, B_: fmt.Sprintf("%x", pub.SerializeCompressed())}) } // Return the MintRequest and the slice of private keys. - return payloads, privateKeys + return payloads.Outputs, privateKeys } func (w MintWallet) checkUsedSecrets(amounts []uint64, secrets []string) error { @@ -117,8 +117,8 @@ func (w MintWallet) mint(amounts []uint64, paymentHash string) []cashu.Proof { if err != nil { panic(err) } - req, privateKeys := constructOutputs(amounts, secrets) - blindedSignatures, err := w.client.Mint(req, paymentHash) + outputs, privateKeys := constructOutputs(amounts, secrets) + blindedSignatures, err := w.client.Mint(cashu.MintRequest{Outputs: outputs}, paymentHash) if err != nil { panic(err) } @@ -240,8 +240,8 @@ func (w MintWallet) PayLightning(proofs []cashu.Proof, invoice string) ([]cashu. for i := 0; i < 4; i++ { secrets = append(secrets, generateSecret()) } - payloads, rs := constructOutputs(amounts, secrets) - res, err := w.client.Melt(cashu.MeltRequest{Proofs: proofs, Pr: invoice, Outputs: payloads.Outputs}) + outputs, rs := constructOutputs(amounts, secrets) + res, err := w.client.Melt(cashu.MeltRequest{Proofs: proofs, Pr: invoice, Outputs: outputs}) if err != nil { return nil, err } @@ -332,46 +332,17 @@ func (w *MintWallet) Split(proofs []cashu.Proof, amount uint64, scndSecret strin if len(proofs) < 0 { return nil, nil, fmt.Errorf("no proofs provided.") } - frstProofs, scndProofs, err := w.split(proofs, amount, scndSecret) - if err != nil { - return nil, nil, err - } - if len(frstProofs) == 0 && len(scndProofs) == 0 { - return nil, nil, fmt.Errorf("received no splits.") - } - usedSecrets := make([]string, 0) - for _, proof := range proofs { - usedSecrets = append(usedSecrets, proof.Secret) - } - w.proofs = lo.Filter[cashu.Proof](w.proofs, func(p cashu.Proof, i int) bool { - _, found := lo.Find[string](usedSecrets, func(secret string) bool { - return secret == p.Secret - }) - return !found - }) - w.proofs = append(w.proofs, frstProofs...) - w.proofs = append(w.proofs, scndProofs...) - err = storeProofs(append(frstProofs, scndProofs...)) - if err != nil { - return nil, nil, err - } - for _, proof := range proofs { - err = invalidateProof(proof) - if err != nil { - return nil, nil, err - } - } - return frstProofs, scndProofs, nil -} -func (w MintWallet) split(proofs []cashu.Proof, amount uint64, scndSecret string) (keep []cashu.Proof, send []cashu.Proof, err error) { - total := SumProofs(proofs) + if amount > total { + return nil, nil, fmt.Errorf("amount too large") + } frstAmt := total - amount scndAmt := amount frstOutputs := mint.AmountSplit(frstAmt) scndOutputs := mint.AmountSplit(scndAmt) amounts := append(frstOutputs, scndOutputs...) secrets := make([]string, 0) + if scndSecret == "" { for range amounts { secrets = append(secrets, generateSecret()) @@ -390,14 +361,48 @@ func (w MintWallet) split(proofs []cashu.Proof, amount uint64, scndSecret string return nil, nil, fmt.Errorf("number of secrets does not match number of outputs") } // TODO -- check used secrets(secrtes) - payloads, rs := constructOutputs(amounts, secrets) - response, err := w.client.Split(cashu.SplitRequest{Amount: amount, Proofs: proofs, Outputs: payloads.Outputs}) + outputs, rs := constructOutputs(amounts, secrets) + + promises, err := w.split(proofs, outputs) if err != nil { return nil, nil, err } + newProofs := w.constructProofs(promises, secrets, rs) - return w.constructProofs(response.Fst, secrets[:len(response.Fst)], rs[:len(response.Fst)]), - w.constructProofs(response.Snd, secrets[len(response.Fst):], rs[len(response.Fst):]), nil + usedSecrets := make([]string, 0) + for _, proof := range newProofs { + usedSecrets = append(usedSecrets, proof.Secret) + } + w.proofs = lo.Filter[cashu.Proof](w.proofs, func(p cashu.Proof, i int) bool { + _, found := lo.Find[string](usedSecrets, func(secret string) bool { + return secret == p.Secret + }) + return !found + }) + w.proofs = append(w.proofs, newProofs...) + err = storeProofs(newProofs) + if err != nil { + return nil, nil, err + } + for _, proof := range proofs { + err = invalidateProof(proof) + if err != nil { + return nil, nil, err + } + } + keepProofs := newProofs[:len(frstOutputs)] + sendProofs := newProofs[len(frstOutputs):] + return keepProofs, sendProofs, nil +} +func (w MintWallet) split(proofs []cashu.Proof, outputs cashu.BlindedMessages) (keep []cashu.BlindedSignature, err error) { + response, err := w.client.Split(cashu.SplitRequest{Proofs: proofs, Outputs: outputs}) + if err != nil { + return nil, err + } + if len(response.Promises) == 0 { + return nil, fmt.Errorf("received no splits.") + } + return response.Promises, nil } func SumProofs(p []cashu.Proof) uint64 { diff --git a/mint/mint.go b/mint/mint.go index 1faa76a..a90ea67 100644 --- a/mint/mint.go +++ b/mint/mint.go @@ -7,7 +7,6 @@ import ( "fmt" "math" "math/bits" - "reflect" "strings" "github.com/btcsuite/btcd/btcutil" @@ -289,10 +288,19 @@ func (m *Mint) generatePromise(amount uint64, keySet *crypto.KeySet, B_ *secp256 } // generatePromises will generate multiple promises and signatures -func (m *Mint) generatePromises(amounts []uint64, keySet *crypto.KeySet, keys []*secp256k1.PublicKey) ([]cashu.BlindedSignature, error) { +func (m *Mint) generatePromises(keySet *crypto.KeySet, outputs cashu.BlindedMessages) ([]cashu.BlindedSignature, error) { + // create first outputs and second outputs promises := make([]cashu.BlindedSignature, 0) - for i, key := range keys { - p, err := m.generatePromise(amounts[i], keySet, key) + for _, output := range outputs { + b, err := hex.DecodeString(output.B_) + if err != nil { + return nil, err + } + B, err := secp256k1.ParsePubKey(b) + if err != nil { + return nil, err + } + p, err := m.generatePromise(output.Amount, keySet, B) if err != nil { return nil, err } @@ -368,19 +376,6 @@ func verifyScript(proofs []cashu.Proof) (addr *btcutil.AddressScriptHash, err er } return addr, nil } - -// verifyOutputs verify output data -func verifyOutputs(total, amount uint64, outputs []cashu.BlindedMessage) (bool, error) { - fstAmt, sndAmt := total-amount, amount - fstOutputs := AmountSplit(fstAmt) - sndOutputs := AmountSplit(sndAmt) - expected := append(fstOutputs, sndOutputs...) - given := make([]uint64, 0) - for _, o := range outputs { - given = append(given, o.Amount) - } - return reflect.DeepEqual(given, expected), nil -} func verifyNoDuplicateProofs(proofs []cashu.Proof) bool { secrets := make([]string, 0) for _, proof := range proofs { @@ -441,11 +436,6 @@ func AmountSplit(amount uint64) []uint64 { return rv } -// verifySplitAmount will verify amount -func verifySplitAmount(amount uint64) (uint64, error) { - return verifyAmount(amount) -} - // verifyAmount make sure that amount is bigger than zero and smaller than 2^MaxOrder func verifyAmount(amount uint64) (uint64, error) { if amount < 0 || amount > uint64(math.Pow(2, crypto.MaxOrder)) { @@ -455,6 +445,10 @@ func verifyAmount(amount uint64) (uint64, error) { } func (m *Mint) verifyProofs(proofs []cashu.Proof) error { + + if _, err := verifyScript(proofs); err != nil { + return err + } // _verify_secret_criteria if err := verifySecretCriteria(proofs); err != nil { return err @@ -569,89 +563,51 @@ func (m *Mint) Melt(proofs []cashu.Proof, invoice string) (payment lightning.Pay } // split will split proofs. creates BlindedSignatures from BlindedMessages. -func (m *Mint) Split(proofs []cashu.Proof, amount uint64, outputs []cashu.BlindedMessage, keySet *crypto.KeySet) ([]cashu.BlindedSignature, []cashu.BlindedSignature, error) { +func (m *Mint) Split(proofs []cashu.Proof, outputs []cashu.BlindedMessage, keySet *crypto.KeySet) ([]cashu.BlindedSignature, error) { err := m.setProofsPending(proofs) if err != nil { - return nil, nil, err + return nil, err } defer m.unsetProofsPending(proofs, &err) total := lo.SumBy[cashu.Proof](proofs, func(p cashu.Proof) uint64 { return p.Amount }) - if amount > total { - return nil, nil, fmt.Errorf("split amount is higher than the total sum.") - } // verifySplitAmount - amount, err = verifySplitAmount(amount) - + total, err = verifyAmount(total) if err != nil { - return nil, nil, err + return nil, err } - - if _, err = verifyScript(proofs); err != nil { - return nil, nil, err + outputAmount := lo.SumBy[cashu.BlindedMessage](outputs, func(t cashu.BlindedMessage) uint64 { + return t.Amount + }) + if outputAmount > total { + return nil, fmt.Errorf("inputs do not have the same amount as outputs") } if err = m.verifyProofs(proofs); err != nil { - return nil, nil, err + return nil, err } if !verifyNoDuplicateOutputs(outputs) { - return nil, nil, fmt.Errorf("duplicate outputs.") + return nil, fmt.Errorf("duplicate outputs.") } // check outputs - _, err = verifyOutputs(total, amount, outputs) - if err != nil { - return nil, nil, err - } // invalidate proofs err = m.invalidateProofs(proofs) if err != nil { - return nil, nil, err - } - // create first outputs and second outputs - outsFts := AmountSplit(total - amount) - outsSnd := AmountSplit(amount) - B_fst := make([]*secp256k1.PublicKey, 0) - B_snd := make([]*secp256k1.PublicKey, 0) - for _, data := range outputs[:len(outsFts)] { - b, err := hex.DecodeString(data.B_) - if err != nil { - return nil, nil, err - } - key, err := secp256k1.ParsePubKey(b) - if err != nil { - return nil, nil, err - } - B_fst = append(B_fst, key) + return nil, err } - for _, data := range outputs[len(outsFts):] { - b, err := hex.DecodeString(data.B_) - if err != nil { - return nil, nil, err - } - key, err := secp256k1.ParsePubKey(b) - if err != nil { - return nil, nil, err - } - B_snd = append(B_snd, key) - } // create promises for outputs - fstPromise, err := m.generatePromises(outsFts, keySet, B_fst) + promises, err := m.generatePromises(keySet, outputs) if err != nil { - return nil, nil, err - } - sendPromise, err := m.generatePromises(outsSnd, keySet, B_snd) - if err != nil { - return nil, nil, err + return nil, err } - outs := append(fstPromise, sendPromise...) // check eq is balanced - _, err = verifyEquationBalanced(proofs, outs) + _, err = verifyEquationBalanced(proofs, promises) if err != nil { - return nil, nil, err + return nil, err } - return fstPromise, sendPromise, nil + return promises, nil } // verifySecretCriteria verifies that a secret is present and is not too long (DOS prevention). From b5633640be32166e856bd9e485698ccb15d80d7e Mon Sep 17 00:00:00 2001 From: gohumble Date: Sun, 16 Jul 2023 22:09:24 +0200 Subject: [PATCH 2/2] update default mint server --- cmd/cashu/feni/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/cashu/feni/config.go b/cmd/cashu/feni/config.go index eff5506..8abd054 100644 --- a/cmd/cashu/feni/config.go +++ b/cmd/cashu/feni/config.go @@ -32,8 +32,8 @@ func defaultConfig() { Config = WalletConfig{ Debug: true, Lightning: true, - MintServerHost: "https://8333.space", - MintServerPort: "3339", + MintServerHost: "https://localhost", + MintServerPort: "3338", Wallet: "wallet", }