diff --git a/cashu/cashu.go b/cashu/cashu.go index 87931ee..e656fd1 100644 --- a/cashu/cashu.go +++ b/cashu/cashu.go @@ -14,13 +14,6 @@ import ( "github.com/decred/dcrd/dcrec/secp256k1/v4" ) -type SecretKind int - -const ( - Random SecretKind = iota - P2PK -) - // Cashu BlindedMessage. See https://github.com/cashubtc/nuts/blob/main/00.md#blindedmessage type BlindedMessage struct { Amount uint64 `json:"amount"` @@ -80,43 +73,6 @@ type Proof struct { Witness string `json:"witness,omitempty"` } -func (p Proof) IsSecretP2PK() bool { - return p.SecretType() == P2PK -} - -func (p Proof) SecretType() SecretKind { - var rawJsonSecret []json.RawMessage - // if not valid json, assume it is random secret - if err := json.Unmarshal([]byte(p.Secret), &rawJsonSecret); err != nil { - return Random - } - - // Well-known secret should have a length of at least 2 - if len(rawJsonSecret) < 2 { - return Random - } - - var kind string - if err := json.Unmarshal(rawJsonSecret[0], &kind); err != nil { - return Random - } - - if kind == "P2PK" { - return P2PK - } - - return Random -} - -func (kind SecretKind) String() string { - switch kind { - case P2PK: - return "P2PK" - default: - return "random" - } -} - type Proofs []Proof // Amount returns the total amount from diff --git a/cashu/cashu_test.go b/cashu/cashu_test.go index 902c329..03fb9ea 100644 --- a/cashu/cashu_test.go +++ b/cashu/cashu_test.go @@ -121,41 +121,3 @@ func TestTokenToString(t *testing.T) { } } } - -func TestSecretType(t *testing.T) { - tests := []struct { - proof Proof - expectedKind SecretKind - expectedIsP2PK bool - }{ - { - proof: Proof{Secret: `["P2PK", {"nonce":"da62796403af76c80cd6ce9153ed3746","data":"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e","tags":[["sigflag","SIG_ALL"]]}]`}, - expectedKind: P2PK, - expectedIsP2PK: true, - }, - - { - proof: Proof{Secret: `["DIFFERENT", {"nonce":"da62796403af76c80cd6ce9153ed3746","data":"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e","tags":[]}]`}, - expectedKind: Random, - expectedIsP2PK: false, - }, - - { - proof: Proof{Secret: `someranadomsecret`}, - expectedKind: Random, - expectedIsP2PK: false, - }, - } - - for _, test := range tests { - kind := test.proof.SecretType() - if kind != test.expectedKind { - t.Fatalf("expected '%v' but got '%v' instead", test.expectedKind.String(), kind.String()) - } - - isP2PK := test.proof.IsSecretP2PK() - if isP2PK != test.expectedIsP2PK { - t.Fatalf("expected '%v' but got '%v' instead", test.expectedIsP2PK, isP2PK) - } - } -} diff --git a/cashu/nuts/nut10/nut10.go b/cashu/nuts/nut10/nut10.go index 1e73b30..1cc9dde 100644 --- a/cashu/nuts/nut10/nut10.go +++ b/cashu/nuts/nut10/nut10.go @@ -8,6 +8,46 @@ import ( "github.com/elnosh/gonuts/cashu" ) +type SecretKind int + +const ( + AnyoneCanSpend SecretKind = iota + P2PK +) + +func SecretType(proof cashu.Proof) SecretKind { + var rawJsonSecret []json.RawMessage + // if not valid json, assume it is random secret + if err := json.Unmarshal([]byte(proof.Secret), &rawJsonSecret); err != nil { + return AnyoneCanSpend + } + + // Well-known secret should have a length of at least 2 + if len(rawJsonSecret) < 2 { + return AnyoneCanSpend + } + + var kind string + if err := json.Unmarshal(rawJsonSecret[0], &kind); err != nil { + return AnyoneCanSpend + } + + if kind == "P2PK" { + return P2PK + } + + return AnyoneCanSpend +} + +func (kind SecretKind) String() string { + switch kind { + case P2PK: + return "P2PK" + default: + return "anyonecanspend" + } +} + type WellKnownSecret struct { Nonce string `json:"nonce"` Data string `json:"data"` @@ -15,7 +55,7 @@ type WellKnownSecret struct { } // SerializeSecret returns the json string to be put in the secret field of a proof -func SerializeSecret(kind cashu.SecretKind, secretData WellKnownSecret) (string, error) { +func SerializeSecret(kind SecretKind, secretData WellKnownSecret) (string, error) { jsonSecret, err := json.Marshal(secretData) if err != nil { return "", err diff --git a/cashu/nuts/nut10/nut10_test.go b/cashu/nuts/nut10/nut10_test.go index 61e93e2..8078615 100644 --- a/cashu/nuts/nut10/nut10_test.go +++ b/cashu/nuts/nut10/nut10_test.go @@ -7,6 +7,44 @@ import ( "github.com/elnosh/gonuts/cashu" ) +func TestSecretType(t *testing.T) { + tests := []struct { + proof cashu.Proof + expectedKind SecretKind + expectedIsP2PK bool + }{ + { + proof: cashu.Proof{Secret: `["P2PK", {"nonce":"da62796403af76c80cd6ce9153ed3746","data":"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e","tags":[["sigflag","SIG_ALL"]]}]`}, + expectedKind: P2PK, + expectedIsP2PK: true, + }, + + { + proof: cashu.Proof{Secret: `["DIFFERENT", {"nonce":"da62796403af76c80cd6ce9153ed3746","data":"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e","tags":[]}]`}, + expectedKind: AnyoneCanSpend, + expectedIsP2PK: false, + }, + + { + proof: cashu.Proof{Secret: `someranadomsecret`}, + expectedKind: AnyoneCanSpend, + expectedIsP2PK: false, + }, + } + + for _, test := range tests { + kind := SecretType(test.proof) + if kind != test.expectedKind { + t.Fatalf("expected '%v' but got '%v' instead", test.expectedKind.String(), kind.String()) + } + + isP2PK := kind == P2PK + if isP2PK != test.expectedIsP2PK { + t.Fatalf("expected '%v' but got '%v' instead", test.expectedIsP2PK, isP2PK) + } + } +} + func TestSerializeSecret(t *testing.T) { secretData := WellKnownSecret{ Nonce: "da62796403af76c80cd6ce9153ed3746", @@ -16,7 +54,7 @@ func TestSerializeSecret(t *testing.T) { }, } - serialized, err := SerializeSecret(cashu.P2PK, secretData) + serialized, err := SerializeSecret(P2PK, secretData) if err != nil { t.Fatalf("got unexpected error: %v", err) } diff --git a/cashu/nuts/nut11/nut11.go b/cashu/nuts/nut11/nut11.go index 889ea87..e565faa 100644 --- a/cashu/nuts/nut11/nut11.go +++ b/cashu/nuts/nut11/nut11.go @@ -5,19 +5,67 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" + "fmt" "reflect" + "slices" + "strconv" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/cashu/nuts/nut10" ) +const ( + // supported tags + SIGFLAG = "sigflag" + NSIGS = "n_sigs" + PUBKEYS = "pubkeys" + LOCKTIME = "locktime" + REFUND = "refund" + + // SIGFLAG types + SIGINPUTS = "SIG_INPUTS" + SIGALL = "SIG_ALL" + + // Error code + NUT11ErrCode cashu.CashuErrCode = 30001 +) + +type SigFlag int + +const ( + SigInputs SigFlag = iota + SigAll + Unknown +) + +// errors +var ( + InvalidTagErr = cashu.Error{Detail: "invalid tag", Code: NUT11ErrCode} + TooManyTagsErr = cashu.Error{Detail: "too many tags", Code: NUT11ErrCode} + NSigsMustBePositiveErr = cashu.Error{Detail: "n_sigs must be a positive integer", Code: NUT11ErrCode} + EmptyPubkeysErr = cashu.Error{Detail: "pubkeys tag cannot be empty if n_sigs tag is present", Code: NUT11ErrCode} + EmptyWitnessErr = cashu.Error{Detail: "witness cannot be empty", Code: NUT11ErrCode} + NotEnoughSignaturesErr = cashu.Error{Detail: "not enough valid signatures provided", Code: NUT11ErrCode} + AllSigAllFlagsErr = cashu.Error{Detail: "all flags must be SIG_ALL", Code: NUT11ErrCode} + SigAllKeysMustBeEqualErr = cashu.Error{Detail: "all public keys must be the same for SIG_ALL", Code: NUT11ErrCode} + SigAllOnlySwap = cashu.Error{Detail: "SIG_ALL can only be used in /swap operation", Code: NUT11ErrCode} + NSigsMustBeEqualErr = cashu.Error{Detail: "all n_sigs must be the same for SIG_ALL", Code: NUT11ErrCode} +) + type P2PKWitness struct { Signatures []string `json:"signatures"` } +type P2PKTags struct { + Sigflag string + NSigs int + Pubkeys []*btcec.PublicKey + Locktime int64 + Refund []*btcec.PublicKey +} + // P2PKSecret returns a secret with a spending condition // that will lock ecash to a public key func P2PKSecret(pubkey string) (string, error) { @@ -34,7 +82,7 @@ func P2PKSecret(pubkey string) (string, error) { Data: pubkey, } - secret, err := nut10.SerializeSecret(cashu.P2PK, secretData) + secret, err := nut10.SerializeSecret(nut10.P2PK, secretData) if err != nil { return "", err } @@ -42,6 +90,76 @@ func P2PKSecret(pubkey string) (string, error) { return secret, nil } +func ParseP2PKTags(tags [][]string) (*P2PKTags, error) { + if len(tags) > 5 { + return nil, TooManyTagsErr + } + + p2pkTags := P2PKTags{} + + for _, tag := range tags { + if len(tag) < 2 { + return nil, InvalidTagErr + } + tagType := tag[0] + switch tagType { + case SIGFLAG: + sigflagType := tag[1] + if sigflagType == SIGINPUTS || sigflagType == SIGALL { + p2pkTags.Sigflag = sigflagType + } else { + errmsg := fmt.Sprintf("invalig sigflag: %v", sigflagType) + return nil, cashu.BuildCashuError(errmsg, NUT11ErrCode) + } + case NSIGS: + nstr := tag[1] + nsig, err := strconv.ParseInt(nstr, 10, 8) + if err != nil { + errmsg := fmt.Sprintf("invalig n_sigs value: %v", err) + return nil, cashu.BuildCashuError(errmsg, NUT11ErrCode) + } + if nsig < 0 { + return nil, NSigsMustBePositiveErr + } + p2pkTags.NSigs = int(nsig) + case PUBKEYS: + pubkeys := make([]*btcec.PublicKey, len(tag)-1) + j := 0 + for i := 1; i < len(tag); i++ { + pubkey, err := ParsePublicKey(tag[i]) + if err != nil { + return nil, err + } + pubkeys[j] = pubkey + j++ + } + p2pkTags.Pubkeys = pubkeys + case LOCKTIME: + locktimestr := tag[1] + locktime, err := strconv.ParseInt(locktimestr, 10, 64) + if err != nil { + errmsg := fmt.Sprintf("invalid locktime: %v", err) + return nil, cashu.BuildCashuError(errmsg, NUT11ErrCode) + } + p2pkTags.Locktime = locktime + case REFUND: + refundKeys := make([]*btcec.PublicKey, len(tag)-1) + j := 0 + for i := 1; i < len(tag); i++ { + pubkey, err := ParsePublicKey(tag[i]) + if err != nil { + return nil, err + } + refundKeys[j] = pubkey + j++ + } + p2pkTags.Refund = refundKeys + } + } + + return &p2pkTags, nil +} + func AddSignatureToInputs(inputs cashu.Proofs, signingKey *btcec.PrivateKey) (cashu.Proofs, error) { for i, proof := range inputs { hash := sha256.Sum256([]byte(proof.Secret)) @@ -98,10 +216,46 @@ func AddSignatureToOutputs( return outputs, nil } +// PublicKeys returns a list of public keys that can sign +// a P2PK locked proof +func PublicKeys(secret nut10.WellKnownSecret) ([]*btcec.PublicKey, error) { + p2pkTags, err := ParseP2PKTags(secret.Tags) + if err != nil { + return nil, err + } + + pubkey, err := ParsePublicKey(secret.Data) + if err != nil { + return nil, err + } + pubkeys := append([]*btcec.PublicKey{pubkey}, p2pkTags.Pubkeys...) + return pubkeys, nil +} + +func IsSecretP2PK(proof cashu.Proof) bool { + return nut10.SecretType(proof) == nut10.P2PK +} + +// ProofsSigAll returns true if at least one of the proofs +// in the list has a SIG_ALL flag +func ProofsSigAll(proofs cashu.Proofs) bool { + for _, proof := range proofs { + secret, err := nut10.DeserializeSecret(proof.Secret) + if err != nil { + return false + } + + if IsSigAll(secret) { + return true + } + } + return false +} + func IsSigAll(secret nut10.WellKnownSecret) bool { for _, tag := range secret.Tags { if len(tag) == 2 { - if tag[0] == "sigflag" && tag[1] == "SIG_ALL" { + if tag[0] == SIGFLAG && tag[1] == SIGALL { return true } } @@ -111,12 +265,7 @@ func IsSigAll(secret nut10.WellKnownSecret) bool { } func CanSign(secret nut10.WellKnownSecret, key *btcec.PrivateKey) bool { - secretData, err := hex.DecodeString(secret.Data) - if err != nil { - return false - } - - publicKey, err := secp256k1.ParsePubKey(secretData) + publicKey, err := ParsePublicKey(secret.Data) if err != nil { return false } @@ -127,3 +276,57 @@ func CanSign(secret nut10.WellKnownSecret, key *btcec.PrivateKey) bool { return false } + +func HasValidSignatures(hash []byte, witness P2PKWitness, Nsigs int, pubkeys []*btcec.PublicKey) bool { + pubkeysCopy := make([]*btcec.PublicKey, len(pubkeys)) + copy(pubkeysCopy, pubkeys) + + validSignatures := 0 + for _, signature := range witness.Signatures { + sig, err := ParseSignature(signature) + if err != nil { + continue + } + + for i, pubkey := range pubkeysCopy { + if sig.Verify(hash, pubkey) { + validSignatures++ + if len(pubkeysCopy) > 1 { + pubkeysCopy = slices.Delete(pubkeysCopy, i, i+1) + } + break + } + } + } + + return validSignatures >= Nsigs +} + +func ParsePublicKey(key string) (*btcec.PublicKey, error) { + hexPubkey, err := hex.DecodeString(key) + if err != nil { + errmsg := fmt.Sprintf("invalid public key: %v", err) + return nil, cashu.BuildCashuError(errmsg, NUT11ErrCode) + } + pubkey, err := btcec.ParsePubKey(hexPubkey) + if err != nil { + errmsg := fmt.Sprintf("invalid public key: %v", err) + return nil, cashu.BuildCashuError(errmsg, NUT11ErrCode) + } + return pubkey, nil +} + +func ParseSignature(signature string) (*schnorr.Signature, error) { + hexSig, err := hex.DecodeString(signature) + if err != nil { + errmsg := fmt.Sprintf("invalid signature: %v", err) + return nil, cashu.BuildCashuError(errmsg, NUT11ErrCode) + } + sig, err := schnorr.ParseSignature(hexSig) + if err != nil { + errmsg := fmt.Sprintf("invalid signature: %v", err) + return nil, cashu.BuildCashuError(errmsg, NUT11ErrCode) + } + + return sig, nil +} diff --git a/mint/mint.go b/mint/mint.go index 3cf50e9..5a62fa3 100644 --- a/mint/mint.go +++ b/mint/mint.go @@ -1,13 +1,16 @@ package mint import ( + "crypto/sha256" "database/sql" "encoding/hex" + "encoding/json" "errors" "fmt" "log" "os" "path/filepath" + "reflect" "time" "github.com/btcsuite/btcd/btcec/v2" @@ -18,6 +21,8 @@ import ( "github.com/elnosh/gonuts/cashu/nuts/nut04" "github.com/elnosh/gonuts/cashu/nuts/nut05" "github.com/elnosh/gonuts/cashu/nuts/nut06" + "github.com/elnosh/gonuts/cashu/nuts/nut10" + "github.com/elnosh/gonuts/cashu/nuts/nut11" "github.com/elnosh/gonuts/crypto" "github.com/elnosh/gonuts/mint/lightning" "github.com/elnosh/gonuts/mint/storage" @@ -388,6 +393,13 @@ func (m *Mint) Swap(proofs cashu.Proofs, blindedMessages cashu.BlindedMessages) return nil, cashu.BlindedMessageAlreadySigned } + // if sig all, verify signatures in blinded messages + if nut11.ProofsSigAll(proofs) { + if err := verifyP2PKBlindedMessages(proofs, blindedMessages); err != nil { + return nil, err + } + } + // if verification complete, sign blinded messages blindedSignatures, err := m.signBlindedMessages(blindedMessages) if err != nil { @@ -512,6 +524,10 @@ func (m *Mint) MeltTokens(method, quoteId string, proofs cashu.Proofs) (storage. return storage.MeltQuote{}, cashu.InsufficientProofsAmount } + if nut11.ProofsSigAll(proofs) { + return storage.MeltQuote{}, nut11.SigAllOnlySwap + } + // if proofs are valid, ask the lightning backend // to make the payment preimage, err := m.lightningClient.SendPayment(meltQuote.InvoiceRequest, meltQuote.Amount) @@ -574,6 +590,13 @@ func (m *Mint) verifyProofs(proofs cashu.Proofs, Ys []string) error { } } + // if P2PK locked proof, verify valid witness + if nut11.IsSecretP2PK(proof) { + if err := verifyP2PKLockedProof(proof); err != nil { + return err + } + } + Cbytes, err := hex.DecodeString(proof.C) if err != nil { return cashu.BuildCashuError(err.Error(), cashu.StandardErrCode) @@ -591,6 +614,154 @@ func (m *Mint) verifyProofs(proofs cashu.Proofs, Ys []string) error { return nil } +func verifyP2PKLockedProof(proof cashu.Proof) error { + p2pkWellKnownSecret, err := nut10.DeserializeSecret(proof.Secret) + if err != nil { + return cashu.BuildCashuError(err.Error(), cashu.StandardErrCode) + } + + var p2pkWitness nut11.P2PKWitness + err = json.Unmarshal([]byte(proof.Witness), &p2pkWitness) + if err != nil { + errmsg := fmt.Sprintf("invalid witness: %v", err) + return cashu.BuildCashuError(errmsg, nut11.NUT11ErrCode) + } + if len(p2pkWitness.Signatures) < 1 { + return nut11.EmptyWitnessErr + } + + p2pkTags, err := nut11.ParseP2PKTags(p2pkWellKnownSecret.Tags) + if err != nil { + return err + } + + signaturesRequired := 1 + // if locktime is expired and there is no refund pubkey, treat as anyone can spend + // if refund pubkey present, check signature + if p2pkTags.Locktime > 0 && time.Now().Local().Unix() > p2pkTags.Locktime { + if len(p2pkTags.Refund) == 0 { + return nil + } else { + hash := sha256.Sum256([]byte(proof.Secret)) + if !nut11.HasValidSignatures(hash[:], p2pkWitness, signaturesRequired, p2pkTags.Refund) { + return nut11.NotEnoughSignaturesErr + } + } + } else { + pubkey, err := nut11.ParsePublicKey(p2pkWellKnownSecret.Data) + if err != nil { + return err + } + keys := []*btcec.PublicKey{pubkey} + // message to sign + hash := sha256.Sum256([]byte(proof.Secret)) + + if p2pkTags.NSigs > 0 { + signaturesRequired = p2pkTags.NSigs + if len(p2pkTags.Pubkeys) == 0 { + return nut11.EmptyPubkeysErr + } + keys = append(keys, p2pkTags.Pubkeys...) + } + + if !nut11.HasValidSignatures(hash[:], p2pkWitness, signaturesRequired, keys) { + return nut11.NotEnoughSignaturesErr + } + } + return nil +} + +func verifyP2PKBlindedMessages(proofs cashu.Proofs, blindedMessages cashu.BlindedMessages) error { + isSigAll := false + for _, proof := range proofs { + secret, err := nut10.DeserializeSecret(proof.Secret) + if err != nil { + continue + } + + if nut11.IsSigAll(secret) { + isSigAll = true + break + } + } + + if isSigAll { + secret, err := nut10.DeserializeSecret(proofs[0].Secret) + if err != nil { + return cashu.BuildCashuError(err.Error(), cashu.StandardErrCode) + } + pubkeys, err := nut11.PublicKeys(secret) + if err != nil { + return err + } + + signaturesRequired := 1 + p2pkTags, err := nut11.ParseP2PKTags(secret.Tags) + if err != nil { + return err + } + if p2pkTags.NSigs > 0 { + signaturesRequired = p2pkTags.NSigs + } + + for _, proof := range proofs { + secret, err := nut10.DeserializeSecret(proof.Secret) + if err != nil { + return cashu.BuildCashuError(err.Error(), cashu.StandardErrCode) + } + // all flags need to be SIG_ALL + if !nut11.IsSigAll(secret) { + return nut11.AllSigAllFlagsErr + } + + currentSignaturesRequired := 1 + p2pkTags, err := nut11.ParseP2PKTags(secret.Tags) + if err != nil { + return err + } + if p2pkTags.NSigs > 0 { + currentSignaturesRequired = p2pkTags.NSigs + } + + currentKeys, err := nut11.PublicKeys(secret) + if err != nil { + return err + } + + // list of valid keys should be the same + // across all proofs + if !reflect.DeepEqual(pubkeys, currentKeys) { + return nut11.SigAllKeysMustBeEqualErr + } + + // all n_sigs must be same + if signaturesRequired != currentSignaturesRequired { + return nut11.NSigsMustBeEqualErr + } + } + + for _, bm := range blindedMessages { + hash := sha256.Sum256([]byte(bm.B_)) + + var witness nut11.P2PKWitness + err := json.Unmarshal([]byte(bm.Witness), &witness) + if err != nil { + errmsg := fmt.Sprintf("invalid witness: %v", err) + return cashu.BuildCashuError(errmsg, nut11.NUT11ErrCode) + } + if len(witness.Signatures) < 1 { + return nut11.EmptyWitnessErr + } + + if !nut11.HasValidSignatures(hash[:], witness, signaturesRequired, pubkeys) { + return nut11.NotEnoughSignaturesErr + } + } + } + + return nil +} + // signBlindedMessages will sign the blindedMessages and // return the blindedSignatures func (m *Mint) signBlindedMessages(blindedMessages cashu.BlindedMessages) (cashu.BlindedSignatures, error) { @@ -694,8 +865,8 @@ func (m *Mint) SetMintInfo(mintInfo MintInfo) error { 7: map[string]bool{"supported": false}, 8: map[string]bool{"supported": false}, 9: map[string]bool{"supported": false}, - 10: map[string]bool{"supported": false}, - 11: map[string]bool{"supported": false}, + 10: map[string]bool{"supported": true}, + 11: map[string]bool{"supported": true}, 12: map[string]bool{"supported": false}, } diff --git a/wallet/wallet.go b/wallet/wallet.go index 2877316..798132b 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -510,7 +510,7 @@ func (w *Wallet) Receive(token cashu.Token, swapToTrusted bool) (uint64, error) func (w *Wallet) swap(proofsToSwap cashu.Proofs, mintURL string) (cashu.Proofs, error) { var nut10secret nut10.WellKnownSecret // if P2PK, add signature to Witness in the proofs - if proofsToSwap[0].IsSecretP2PK() { + if nut11.IsSecretP2PK(proofsToSwap[0]) { var err error nut10secret, err = nut10.DeserializeSecret(proofsToSwap[0].Secret) if err != nil { @@ -559,7 +559,7 @@ func (w *Wallet) swap(proofsToSwap cashu.Proofs, mintURL string) (cashu.Proofs, } // if P2PK locked ecash has `SIG_ALL` flag, sign outputs - if proofsToSwap[0].IsSecretP2PK() && nut11.IsSigAll(nut10secret) { + if nut11.IsSecretP2PK(proofsToSwap[0]) && nut11.IsSigAll(nut10secret) { outputs, err = nut11.AddSignatureToOutputs(outputs, w.privateKey) if err != nil { return nil, fmt.Errorf("error signing outputs: %v", err) @@ -615,7 +615,8 @@ func (w *Wallet) swapToTrusted(token cashu.Token) (cashu.Proofs, error) { fees := uint64(w.fees(proofsToSwap, mint)) // if proofs are P2PK locked, sign appropriately - if proofsToSwap[0].IsSecretP2PK() { + //if proofsToSwap[0].IsSecretP2PK() { + if nut11.IsSecretP2PK(proofsToSwap[0]) { nut10secret, err := nut10.DeserializeSecret(proofsToSwap[0].Secret) if err != nil { return nil, err