Skip to content

Commit

Permalink
Merge pull request #57 from elnosh/mint-nut09
Browse files Browse the repository at this point in the history
mint: NUT-09 signature restore
  • Loading branch information
elnosh authored Sep 13, 2024
2 parents 6393139 + 1c943c3 commit 7d81726
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 21 deletions.
30 changes: 25 additions & 5 deletions mint/mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ func (m *Mint) GetMintQuoteState(method, quoteId string) (storage.MintQuote, err
}
}

return *mintQuote, nil
return mintQuote, nil
}

// MintTokens verifies whether the mint quote with id has been paid and proceeds to
Expand Down Expand Up @@ -484,7 +484,7 @@ func (m *Mint) GetMeltQuoteState(method, quoteId string) (storage.MeltQuote, err
return storage.MeltQuote{}, cashu.QuoteNotExistErr
}

return *meltQuote, nil
return meltQuote, nil
}

// MeltTokens verifies whether proofs provided are valid
Expand Down Expand Up @@ -553,7 +553,7 @@ func (m *Mint) MeltTokens(method, quoteId string, proofs cashu.Proofs) (storage.
return storage.MeltQuote{}, cashu.BuildCashuError(msg, cashu.DBErrCode)
}

return *meltQuote, nil
return meltQuote, nil
}

func (m *Mint) ProofsStateCheck(Ys []string) ([]nut07.ProofState, error) {
Expand Down Expand Up @@ -582,6 +582,26 @@ func (m *Mint) ProofsStateCheck(Ys []string) ([]nut07.ProofState, error) {
return proofStates, nil
}

func (m *Mint) RestoreSignatures(blindedMessages cashu.BlindedMessages) (cashu.BlindedMessages, cashu.BlindedSignatures, error) {
outputs := make(cashu.BlindedMessages, 0, len(blindedMessages))
signatures := make(cashu.BlindedSignatures, 0, len(blindedMessages))

for _, bm := range blindedMessages {
sig, err := m.db.GetBlindSignature(bm.B_)
if errors.Is(err, sql.ErrNoRows) {
continue
} else if err != nil {
msg := fmt.Sprintf("could not get signature from db: %v", err)
return nil, nil, cashu.BuildCashuError(msg, cashu.DBErrCode)
}

outputs = append(outputs, bm)
signatures = append(signatures, sig)
}

return outputs, signatures, nil
}

func (m *Mint) verifyProofs(proofs cashu.Proofs, Ys []string) error {
if len(proofs) == 0 {
return cashu.NoProofsProvided
Expand Down Expand Up @@ -878,9 +898,9 @@ func (m *Mint) SetMintInfo(mintInfo MintInfo) error {
},
Disabled: false,
},
7: map[string]bool{"supported": false},
7: map[string]bool{"supported": true},
8: map[string]bool{"supported": false},
9: map[string]bool{"supported": false},
9: map[string]bool{"supported": true},
10: map[string]bool{"supported": true},
11: map[string]bool{"supported": true},
12: map[string]bool{"supported": false},
Expand Down
54 changes: 53 additions & 1 deletion mint/mint_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"log"
"os"
"path/filepath"
"reflect"
"testing"
"time"

Expand Down Expand Up @@ -621,6 +622,58 @@ func TestProofsStateCheck(t *testing.T) {
}
}

func TestRestoreSignatures(t *testing.T) {
// create blinded messages
blindedMessages, _, _, blindedSignatures, err := testutils.GetBlindedSignatures(5000, testMint, lnd2)
if err != nil {
t.Fatalf("error generating blinded signatures: %v", err)
}

outputs, signatures, err := testMint.RestoreSignatures(blindedMessages)
if err != nil {
t.Fatalf("unexpected error restoring signatures: %v\n", err)
}

if len(outputs) != len(signatures) {
t.Fatalf("length of ouputs '%v' does not match length of signatures '%v'\n", len(outputs), len(signatures))
}
if !reflect.DeepEqual(blindedMessages, outputs) {
t.Fatal("outputs in request do not match outputs from mint response")
}
if !reflect.DeepEqual(blindedSignatures, signatures) {
t.Fatal("blinded signatures do not match signatures from mint response")
}

// test with blinded messages that have not been previously signed
unsigned, _, _, _ := testutils.CreateBlindedMessages(4200, testMint.GetActiveKeyset())
outputs, signatures, err = testMint.RestoreSignatures(unsigned)
if err != nil {
t.Fatalf("unexpected error restoring signatures: %v\n", err)
}

// response should be empty
if len(outputs) != 0 && len(signatures) != 0 {
t.Fatalf("expected empty outputs and signatures but got %v and %v\n", len(outputs), len(signatures))
}

// test with only a portion of blinded messages signed
partial := append(blindedMessages, unsigned...)
outputs, signatures, err = testMint.RestoreSignatures(partial)
if err != nil {
t.Fatalf("unexpected error restoring signatures: %v\n", err)
}

if len(outputs) != len(signatures) {
t.Fatalf("length of ouputs '%v' does not match length of signatures '%v'\n", len(outputs), len(signatures))
}
if !reflect.DeepEqual(blindedMessages, outputs) {
t.Fatal("outputs in request do not match outputs from mint response")
}
if !reflect.DeepEqual(blindedSignatures, signatures) {
t.Fatal("blinded signatures do not match signatures from mint response")
}
}

func TestMintLimits(t *testing.T) {
// setup mint with limits
limitsMintPath := filepath.Join(".", "limitsMint")
Expand Down Expand Up @@ -905,5 +958,4 @@ func TestNUT11P2PK(t *testing.T) {
if !errors.Is(err, nut11.SigAllOnlySwap) {
t.Fatalf("expected error '%v' but got '%v' instead", nut11.SigAllOnlySwap, err)
}

}
32 changes: 31 additions & 1 deletion mint/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/elnosh/gonuts/cashu/nuts/nut04"
"github.com/elnosh/gonuts/cashu/nuts/nut05"
"github.com/elnosh/gonuts/cashu/nuts/nut07"
"github.com/elnosh/gonuts/cashu/nuts/nut09"
"github.com/elnosh/gonuts/crypto"
"github.com/gorilla/mux"
)
Expand Down Expand Up @@ -100,6 +101,7 @@ func (ms *MintServer) setupHttpServer(port string) error {
r.HandleFunc("/v1/melt/quote/{method}/{quote_id}", ms.meltQuoteState).Methods(http.MethodGet, http.MethodOptions)
r.HandleFunc("/v1/melt/{method}", ms.meltTokens).Methods(http.MethodPost, http.MethodOptions)
r.HandleFunc("/v1/checkstate", ms.tokenStateCheck).Methods(http.MethodPost, http.MethodOptions)
r.HandleFunc("/v1/restore", ms.restoreSignatures).Methods(http.MethodPost, http.MethodOptions)
r.HandleFunc("/v1/info", ms.mintInfo).Methods(http.MethodGet, http.MethodOptions)

r.Use(setupHeaders)
Expand Down Expand Up @@ -501,10 +503,38 @@ func (ms *MintServer) tokenStateCheck(rw http.ResponseWriter, req *http.Request)
ms.writeResponse(rw, req, jsonRes, "returned proof states")
}

func (ms *MintServer) restoreSignatures(rw http.ResponseWriter, req *http.Request) {
var restoreRequest nut09.PostRestoreRequest
err := decodeJsonReqBody(req, &restoreRequest)
if err != nil {
ms.writeErr(rw, req, err)
return
}

blindedMessages, blindedSignatures, err := ms.mint.RestoreSignatures(restoreRequest.Outputs)
if err != nil {
ms.writeErr(rw, req, cashu.StandardErr, err.Error())
return
}

restoreResponse := nut09.PostRestoreResponse{
Outputs: blindedMessages,
Signatures: blindedSignatures,
}

jsonRes, err := json.Marshal(restoreResponse)
if err != nil {
ms.writeErr(rw, req, cashu.StandardErr)
return
}

ms.writeResponse(rw, req, jsonRes, "returned signatures from restore request")
}

func (ms *MintServer) mintInfo(rw http.ResponseWriter, req *http.Request) {
mintInfo, err := ms.mint.RetrieveMintInfo()
if err != nil {
ms.writeErr(rw, req, cashu.StandardErr)
ms.writeErr(rw, req, cashu.StandardErr, err.Error())
return
}
jsonRes, err := json.Marshal(mintInfo)
Expand Down
28 changes: 22 additions & 6 deletions mint/storage/sqlite/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func (sqlite *SQLiteDB) SaveMintQuote(mintQuote storage.MintQuote) error {
return err
}

func (sqlite *SQLiteDB) GetMintQuote(quoteId string) (*storage.MintQuote, error) {
func (sqlite *SQLiteDB) GetMintQuote(quoteId string) (storage.MintQuote, error) {
row := sqlite.db.QueryRow("SELECT * FROM mint_quotes WHERE id = ?", quoteId)

var mintQuote storage.MintQuote
Expand All @@ -234,11 +234,11 @@ func (sqlite *SQLiteDB) GetMintQuote(quoteId string) (*storage.MintQuote, error)
&mintQuote.Expiry,
)
if err != nil {
return nil, err
return storage.MintQuote{}, err
}
mintQuote.State = nut04.StringToState(state)

return &mintQuote, nil
return mintQuote, nil
}

func (sqlite *SQLiteDB) UpdateMintQuoteState(quoteId string, state nut04.State) error {
Expand Down Expand Up @@ -276,7 +276,7 @@ func (sqlite *SQLiteDB) SaveMeltQuote(meltQuote storage.MeltQuote) error {
return err
}

func (sqlite *SQLiteDB) GetMeltQuote(quoteId string) (*storage.MeltQuote, error) {
func (sqlite *SQLiteDB) GetMeltQuote(quoteId string) (storage.MeltQuote, error) {
row := sqlite.db.QueryRow("SELECT * FROM melt_quotes WHERE id = ?", quoteId)

var meltQuote storage.MeltQuote
Expand All @@ -293,11 +293,11 @@ func (sqlite *SQLiteDB) GetMeltQuote(quoteId string) (*storage.MeltQuote, error)
&meltQuote.Preimage,
)
if err != nil {
return nil, err
return storage.MeltQuote{}, err
}
meltQuote.State = nut05.StringToState(state)

return &meltQuote, nil
return meltQuote, nil
}

func (sqlite *SQLiteDB) UpdateMeltQuote(quoteId, preimage string, state nut05.State) error {
Expand Down Expand Up @@ -328,6 +328,22 @@ func (sqlite *SQLiteDB) SaveBlindSignature(B_, C_, keysetId string, amount uint6
return err
}

func (sqlite *SQLiteDB) GetBlindSignature(B_ string) (cashu.BlindedSignature, error) {
row := sqlite.db.QueryRow("SELECT amount, c_, keyset_id FROM blind_signatures WHERE b_ = ?", B_)

var signature cashu.BlindedSignature
err := row.Scan(
&signature.Amount,
&signature.C_,
&signature.Id,
)
if err != nil {
return cashu.BlindedSignature{}, err
}

return signature, nil
}

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) + `)`
Expand Down
5 changes: 3 additions & 2 deletions mint/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ type MintDB interface {
GetProofsUsed(Ys []string) ([]DBProof, error)

SaveMintQuote(MintQuote) error
GetMintQuote(string) (*MintQuote, error)
GetMintQuote(string) (MintQuote, error)
UpdateMintQuoteState(quoteId string, state nut04.State) error

SaveMeltQuote(MeltQuote) error
GetMeltQuote(string) (*MeltQuote, error)
GetMeltQuote(string) (MeltQuote, error)
UpdateMeltQuote(quoteId string, preimage string, state nut05.State) error

SaveBlindSignature(B_, C_, keysetId string, amount uint64) error
GetBlindSignature(B_ string) (cashu.BlindedSignature, error)
GetBlindSignatures(B_s []string) (cashu.BlindedSignatures, error)

Close()
Expand Down
27 changes: 21 additions & 6 deletions testutils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,17 +411,22 @@ func ConstructProofs(blindedSignatures cashu.BlindedSignatures,
return proofs, nil
}

func GetValidProofsForAmount(amount uint64, mint *mint.Mint, payer *btcdocker.Lnd) (cashu.Proofs, error) {
func GetBlindedSignatures(amount uint64, mint *mint.Mint, payer *btcdocker.Lnd) (
cashu.BlindedMessages,
[]string,
[]*secp256k1.PrivateKey,
cashu.BlindedSignatures,
error) {

mintQuoteResponse, err := mint.RequestMintQuote(BOLT11_METHOD, amount, SAT_UNIT)
if err != nil {
return nil, fmt.Errorf("error requesting mint quote: %v", err)
return nil, nil, nil, nil, fmt.Errorf("error requesting mint quote: %v", err)
}

keyset := mint.GetActiveKeyset()

blindedMessages, secrets, rs, err := CreateBlindedMessages(amount, keyset)
if err != nil {
return nil, fmt.Errorf("error creating blinded message: %v", err)
return nil, nil, nil, nil, fmt.Errorf("error creating blinded message: %v", err)
}

ctx := context.Background()
Expand All @@ -431,12 +436,22 @@ func GetValidProofsForAmount(amount uint64, mint *mint.Mint, payer *btcdocker.Ln
}
response, _ := payer.Client.SendPaymentSync(ctx, &sendPaymentRequest)
if len(response.PaymentError) > 0 {
return nil, fmt.Errorf("error paying invoice: %v", response.PaymentError)
return nil, nil, nil, nil, fmt.Errorf("error paying invoice: %v", response.PaymentError)
}

blindedSignatures, err := mint.MintTokens(BOLT11_METHOD, mintQuoteResponse.Id, blindedMessages)
if err != nil {
return nil, fmt.Errorf("got unexpected error minting tokens: %v", err)
return nil, nil, nil, nil, fmt.Errorf("got unexpected error minting tokens: %v", err)
}

return blindedMessages, secrets, rs, blindedSignatures, nil
}

func GetValidProofsForAmount(amount uint64, mint *mint.Mint, payer *btcdocker.Lnd) (cashu.Proofs, error) {
keyset := mint.GetActiveKeyset()
_, secrets, rs, blindedSignatures, err := GetBlindedSignatures(amount, mint, payer)
if err != nil {
return nil, fmt.Errorf("error generating blinded signatures: %v", err)
}

proofs, err := ConstructProofs(blindedSignatures, secrets, rs, &keyset)
Expand Down

0 comments on commit 7d81726

Please sign in to comment.