Skip to content

Commit

Permalink
mint - sqlite storage
Browse files Browse the repository at this point in the history
  • Loading branch information
elnosh committed Jul 29, 2024
1 parent 67b03a6 commit 80f17d5
Show file tree
Hide file tree
Showing 24 changed files with 909 additions and 599 deletions.
6 changes: 2 additions & 4 deletions .env.mint.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# mint
MINT_PRIVATE_KEY="mykey"
# use only if setting up mint with one unit (defaults to sat)
MINT_DERIVATION_PATH="0/0/0"
# Bump this number to generate new active keyset and deactivate previous one
DERIVATION_PATH_IDX=0
# fee to charge per input (in parts per thousand)
INPUT_FEE_PPK=100

Expand Down
29 changes: 29 additions & 0 deletions cashu/cashu.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
package cashu

import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
Expand Down Expand Up @@ -218,6 +220,7 @@ const (
QuoteErrCode
InvoiceErrCode
ProofsErrCode
DBErrorCode
)

var (
Expand All @@ -236,6 +239,8 @@ var (
InvoiceTokensIssuedErr = Error{Detail: "tokens already issued for invoice", Code: InvoiceErrCode}
ProofAlreadyUsedErr = Error{Detail: "proofs already used", Code: ProofsErrCode}
InvalidProofErr = Error{Detail: "invalid proof", Code: ProofsErrCode}
NoProofsProvided = Error{Detail: "no proofs provided", Code: ProofsErrCode}
DuplicateProofs = Error{Detail: "duplicate proofs", Code: ProofsErrCode}
InputsBelowOutputs = Error{Detail: "amount of input proofs is below amount of outputs", Code: ProofsErrCode}
EmptyInputsErr = Error{Detail: "inputs cannot be empty", Code: ProofsErrCode}
QuoteNotExistErr = Error{Detail: "quote does not exist", Code: QuoteErrCode}
Expand All @@ -259,6 +264,30 @@ func AmountSplit(amount uint64) []uint64 {
return rv
}

func CheckDuplicateProofs(proofs Proofs) bool {
proofsMap := make(map[Proof]bool)

for _, proof := range proofs {
if proofsMap[proof] {
return true
} else {
proofsMap[proof] = true
}
}

return false
}

func GenerateRandomQuoteId() (string, error) {
randomBytes := make([]byte, 32)
_, err := rand.Read(randomBytes)
if err != nil {
return "", err
}
hash := sha256.Sum256(randomBytes)
return hex.EncodeToString(hash[:]), nil
}

func Max(x, y uint64) uint64 {
if x > y {
return x
Expand Down
4 changes: 2 additions & 2 deletions cashu/nuts/nut04/nut04.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ type PostMintQuoteBolt11Response struct {
Request string `json:"request"`
State State `json:"state"`
Paid bool `json:"paid"` // DEPRECATED: use State instead
Expiry int64 `json:"expiry"`
Expiry uint64 `json:"expiry"`
}

type PostMintBolt11Request struct {
Expand All @@ -70,7 +70,7 @@ type TempQuote struct {
Request string `json:"request"`
State string `json:"state"`
Paid bool `json:"paid"` // DEPRECATED: use State instead
Expiry int64 `json:"expiry"`
Expiry uint64 `json:"expiry"`
}

func (quoteResponse *PostMintQuoteBolt11Response) MarshalJSON() ([]byte, error) {
Expand Down
4 changes: 2 additions & 2 deletions cashu/nuts/nut05/nut05.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type PostMeltQuoteBolt11Response struct {
FeeReserve uint64 `json:"fee_reserve"`
State State `json:"state"`
Paid bool `json:"paid"` // DEPRECATED: use state instead
Expiry int64 `json:"expiry"`
Expiry uint64 `json:"expiry"`
Preimage string `json:"payment_preimage,omitempty"`
}

Expand All @@ -69,7 +69,7 @@ type TempQuote struct {
FeeReserve uint64 `json:"fee_reserve"`
State string `json:"state"`
Paid bool `json:"paid"` // DEPRECATED: use state instead
Expiry int64 `json:"expiry"`
Expiry uint64 `json:"expiry"`
Preimage string `json:"payment_preimage,omitempty"`
}

Expand Down
39 changes: 1 addition & 38 deletions crypto/bdhke.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,7 @@ func Verify(secret string, k *secp256k1.PrivateKey, C *secp256k1.PublicKey) bool
if err != nil {
return false
}
valid := verify(Y, k, C)
if !valid {
Y := HashToCurveDeprecated([]byte(secret))
valid = verify(Y, k, C)
}

return valid
return verify(Y, k, C)
}

func verify(Y *secp256k1.PublicKey, k *secp256k1.PrivateKey, C *secp256k1.PublicKey) bool {
Expand All @@ -130,34 +124,3 @@ func verify(Y *secp256k1.PublicKey, k *secp256k1.PrivateKey, C *secp256k1.Public

return C.IsEqual(pk)
}

// Deprecated HashToCurve

func HashToCurveDeprecated(message []byte) *secp256k1.PublicKey {
var point *secp256k1.PublicKey

for point == nil || !point.IsOnCurve() {
hash := sha256.Sum256(message)
pkhash := append([]byte{0x02}, hash[:]...)
point, _ = secp256k1.ParsePubKey(pkhash)
message = hash[:]
}
return point
}

func BlindMessageDeprecated(secret string, r *secp256k1.PrivateKey) (*secp256k1.PublicKey, *secp256k1.PrivateKey) {
var ypoint, rpoint, blindedMessage secp256k1.JacobianPoint

Y := HashToCurveDeprecated([]byte(secret))
Y.AsJacobian(&ypoint)

rpub := r.PubKey()
rpub.AsJacobian(&rpoint)

// blindedMessage = Y + rG
secp256k1.AddNonConst(&ypoint, &rpoint, &blindedMessage)
blindedMessage.ToAffine()
B_ := secp256k1.NewPublicKey(&blindedMessage.X, &blindedMessage.Y)

return B_, r
}
59 changes: 0 additions & 59 deletions crypto/bdhke_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,62 +166,3 @@ func TestVerify(t *testing.T) {
t.Error("failed verification")
}
}

// Tests for deprecated HashToCurve
func TestHashToCurveDeprecated(t *testing.T) {
tests := []struct {
message string
expected string
}{
{message: "0000000000000000000000000000000000000000000000000000000000000000",
expected: "0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925"},
{message: "0000000000000000000000000000000000000000000000000000000000000001",
expected: "02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5"},
{message: "0000000000000000000000000000000000000000000000000000000000000002",
expected: "02076c988b353fcbb748178ecb286bc9d0b4acf474d4ba31ba62334e46c97c416a"},
}

for _, test := range tests {
msgBytes, err := hex.DecodeString(test.message)
if err != nil {
t.Errorf("error decoding msg: %v", err)
}

pk := HashToCurveDeprecated(msgBytes)
hexStr := hex.EncodeToString(pk.SerializeCompressed())
if hexStr != test.expected {
t.Errorf("expected '%v' but got '%v' instead\n", test.expected, hexStr)
}
}
}

func TestBlindMessageDeprecated(t *testing.T) {
tests := []struct {
secret string
blindingFactor string
expected string
}{
{secret: "test_message",
blindingFactor: "0000000000000000000000000000000000000000000000000000000000000001",
expected: "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
},
{secret: "hello",
blindingFactor: "6d7e0abffc83267de28ed8ecc8760f17697e51252e13333ba69b4ddad1f95d05",
expected: "0249eb5dbb4fac2750991cf18083388c6ef76cde9537a6ac6f3e6679d35cdf4b0c",
},
}

for _, test := range tests {
rbytes, err := hex.DecodeString(test.blindingFactor)
if err != nil {
t.Errorf("error decoding blinding factor: %v", err)
}
r := secp256k1.PrivKeyFromBytes(rbytes)

B_, _ := BlindMessageDeprecated(test.secret, r)
B_Hex := hex.EncodeToString(B_.SerializeCompressed())
if B_Hex != test.expected {
t.Errorf("expected '%v' but got '%v' instead\n", test.expected, B_Hex)
}
}
}
82 changes: 61 additions & 21 deletions crypto/keyset.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,88 @@ import (
"encoding/json"
"math"
"sort"
"strconv"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/elnosh/gonuts/cashu/nuts/nut01"
)

const MAX_ORDER = 60

type Keyset struct {
Id string
Unit string
Active bool
Keys map[uint64]KeyPair
InputFeePpk uint
type MintKeyset struct {
Id string
Unit string
Active bool
DerivationPathIdx uint32
Keys map[uint64]KeyPair
InputFeePpk uint
}

type KeyPair struct {
PrivateKey *secp256k1.PrivateKey
PublicKey *secp256k1.PublicKey
}

func GenerateKeyset(seed, derivationPath string, inputFeePpk uint) *Keyset {
func DeriveKeysetPath(key *hdkeychain.ExtendedKey, index uint32) (*hdkeychain.ExtendedKey, error) {
// path m/0'
child, err := key.Derive(hdkeychain.HardenedKeyStart + 0)
if err != nil {
return nil, err
}

// path m/0'/0' for sat
unitPath, err := child.Derive(hdkeychain.HardenedKeyStart + 0)
if err != nil {
return nil, err
}

// path m/0'/0'/index'
keysetPath, err := unitPath.Derive(hdkeychain.HardenedKeyStart + index)
if err != nil {
return nil, err
}

return keysetPath, nil
}

func GenerateKeyset(master *hdkeychain.ExtendedKey, index uint32, inputFeePpk uint) (*MintKeyset, error) {
keys := make(map[uint64]KeyPair, MAX_ORDER)

keysetPath, err := DeriveKeysetPath(master, index)
if err != nil {
return nil, err
}

pks := make(map[uint64]*secp256k1.PublicKey)
for i := 0; i < MAX_ORDER; i++ {
amount := uint64(math.Pow(2, float64(i)))
hash := sha256.Sum256([]byte(seed + derivationPath + strconv.FormatUint(amount, 10)))
privKey, pubKey := btcec.PrivKeyFromBytes(hash[:])
amountPath, err := keysetPath.Derive(hdkeychain.HardenedKeyStart + uint32(i))
if err != nil {
return nil, err
}

privKey, err := amountPath.ECPrivKey()
if err != nil {
return nil, err
}
pubKey, err := amountPath.ECPubKey()
if err != nil {
return nil, err
}

keys[amount] = KeyPair{PrivateKey: privKey, PublicKey: pubKey}
pks[amount] = pubKey
}
keysetId := DeriveKeysetId(pks)

return &Keyset{
Id: keysetId,
Unit: "sat",
Active: true,
Keys: keys,
InputFeePpk: inputFeePpk,
}
return &MintKeyset{
Id: keysetId,
Unit: "sat",
Active: true,
DerivationPathIdx: index,
Keys: keys,
InputFeePpk: inputFeePpk,
}, nil
}

// DeriveKeysetId returns the string ID derived from the map keyset
Expand Down Expand Up @@ -84,7 +124,7 @@ func DeriveKeysetId(keyset map[uint64]*secp256k1.PublicKey) string {

// DerivePublic returns the keyset's public keys as
// a map of amounts uint64 to strings that represents the public key
func (ks *Keyset) DerivePublic() map[uint64]string {
func (ks *MintKeyset) DerivePublic() map[uint64]string {
pubkeys := make(map[uint64]string)
for amount, key := range ks.Keys {
pubkey := hex.EncodeToString(key.PublicKey.SerializeCompressed())
Expand All @@ -101,7 +141,7 @@ type KeysetTemp struct {
InputFeePpk uint
}

func (ks *Keyset) MarshalJSON() ([]byte, error) {
func (ks *MintKeyset) MarshalJSON() ([]byte, error) {
temp := &KeysetTemp{
Id: ks.Id,
Unit: ks.Unit,
Expand All @@ -120,7 +160,7 @@ func (ks *Keyset) MarshalJSON() ([]byte, error) {
return json.Marshal(temp)
}

func (ks *Keyset) UnmarshalJSON(data []byte) error {
func (ks *MintKeyset) UnmarshalJSON(data []byte) error {
temp := &KeysetTemp{}

if err := json.Unmarshal(data, &temp); err != nil {
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/gorilla/mux v1.8.0
github.com/joho/godotenv v1.5.1
github.com/lightningnetwork/lnd v0.17.4-beta
github.com/mattn/go-sqlite3 v1.14.22
github.com/nbd-wtf/ln-decodepay v1.12.1
github.com/testcontainers/testcontainers-go v0.31.0
github.com/tyler-smith/go-bip39 v1.1.0
Expand Down Expand Up @@ -67,7 +68,7 @@ require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/golang-migrate/migrate/v4 v4.17.0 // indirect
github.com/golang-migrate/migrate/v4 v4.17.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.0.1 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ github.com/decred/dcrd/lru v1.1.2 h1:KdCzlkxppuoIDGEvCGah1fZRicrDH36IipvlB1ROkFY
github.com/decred/dcrd/lru v1.1.2/go.mod h1:gEdCVgXs1/YoBvFWt7Scgknbhwik3FgVSzlnCcXL2N8=
github.com/dhui/dktest v0.4.0 h1:z05UmuXZHO/bgj/ds2bGMBu8FI4WA+Ag/m3ghL+om7M=
github.com/dhui/dktest v0.4.0/go.mod h1:v/Dbz1LgCBOi2Uki2nUqLBGa83hWBGFMu5MrgMDCc78=
github.com/dhui/dktest v0.4.1 h1:/w+IWuDXVymg3IrRJCHHOkMK10m9aNVMOyD0X12YVTg=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v23.0.3+incompatible h1:Zcse1DuDqBdgI7OQDV8Go7b83xLgfhW1eza4HfEdxpY=
Expand Down Expand Up @@ -239,6 +240,8 @@ github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQA
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU=
github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM=
github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4=
github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
Expand Down
Loading

0 comments on commit 80f17d5

Please sign in to comment.