Skip to content
Merged
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
67 changes: 62 additions & 5 deletions cashu/nuts/nut13/nut13.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,50 @@
package nut13

import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"math/big"

"encoding/base64"
"regexp"

"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)

var (
ErrCollidingKeysetId = errors.New("error: colliding keyset detected")
)

func keysetIdToBigInt(id string) (*big.Int, error) {
hexPattern := regexp.MustCompile("^[0-9a-fA-F]+$")

var result *big.Int
modulus := big.NewInt(2147483647) // 2^31 - 1

if hexPattern.MatchString(id) {
result = new(big.Int)
result.SetString(id, 16)
} else {
decoded, err := base64.StdEncoding.DecodeString(id)
if err != nil {
return nil, err
}

hexStr := hex.EncodeToString(decoded)
result = new(big.Int)
result.SetString(hexStr, 16)
}

return result.Mod(result, modulus), nil
}

func DeriveKeysetPath(master *hdkeychain.ExtendedKey, keysetId string) (*hdkeychain.ExtendedKey, error) {
keysetBytes, err := hex.DecodeString(keysetId)
keysetIdInt, err := keysetIdToBigInt(keysetId)
if err != nil {
return nil, err
}
bigEndianBytes := binary.BigEndian.Uint64(keysetBytes)
keysetIdInt := bigEndianBytes % (1<<31 - 1)

// m/129372
purpose, err := master.Derive(hdkeychain.HardenedKeyStart + 129372)
Expand All @@ -29,7 +59,7 @@ func DeriveKeysetPath(master *hdkeychain.ExtendedKey, keysetId string) (*hdkeych
}

// m/129372'/0'/keyset_k_int'
keysetPath, err := coinType.Derive(hdkeychain.HardenedKeyStart + uint32(keysetIdInt))
keysetPath, err := coinType.Derive(hdkeychain.HardenedKeyStart + uint32(keysetIdInt.Uint64()))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -81,3 +111,30 @@ func DeriveSecret(keysetPath *hdkeychain.ExtendedKey, counter uint32) (string, e

return secret, nil
}

func CheckCollidingKeysets(currentKeysetIds []string, newMintKeysetIds []string) error {

for i := range currentKeysetIds {
keysetIdInt, err := keysetIdToBigInt(currentKeysetIds[i])
if err != nil {
return err
}

for j := range newMintKeysetIds {
if currentKeysetIds[i] == newMintKeysetIds[j] {
return fmt.Errorf("%w. KeysetId: %+v", ErrCollidingKeysetId, currentKeysetIds[i])
}

keysetIdIntToCompare, err := keysetIdToBigInt(newMintKeysetIds[j])
if err != nil {
return err
}

if keysetIdInt == keysetIdIntToCompare {
return fmt.Errorf("%w. KeysetId: %+v", ErrCollidingKeysetId, currentKeysetIds[i])
}
}
}

return nil
}
24 changes: 24 additions & 0 deletions cashu/nuts/nut13/nut13_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package nut13

import (
"encoding/hex"
"errors"
"testing"

"github.com/btcsuite/btcd/btcutil/hdkeychain"
Expand Down Expand Up @@ -72,3 +73,26 @@ func TestSecretDerivation(t *testing.T) {
}

}

func TestCollisionOfIdNoCollision(t *testing.T) {
keysetId := []string{"009a1f293253e41e"}

keysets := []string{"009a1f293253d41e", "009a1f283253e41e"}

err := CheckCollidingKeysets(keysetId, keysets)

if err != nil {
t.Errorf("There should not have been any keyset collision")
}
}
func TestCollisionOfIdWithCollision(t *testing.T) {
keysetId := []string{"009a1f293253e41e", "009a1f293253e41d"}

oldKeysets := []string{"009b1f293253e41d", "009a1f293253e41e"}

err := CheckCollidingKeysets(keysetId, oldKeysets)

if !errors.Is(err, ErrCollidingKeysetId) {
t.Errorf("there should have been a keyset collition error")
}
}
11 changes: 11 additions & 0 deletions crypto/keyset.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,17 @@ func (kp *KeyPair) UnmarshalJSON(data []byte) error {
// KeysetsMap maps a mint url to map of string keyset id to keyset
type KeysetsMap map[string][]WalletKeyset

func (k KeysetsMap) GetAllKeysetIds() []string {
keysetList := []string{}
for _, mint := range k {
for _, walletKeyset := range mint {
keysetList = append(keysetList, walletKeyset.Id)
}
}

return keysetList
}

type WalletKeyset struct {
Id string
MintURL string
Expand Down
2 changes: 1 addition & 1 deletion testutils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -1138,7 +1138,7 @@ func CreateNutshellMintContainer(ctx context.Context, inputFeePpk int, lnd *lnd.

req := testcontainers.ContainerRequest{
//Image: "cashubtc/nutshell:0.16.5",
Image: "cashubtc/nutshell:latest",
Image: "cashubtc/nutshell:0.16.5",
ExposedPorts: []string{"3338"},
Cmd: []string{
"poetry",
Expand Down
21 changes: 21 additions & 0 deletions wallet/keyset.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"

"github.com/elnosh/gonuts/cashu"
"github.com/elnosh/gonuts/cashu/nuts/nut13"
"github.com/elnosh/gonuts/crypto"
"github.com/elnosh/gonuts/wallet/client"
)
Expand Down Expand Up @@ -77,6 +78,17 @@ func GetKeysetKeys(mintURL, id string) (crypto.PublicKeys, error) {
return keysetsResponse.Keysets[0].Keys, nil
}

func getListOfIdsFromMap(walletMap map[string]crypto.WalletKeyset) []string {

keysetIdList := []string{}
for _, keyset := range walletMap {

keysetIdList = append(keysetIdList, keyset.Id)

}
return keysetIdList
}

// getActiveKeyset returns the active keyset for the mint passed.
// if mint passed is known and the latest active keyset has changed,
// it will inactivate the previous active and save new active to db
Expand Down Expand Up @@ -124,6 +136,15 @@ func (w *Wallet) getActiveKeyset(mintURL string) (*crypto.WalletKeyset, error) {
if storedKeyset != nil {
storedKeyset.Active = true
storedKeyset.InputFeePpk = keyset.InputFeePpk

// Verify before collision
keysetsMap := w.db.GetKeysets()
listOfCurrentKeysets := keysetsMap.GetAllKeysetIds()
err = nut13.CheckCollidingKeysets(listOfCurrentKeysets, []string{activeKeyset.Id})
if err != nil {
return nil, err
}

if err := w.db.SaveKeyset(storedKeyset); err != nil {
return nil, err
}
Expand Down
11 changes: 11 additions & 0 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,17 @@ func (w *Wallet) AddMint(mint string) (*walletMint, error) {
return nil, err
}

newMintKeysetIdList := []string{activeKeyset.Id}
newMintKeysetIdList = append(newMintKeysetIdList, getListOfIdsFromMap(inactiveKeysets)...)

keysetsMap := w.db.GetKeysets()
listOfCurrentKeysets := keysetsMap.GetAllKeysetIds()

err = nut13.CheckCollidingKeysets(listOfCurrentKeysets, newMintKeysetIdList)
if err != nil {
return nil, err
}

if err := w.db.SaveKeyset(activeKeyset); err != nil {
return nil, err
}
Expand Down