From 037a854c77f9b5dc6a4b8805fc02676ec613df62 Mon Sep 17 00:00:00 2001 From: elnosh Date: Thu, 8 Aug 2024 13:13:03 -0500 Subject: [PATCH 1/2] mint - reject dup blinded messages --- cashu/cashu.go | 6 ++- mint/mint.go | 41 +++++++++++++++++-- .../000003_blind_signatures.down.sql | 4 ++ .../migrations/000003_blind_signatures.up.sql | 9 ++++ mint/storage/sqlite/sqlite.go | 35 ++++++++++++++++ mint/storage/storage.go | 3 ++ 6 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 mint/storage/sqlite/migrations/000003_blind_signatures.down.sql create mode 100644 mint/storage/sqlite/migrations/000003_blind_signatures.up.sql diff --git a/cashu/cashu.go b/cashu/cashu.go index 2c41fd0..87931ee 100644 --- a/cashu/cashu.go +++ b/cashu/cashu.go @@ -220,8 +220,9 @@ const ( DBErrCode CashuErrCode = 1 LightningBackendErrCode CashuErrCode = 2 - UnitErrCode CashuErrCode = 11005 - PaymentMethodErrCode CashuErrCode = 11006 + UnitErrCode CashuErrCode = 11005 + PaymentMethodErrCode CashuErrCode = 11006 + BlindedMessageAlreadySignedErrCode CashuErrCode = 10002 InvalidProofErrCode CashuErrCode = 10003 ProofAlreadyUsedErrCode CashuErrCode = 11001 @@ -249,6 +250,7 @@ var ( PaymentMethodNotSupportedErr = Error{Detail: "payment method not supported", Code: PaymentMethodErrCode} UnitNotSupportedErr = Error{Detail: "unit not supported", Code: UnitErrCode} InvalidBlindedMessageAmount = Error{Detail: "invalid amount in blinded message", Code: StandardErrCode} + BlindedMessageAlreadySigned = Error{Detail: "blinded message already signed", Code: BlindedMessageAlreadySignedErrCode} MintQuoteRequestNotPaid = Error{Detail: "quote request has not been paid", Code: MintQuoteRequestNotPaidErrCode} MintQuoteAlreadyIssued = Error{Detail: "quote already issued", Code: MintQuoteAlreadyIssuedErrCode} MintingDisabled = Error{Detail: "minting is disabled", Code: MintingDisabledErrCode} diff --git a/mint/mint.go b/mint/mint.go index 5107618..3cf50e9 100644 --- a/mint/mint.go +++ b/mint/mint.go @@ -287,8 +287,13 @@ func (m *Mint) MintTokens(method, id string, blindedMessages cashu.BlindedMessag return nil, cashu.MintQuoteAlreadyIssued } - blindedMessagesAmount := blindedMessages.Amount() - // check overflow + var blindedMessagesAmount uint64 + B_s := make([]string, len(blindedMessages)) + for i, bm := range blindedMessages { + blindedMessagesAmount += bm.Amount + B_s[i] = bm.B_ + } + if len(blindedMessages) > 0 { for _, msg := range blindedMessages { if blindedMessagesAmount < msg.Amount { @@ -303,7 +308,15 @@ func (m *Mint) MintTokens(method, id string, blindedMessages cashu.BlindedMessag return nil, cashu.OutputsOverQuoteAmountErr } - var err error + sigs, err := m.db.GetBlindSignatures(B_s) + if err != nil { + msg := fmt.Sprintf("could not get signatures from db: %v", err) + return nil, cashu.BuildCashuError(msg, cashu.DBErrCode) + } + if len(sigs) > 0 { + return nil, cashu.BlindedMessageAlreadySigned + } + blindedSignatures, err = m.signBlindedMessages(blindedMessages) if err != nil { return nil, err @@ -341,7 +354,13 @@ func (m *Mint) Swap(proofs cashu.Proofs, blindedMessages cashu.BlindedMessages) Ys[i] = Yhex } - blindedMessagesAmount := blindedMessages.Amount() + var blindedMessagesAmount uint64 + B_s := make([]string, len(blindedMessages)) + for i, bm := range blindedMessages { + blindedMessagesAmount += bm.Amount + B_s[i] = bm.B_ + } + // check overflow if len(blindedMessages) > 0 { for _, msg := range blindedMessages { @@ -360,6 +379,15 @@ func (m *Mint) Swap(proofs cashu.Proofs, blindedMessages cashu.BlindedMessages) return nil, err } + sigs, err := m.db.GetBlindSignatures(B_s) + if err != nil { + msg := fmt.Sprintf("could not get signatures from db: %v", err) + return nil, cashu.BuildCashuError(msg, cashu.DBErrCode) + } + if len(sigs) > 0 { + return nil, cashu.BlindedMessageAlreadySigned + } + // if verification complete, sign blinded messages blindedSignatures, err := m.signBlindedMessages(blindedMessages) if err != nil { @@ -600,6 +628,11 @@ func (m *Mint) signBlindedMessages(blindedMessages cashu.BlindedMessages) (cashu C_: C_hex, Id: keyset.Id} blindedSignatures[i] = blindedSignature + + if err := m.db.SaveBlindSignature(msg.B_, C_hex, msg.Id, msg.Amount); err != nil { + msg := fmt.Sprintf("error saving signatures: %v", err) + return nil, cashu.BuildCashuError(msg, cashu.DBErrCode) + } } return blindedSignatures, nil diff --git a/mint/storage/sqlite/migrations/000003_blind_signatures.down.sql b/mint/storage/sqlite/migrations/000003_blind_signatures.down.sql new file mode 100644 index 0000000..094dd5a --- /dev/null +++ b/mint/storage/sqlite/migrations/000003_blind_signatures.down.sql @@ -0,0 +1,4 @@ + +DROP TABLE IF EXISTS blind_signatures; + +DROP INDEX IF EXISTS idx_blind_signatures_b; diff --git a/mint/storage/sqlite/migrations/000003_blind_signatures.up.sql b/mint/storage/sqlite/migrations/000003_blind_signatures.up.sql new file mode 100644 index 0000000..ba52ccf --- /dev/null +++ b/mint/storage/sqlite/migrations/000003_blind_signatures.up.sql @@ -0,0 +1,9 @@ + +CREATE TABLE IF NOT EXISTS blind_signatures ( + b_ TEXT NOT NULL PRIMARY KEY, + c_ TEXT NOT NULL, + keyset_id TEXT NOT NULL, + amount INTEGER NOT NULL +); + +CREATE INDEX IF NOT EXISTS idx_blind_signatures_b ON blind_signatures(b_); diff --git a/mint/storage/sqlite/sqlite.go b/mint/storage/sqlite/sqlite.go index b92e8b3..9512cf3 100644 --- a/mint/storage/sqlite/sqlite.go +++ b/mint/storage/sqlite/sqlite.go @@ -318,3 +318,38 @@ func (sqlite *SQLiteDB) UpdateMeltQuote(quoteId, preimage string, state nut05.St } return nil } + +func (sqlite *SQLiteDB) SaveBlindSignature(B_, C_, keysetId string, amount uint64) error { + _, err := sqlite.db.Exec(` + INSERT INTO blind_signatures (b_, c_, keyset_id, amount) VALUES (?, ?, ?, ?)`, + B_, C_, keysetId, amount, + ) + return err +} + +func (sqlite *SQLiteDB) GetBlindSignatures(B_s []string) (cashu.BlindedSignatures, error) { + signatures := cashu.BlindedSignatures{} + query := `SELECT amount, c_, keyset_id FROM blind_signatures WHERE b_ in (?` + strings.Repeat(",?", len(B_s)-1) + `)` + + args := make([]any, len(B_s)) + for i, B_ := range B_s { + args[i] = B_ + } + + rows, err := sqlite.db.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var blindSignature cashu.BlindedSignature + err := rows.Scan(&blindSignature.Amount, &blindSignature.C_, &blindSignature.Id) + if err != nil { + return nil, err + } + signatures = append(signatures, blindSignature) + } + + return signatures, nil +} diff --git a/mint/storage/storage.go b/mint/storage/storage.go index ccdf86b..aa43c78 100644 --- a/mint/storage/storage.go +++ b/mint/storage/storage.go @@ -27,6 +27,9 @@ type MintDB interface { GetMeltQuote(string) (*MeltQuote, error) UpdateMeltQuote(quoteId string, preimage string, state nut05.State) error + SaveBlindSignature(B_, C_, keysetId string, amount uint64) error + GetBlindSignatures(B_s []string) (cashu.BlindedSignatures, error) + Close() } From 1fdf26739c4cadff95818be4463105e6f172444d Mon Sep 17 00:00:00 2001 From: elnosh Date: Thu, 8 Aug 2024 16:00:07 -0500 Subject: [PATCH 2/2] mint - test blinded message already signed --- mint/mint_integration_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/mint/mint_integration_test.go b/mint/mint_integration_test.go index 18b93b7..9b7d4b6 100644 --- a/mint/mint_integration_test.go +++ b/mint/mint_integration_test.go @@ -238,6 +238,25 @@ func TestMintTokens(t *testing.T) { if !errors.Is(err, cashu.MintQuoteAlreadyIssued) { t.Fatalf("expected error '%v' but got '%v' instead", cashu.MintQuoteAlreadyIssued, err) } + + // test mint with blinded messages already signed + mintQuoteResponse, err = testMint.RequestMintQuote(testutils.BOLT11_METHOD, mintAmount, testutils.SAT_UNIT) + if err != nil { + t.Fatalf("error requesting mint quote: %v", err) + } + + sendPaymentRequest = lnrpc.SendRequest{ + PaymentRequest: mintQuoteResponse.PaymentRequest, + } + response, _ = lnd2.Client.SendPaymentSync(ctx, &sendPaymentRequest) + if len(response.PaymentError) > 0 { + t.Fatalf("error paying invoice: %v", response.PaymentError) + } + + _, err = testMint.MintTokens(testutils.BOLT11_METHOD, mintQuoteResponse.Id, blindedMessages) + if !errors.Is(err, cashu.BlindedMessageAlreadySigned) { + t.Fatalf("expected error '%v' but got '%v' instead", cashu.BlindedMessageAlreadySigned, err) + } } func TestSwap(t *testing.T) { @@ -280,6 +299,17 @@ func TestSwap(t *testing.T) { t.Fatalf("expected error '%v' but got '%v' instead", cashu.ProofAlreadyUsedErr, err) } + proofs, err = testutils.GetValidProofsForAmount(amount, testMint, lnd2) + if err != nil { + t.Fatalf("error generating valid proofs: %v", err) + } + + // test with blinded messages already signed + _, err = testMint.Swap(proofs, newBlindedMessages) + if !errors.Is(err, cashu.BlindedMessageAlreadySigned) { + t.Fatalf("expected error '%v' but got '%v' instead", cashu.BlindedMessageAlreadySigned, err) + } + // mint with fees mintFeesPath := filepath.Join(".", "mintfees") mintFees, err := testutils.CreateTestMint(lnd1, mintFeesPath, dbMigrationPath, 100, mint.MintLimits{})