Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove split amount #51

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
8 changes: 3 additions & 5 deletions cashu/cashu.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand Down Expand Up @@ -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"`
}
4 changes: 2 additions & 2 deletions cmd/cashu/feni/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}

Expand Down
91 changes: 48 additions & 43 deletions cmd/cashu/feni/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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())
Expand All @@ -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 {
Expand Down
110 changes: 33 additions & 77 deletions mint/mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"fmt"
"math"
"math/bits"
"reflect"
"strings"

"github.com/btcsuite/btcd/btcutil"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)) {
Expand All @@ -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
Expand Down Expand Up @@ -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).
Expand Down