diff --git a/cashu/cashu_test.go b/cashu/cashu_test.go index 11d2c09..b020d5d 100644 --- a/cashu/cashu_test.go +++ b/cashu/cashu_test.go @@ -113,3 +113,41 @@ 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_test.go b/cashu/nuts/nut10/nut10_test.go new file mode 100644 index 0000000..61e93e2 --- /dev/null +++ b/cashu/nuts/nut10/nut10_test.go @@ -0,0 +1,54 @@ +package nut10 + +import ( + "reflect" + "testing" + + "github.com/elnosh/gonuts/cashu" +) + +func TestSerializeSecret(t *testing.T) { + secretData := WellKnownSecret{ + Nonce: "da62796403af76c80cd6ce9153ed3746", + Data: "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e", + Tags: [][]string{ + {"sigflag", "SIG_ALL"}, + }, + } + + serialized, err := SerializeSecret(cashu.P2PK, secretData) + if err != nil { + t.Fatalf("got unexpected error: %v", err) + } + + expected := `["P2PK", {"nonce":"da62796403af76c80cd6ce9153ed3746","data":"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e","tags":[["sigflag","SIG_ALL"]]}]` + + if serialized != expected { + t.Fatalf("expected secret:\n%v\n\n but got:\n%v", expected, serialized) + } +} + +func TestDeserializeSecret(t *testing.T) { + secret := `["P2PK", {"nonce":"da62796403af76c80cd6ce9153ed3746","data":"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e","tags":[["sigflag","SIG_ALL"]]}]` + secretData, err := DeserializeSecret(secret) + if err != nil { + t.Fatalf("got unexpected error: %v", err) + } + + expectedNonce := "da62796403af76c80cd6ce9153ed3746" + if secretData.Nonce != expectedNonce { + t.Fatalf("expected nonce '%v' but got '%v' instead", expectedNonce, secretData.Nonce) + } + + expectedData := "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e" + if secretData.Data != expectedData { + t.Fatalf("expected data '%v' but got '%v' instead", expectedData, secretData.Data) + } + + expectedTags := [][]string{ + {"sigflag", "SIG_ALL"}, + } + if !reflect.DeepEqual(secretData.Tags, expectedTags) { + t.Fatalf("expected tags '%v' but got '%v' instead", expectedTags, secretData.Tags) + } +} diff --git a/cashu/nuts/nut11/nut11_test.go b/cashu/nuts/nut11/nut11_test.go new file mode 100644 index 0000000..c74bc59 --- /dev/null +++ b/cashu/nuts/nut11/nut11_test.go @@ -0,0 +1,84 @@ +package nut11 + +import ( + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/elnosh/gonuts/cashu/nuts/nut10" +) + +func TestIsSigAll(t *testing.T) { + tests := []struct { + p2pkSecretData nut10.WellKnownSecret + expected bool + }{ + { + p2pkSecretData: nut10.WellKnownSecret{ + Tags: [][]string{}, + }, + expected: false, + }, + { + p2pkSecretData: nut10.WellKnownSecret{ + Tags: [][]string{{"sigflag", "SIG_INPUTS"}}, + }, + expected: false, + }, + { + p2pkSecretData: nut10.WellKnownSecret{ + Tags: [][]string{ + {"locktime", "882912379"}, + {"refund", "refundkey"}, + {"sigflag", "SIG_ALL"}, + }, + }, + expected: true, + }, + } + + for _, test := range tests { + result := IsSigAll(test.p2pkSecretData) + if result != test.expected { + t.Fatalf("expected '%v' but got '%v' instead", test.expected, result) + } + } +} + +func TestCanSign(t *testing.T) { + privateKey, _ := btcec.NewPrivateKey() + publicKey := hex.EncodeToString(privateKey.PubKey().SerializeCompressed()) + + tests := []struct { + p2pkSecretData nut10.WellKnownSecret + expected bool + }{ + { + p2pkSecretData: nut10.WellKnownSecret{ + Data: publicKey, + }, + expected: true, + }, + + { + p2pkSecretData: nut10.WellKnownSecret{ + Data: "somerandomkey", + }, + expected: false, + }, + + { + p2pkSecretData: nut10.WellKnownSecret{ + Data: "sdjflksjdflsdjfd", + }, + expected: false, + }, + } + + for _, test := range tests { + result := CanSign(test.p2pkSecretData, privateKey) + if result != test.expected { + t.Fatalf("expected '%v' but got '%v' instead", test.expected, result) + } + } +} diff --git a/wallet/wallet.go b/wallet/wallet.go index bad1b19..a788f27 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -487,40 +487,33 @@ func (w *Wallet) swap(proofsToSwap cashu.Proofs, mintURL string) (cashu.Proofs, } } - var activeSatKeyset crypto.WalletKeyset - var outputs cashu.BlindedMessages - var secrets []string - var rs []*secp256k1.PrivateKey - var err error + var activeKeysets map[string]crypto.WalletKeyset walletMint, trustedMint := w.mints[mintURL] if !trustedMint { - activeKeysets, err := GetMintActiveKeysets(mintURL) + // get keys if mint not trusted + var err error + activeKeysets, err = GetMintActiveKeysets(mintURL) if err != nil { return nil, err } - for _, k := range activeKeysets { - activeSatKeyset = k - break - } - - // create blinded messages - outputs, secrets, rs, err = w.createBlindedMessages(proofsToSwap.Amount(), activeSatKeyset.Id, nil) - if err != nil { - return nil, fmt.Errorf("createBlindedMessages: %v", err) - } } else { - for _, k := range walletMint.activeKeysets { - activeSatKeyset = k - break - } + activeKeysets = walletMint.activeKeysets + } - counter := w.counterForKeyset(activeSatKeyset.Id) + var activeSatKeyset crypto.WalletKeyset + for _, k := range activeKeysets { + activeSatKeyset = k + break + } + var counter *uint32 = nil + if trustedMint { + keysetCounter := w.counterForKeyset(activeSatKeyset.Id) + counter = &keysetCounter + } - // create blinded messages - outputs, secrets, rs, err = w.createBlindedMessages(proofsToSwap.Amount(), activeSatKeyset.Id, &counter) - if err != nil { - return nil, fmt.Errorf("createBlindedMessages: %v", err) - } + outputs, secrets, rs, err := w.createBlindedMessages(proofsToSwap.Amount(), activeSatKeyset.Id, counter) + if err != nil { + return nil, fmt.Errorf("createBlindedMessages: %v", err) } // if P2PK locked ecash has `SIG_ALL` flag, sign outputs @@ -611,8 +604,8 @@ func (w *Wallet) swapToTrusted(token cashu.Token) (cashu.Proofs, error) { if err != nil { return nil, err } - } else { // if not sig all, can just sign inputs and no need to do a swap first - // check that public key in data is one wallet can sign for + } else { + // if not sig all, can just sign inputs and no need to do a swap first if !nut11.CanSign(nut10secret, w.privateKey) { return nil, fmt.Errorf("cannot sign locked proofs") } @@ -788,8 +781,7 @@ func (w *Wallet) getProofsForAmount(amount uint64, mintURL string, pubkeyLock *b var secrets []string var rs []*secp256k1.PrivateKey var counter, incrementCounterBy uint32 - // check here if lock is present and if so, use it to generate blinded messages - // instead of generating from counter + if pubkeyLock == nil { counter = w.counterForKeyset(activeSatKeyset.Id) var err error diff --git a/wallet/wallet_integration_test.go b/wallet/wallet_integration_test.go index 2df41a7..9da9f4f 100644 --- a/wallet/wallet_integration_test.go +++ b/wallet/wallet_integration_test.go @@ -377,13 +377,99 @@ func TestWalletBalance(t *testing.T) { } } -func TestWalletRestore(t *testing.T) { +func TestSendToPubkey(t *testing.T) { nutshellMint, err := testutils.CreateNutshellMintContainer(ctx) if err != nil { t.Fatalf("error starting nutshell mint: %v", err) } defer nutshellMint.Terminate(ctx) + nutshellURL := nutshellMint.Host + + nutshellMint2, err := testutils.CreateNutshellMintContainer(ctx) + if err != nil { + t.Fatalf("error starting nutshell mint: %v", err) + } + defer nutshellMint2.Terminate(ctx) + + testWalletPath := filepath.Join(".", "/testwalletp2pk") + testWallet, err := testutils.CreateTestWallet(testWalletPath, nutshellURL) + if err != nil { + t.Fatal(err) + } + defer func() { + os.RemoveAll(testWalletPath) + }() + + testWalletPath2 := filepath.Join(".", "/testwalletp2pk2") + testWallet2, err := testutils.CreateTestWallet(testWalletPath2, nutshellMint2.Host) + if err != nil { + t.Fatal(err) + } + defer func() { + os.RemoveAll(testWalletPath2) + }() + + mintRequest, err := testWallet.RequestMint(20000) + if err != nil { + t.Fatalf("unexpected error in mint request: %v", err) + } + _, err = testWallet.MintTokens(mintRequest.Quote) + if err != nil { + t.Fatalf("unexpected error in mint tokens: %v", err) + } + + receiverPubkey := testWallet2.GetReceivePubkey() + lockedEcash, err := testWallet.SendToPubkey(500, nutshellURL, receiverPubkey) + if err != nil { + t.Fatalf("unexpected error generating locked ecash: %v", err) + } + + // try receiving invalid + _, err = testWallet.Receive(*lockedEcash, true) + if err == nil { + t.Fatal("expected error trying to redeem locked ecash") + } + + // this should unlock ecash and swap to trusted mint + amountReceived, err := testWallet2.Receive(*lockedEcash, true) + if err != nil { + t.Fatalf("unexpected error receiving locked ecash: %v", err) + } + + trustedMints := testWallet2.TrustedMints() + if len(trustedMints) != 1 { + t.Fatalf("expected len of trusted mints '%v' but got '%v' instead", 1, len(trustedMints)) + } + + balance := testWallet2.GetBalance() + if balance != amountReceived { + t.Fatalf("expected balance of '%v' but got '%v' instead", amountReceived, balance) + } + + lockedEcash, err = testWallet.SendToPubkey(500, nutshellURL, receiverPubkey) + if err != nil { + t.Fatalf("unexpected error generating locked ecash: %v", err) + } + + // unlock ecash and trust mint + amountReceived, err = testWallet2.Receive(*lockedEcash, false) + if err != nil { + t.Fatalf("unexpected error receiving locked ecash: %v", err) + } + + trustedMints = testWallet2.TrustedMints() + if len(trustedMints) != 2 { + t.Fatalf("expected len of trusted mints '%v' but got '%v' instead", 2, len(trustedMints)) + } +} + +func TestWalletRestore(t *testing.T) { + nutshellMint, err := testutils.CreateNutshellMintContainer(ctx) + if err != nil { + t.Fatalf("error starting nutshell mint: %v", err) + } + defer nutshellMint.Terminate(ctx) mintURL := nutshellMint.Host testWalletPath := filepath.Join(".", "/testrestorewallet") @@ -391,6 +477,9 @@ func TestWalletRestore(t *testing.T) { if err != nil { t.Fatal(err) } + defer func() { + os.RemoveAll(testWalletPath) + }() testWalletPath2 := filepath.Join(".", "/testrestorewallet2") testWallet2, err := testutils.CreateTestWallet(testWalletPath2, mintURL)