Skip to content
Draft
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
29 changes: 29 additions & 0 deletions cashu/nuts/nut10/nut10.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ package nut10

import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/elnosh/gonuts/cashu"
)

type SecretKind int
Expand Down Expand Up @@ -119,3 +124,27 @@ func NewSecretFromSpendingCondition(spendingCondition SpendingCondition) (string

return secret, nil
}
func SigAllMessageToSign(inputs cashu.Proofs, outputs cashu.BlindedMessages, isMelt bool, meltquote string) string {
message := ""
for _, proof := range inputs {
message = message + proof.Secret
}
for _, blindMessage := range outputs {
message = message + blindMessage.B_
}
if isMelt {
message = message + meltquote
}
return message

}
func SignMessage(message string, privateKey *btcec.PrivateKey) (*schnorr.Signature, error) {
hashMessage := sha256.Sum256([]byte(message))
signature, err := schnorr.Sign(privateKey, hashMessage[:])
if err != nil {
return nil, err
}

return signature, nil

}
144 changes: 118 additions & 26 deletions cashu/nuts/nut11/nut11.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ import (

const (
// supported tags
SIGFLAG = "sigflag"
NSIGS = "n_sigs"
PUBKEYS = "pubkeys"
LOCKTIME = "locktime"
REFUND = "refund"
SIGFLAG = "sigflag"
NSIGS = "n_sigs"
PUBKEYS = "pubkeys"
LOCKTIME = "locktime"
REFUND = "refund"
NSIGREFUND = "n_sigs_refund"

// SIGFLAG types
SIGINPUTS = "SIG_INPUTS"
Expand Down Expand Up @@ -54,11 +55,12 @@ type P2PKWitness struct {
}

type P2PKTags struct {
Sigflag string
NSigs int
Pubkeys []*btcec.PublicKey
Locktime int64
Refund []*btcec.PublicKey
Sigflag string
NSigs int
Pubkeys []*btcec.PublicKey
Locktime int64
Refund []*btcec.PublicKey
NSigsRefund int
}

func SerializeP2PKTags(p2pkTags P2PKTags) [][]string {
Expand Down Expand Up @@ -95,7 +97,7 @@ func SerializeP2PKTags(p2pkTags P2PKTags) [][]string {
}

func ParseP2PKTags(tags [][]string) (*P2PKTags, error) {
if len(tags) > 5 {
if len(tags) > 6 {
return nil, TooManyTagsErr
}

Expand Down Expand Up @@ -158,33 +160,51 @@ func ParseP2PKTags(tags [][]string) (*P2PKTags, error) {
j++
}
p2pkTags.Refund = refundKeys
case NSIGREFUND:
nstr := tag[1]
nsig, err := strconv.ParseInt(nstr, 10, 8)
if err != nil {
errmsg := fmt.Sprintf("invalig n_sigs_refund value: %v", err)
return nil, cashu.BuildCashuError(errmsg, NUT11ErrCode)
}
if nsig < 0 {
return nil, NSigsMustBePositiveErr
}
p2pkTags.NSigsRefund = int(nsig)
}
}

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))
signature, err := schnorr.Sign(signingKey, hash[:])
if err != nil {
return nil, err
}
signatureBytes := signature.Serialize()
func AddSignatureToInput(input cashu.Proof, signingKey *btcec.PrivateKey) (cashu.Proof, error) {
hash := sha256.Sum256([]byte(input.Secret))
signature, err := schnorr.Sign(signingKey, hash[:])
if err != nil {
return cashu.Proof{}, err
}
signatureBytes := signature.Serialize()

p2pkWitness := P2PKWitness{
Signatures: []string{hex.EncodeToString(signatureBytes)},
}
p2pkWitness := P2PKWitness{
Signatures: []string{hex.EncodeToString(signatureBytes)},
}

witness, err := json.Marshal(p2pkWitness)
witness, err := json.Marshal(p2pkWitness)
if err != nil {
return cashu.Proof{}, err
}
input.Witness = string(witness)
return input, nil
}

func AddSignatureToInputs(inputs cashu.Proofs, signingKey *btcec.PrivateKey) (cashu.Proofs, error) {
for i, proof := range inputs {
signedProof, err := AddSignatureToInput(proof, signingKey)
if err != nil {
return nil, err
}
proof.Witness = string(witness)
inputs[i] = proof
inputs[i] = signedProof
}

return inputs, nil
}

Expand Down Expand Up @@ -402,3 +422,75 @@ func VerifyP2PKLockedProof(proof cashu.Proof, proofSecret nut10.WellKnownSecret)
}
return nil
}

type SigflagValidation struct {
sigFlag string
signaturesRequired uint
pubkeys map[*btcec.PublicKey]bool
}

func checkForSigAll(proofs cashu.Proofs) (SigflagValidation, error) {
sigFlagValidation := SigflagValidation{
sigFlag: SIGINPUTS,
signaturesRequired: 0,
pubkeys: make(map[*btcec.PublicKey]bool),
}
for _, proof := range proofs {
wellknownSecret, err := nut10.DeserializeSecret(proof.Secret)
if err != nil {
return sigFlagValidation, err
}

p2pkTags, err := ParseP2PKTags(wellknownSecret.Data.Tags)
if err != nil {
return sigFlagValidation, err
}

if p2pkTags != nil {
if p2pkTags.Sigflag == SIGALL {
sigFlagValidation.sigFlag = SIGALL
}
if sigFlagValidation.signaturesRequired < uint(p2pkTags.NSigs) {
sigFlagValidation.signaturesRequired = uint(p2pkTags.NSigs)
}

for _, pubkey := range p2pkTags.Pubkeys {
sigFlagValidation.pubkeys[pubkey] = true
}
}
}
return sigFlagValidation, nil
}

func verifySigAll(proofs cashu.Proofs) (SigflagValidation, error) {
sigFlagValidation := SigflagValidation{
sigFlag: SIGINPUTS,
signaturesRequired: 0,
pubkeys: make(map[*btcec.PublicKey]bool),
}
for _, proof := range proofs {
wellknownSecret, err := nut10.DeserializeSecret(proof.Secret)
if err != nil {
return sigFlagValidation, err
}

p2pkTags, err := ParseP2PKTags(wellknownSecret.Data.Tags)
if err != nil {
return sigFlagValidation, err
}

if p2pkTags != nil {
if p2pkTags.Sigflag == SIGALL {
sigFlagValidation.sigFlag = SIGALL
}
if sigFlagValidation.signaturesRequired < uint(p2pkTags.NSigs) {
sigFlagValidation.signaturesRequired = uint(p2pkTags.NSigs)
}

for _, pubkey := range p2pkTags.Pubkeys {
sigFlagValidation.pubkeys[pubkey] = true
}
}
}
return sigFlagValidation, nil
}
62 changes: 50 additions & 12 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math"
Expand Down Expand Up @@ -557,10 +558,7 @@ func (w *Wallet) Receive(token cashu.Token, swapToTrusted bool) (uint64, error)
if !nut11.CanSign(nut10Secret, w.privateKey) {
return 0, fmt.Errorf("cannot sign locked proofs")
}
proofsToSwap, err = nut11.AddSignatureToInputs(proofsToSwap, w.privateKey)
if err != nil {
return 0, fmt.Errorf("error signing inputs: %v", err)
}

}

// if mint in token is already the default mint, do not swap to trusted
Expand Down Expand Up @@ -594,12 +592,31 @@ func (w *Wallet) Receive(token cashu.Token, swapToTrusted bool) (uint64, error)
if err != nil {
return 0, fmt.Errorf("could not create swap request: %v", err)
}
nut10Secret, err := nut10.DeserializeSecret(proofsToSwap[0].Secret)
if err == nil && nut10Secret.Kind == nut10.P2PK && nut11.IsSigAll(nut10Secret) {
if nut11.IsSigAll(nut10Secret) {
msgToSign := nut10.SigAllMessageToSign(req.inputs, req.outputs, false, "")

//if P2PK locked ecash has `SIG_ALL` flag, sign outputs
if nut10Secret.Kind == nut10.P2PK && nut11.IsSigAll(nut10Secret) {
req.outputs, err = nut11.AddSignatureToOutputs(req.outputs, w.privateKey)
if err != nil {
return 0, fmt.Errorf("error signing outputs: %v", err)
sig, err := nut10.SignMessage(msgToSign, w.privateKey)
if err != nil {
return 0, fmt.Errorf("error signing sigall message: %v", err)
}

sigAllWitness := nut11.P2PKWitness{
Signatures: []string{hex.EncodeToString(sig.Serialize())},
}

witness, err := json.Marshal(sigAllWitness)
if err != nil {
return 0, err
}
// sign only the first input and keep the rest as unsigned
req.inputs[0].Witness = string(witness)
} else {
req.inputs, err = nut11.AddSignatureToInputs(req.inputs, w.privateKey)
if err != nil {
return 0, fmt.Errorf("error signing inputs: %v", err)
}
}
}

Expand Down Expand Up @@ -754,9 +771,30 @@ func (w *Wallet) swapToTrusted(proofs cashu.Proofs, mint *walletMint) (uint64, e
if err != nil {
return 0, fmt.Errorf("could not create swap request: %v", err)
}
req.outputs, err = nut11.AddSignatureToOutputs(req.outputs, w.privateKey)
if err != nil {
return 0, fmt.Errorf("error signing outputs: %v", err)
if nut11.IsSigAll(nut10Secret) {
msgToSign := nut10.SigAllMessageToSign(req.inputs, req.outputs, false, "")

sig, err := nut10.SignMessage(msgToSign, w.privateKey)
if err != nil {
return 0, fmt.Errorf("error signing sigall message: %v", err)
}

sigAllWitness := nut11.P2PKWitness{
Signatures: []string{hex.EncodeToString(sig.Serialize())},
}

witness, err := json.Marshal(sigAllWitness)
if err != nil {
return 0, err
}
// sign only the first input and keep the rest as unsigned
req.inputs[0].Witness = string(witness)

} else {
req.inputs, err = nut11.AddSignatureToInputs(req.inputs, w.privateKey)
if err != nil {
return 0, fmt.Errorf("error signing inputs: %v", err)
}
}

newProofs, err := swap(mint.mintURL, req)
Expand Down
Loading