Skip to content

Commit

Permalink
keysets (#26)
Browse files Browse the repository at this point in the history
* derive key sets

* derive key sets
  • Loading branch information
gohumble authored Oct 14, 2022
1 parent 350bba6 commit ab09ae8
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 89 deletions.
3 changes: 3 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ type SplitResponse struct {
Fst string
Snd string
}
type GetKeySetsResponse struct {
KeySets []string `json:"keysets"`
}
type GetMintResponse struct {
Pr string
Hash string
Expand Down
9 changes: 5 additions & 4 deletions api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ type Configuration struct {
LogLevel string `yaml:"log_level" json:"log_level"`

Mint struct {
PrivateKey string `json:"private_key" yaml:"private_key"`
Host string `json:"host" yaml:"host"`
Port string `json:"port" yaml:"port"`
Tls struct {
PrivateKey string `json:"private_key" yaml:"private_key"`
DerivationPath string `json:"derivation_path" yaml:"derivation_path"`
Host string `json:"host" yaml:"host"`
Port string `json:"port" yaml:"port"`
Tls struct {
Enabled bool `json:"enabled" yaml:"enabled"`
KeyFile string `json:"key_path" yaml:"key_path"`
CertFile string `json:"cert_path" yaml:"cert_path"`
Expand Down
23 changes: 15 additions & 8 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ func New() *Api {
Mint: mint.New(Config.Mint.PrivateKey,
mint.WithClient(lnBitsClient),
mint.WithStorage(sqlStorage),
mint.WithInitialKeySet(Config.Mint.PrivateKey, Config.Mint.DerivationPath),
),
}

m.HttpServer.Handler = newRouter(*m)
m.HttpServer.Handler = newRouter(m)
log.Trace("created mint server")
return m
}
Expand Down Expand Up @@ -75,11 +76,11 @@ func (api Api) StartServer() {
log.Println(api.HttpServer.ListenAndServe())
}
}
func newRouter(a Api) *mux.Router {
func newRouter(a *Api) *mux.Router {
router := mux.NewRouter()
// route to receive mint public keys
router.HandleFunc("/keys", Use(a.getKeys, LoggingMiddleware)).Methods(http.MethodGet)
router.HandleFunc("/keysets", Use(a.getKeysets, LoggingMiddleware)).Methods(http.MethodGet)
router.HandleFunc("/keysets", Use(a.getKeySets, LoggingMiddleware)).Methods(http.MethodGet)
// route to get mint (create tokens)
router.HandleFunc("/mint", Use(a.getMint, LoggingMiddleware)).Methods(http.MethodGet)
// route to real mint (with LIGHTNING enabled)
Expand Down Expand Up @@ -186,15 +187,14 @@ func (api Api) getMint(w http.ResponseWriter, r *http.Request) {
// @Tags POST
func (api Api) mint(w http.ResponseWriter, r *http.Request) {
pr := r.URL.Query().Get("payment_hash")
amounts := make([]int64, 0)
mintRequest := MintRequest{BlindedMessages: make(cashu.BlindedMessages, 0)}
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&mintRequest)
if err != nil {
panic(err)
}

promises, err := api.Mint.Mint(mintRequest.BlindedMessages, amounts, pr)
promises, err := api.Mint.MintWithoutKeySet(mintRequest.BlindedMessages, pr)
if err != nil {
responseError(w, cashu.NewErrorResponse(err))
return
Expand Down Expand Up @@ -265,8 +265,15 @@ func (api Api) getKeys(w http.ResponseWriter, r *http.Request) {
return
}
}
func (api Api) getKeysets(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"keysets":{}}`))

func (api Api) getKeySets(w http.ResponseWriter, r *http.Request) {
response := GetKeySetsResponse{KeySets: api.Mint.GetKeySetIds()}
res, err := json.Marshal(response)
if err != nil {
responseError(w, cashu.NewErrorResponse(err))
return
}
w.Write(res)
}

// check is the http handler function for POST /check
Expand Down Expand Up @@ -322,7 +329,7 @@ func (api Api) split(w http.ResponseWriter, r *http.Request) {
payload.Outputs.BlindedMessages = payload.OutputData.BlindedMessages
}
outputs := payload.Outputs
fstPromise, sendPromise, err := api.Mint.Split(proofs, amount, outputs.BlindedMessages)
fstPromise, sendPromise, err := api.Mint.Split(proofs, amount, outputs.BlindedMessages, api.Mint.LoadKeySet(api.Mint.KeySetId))
if err != nil {
responseError(w, cashu.NewErrorResponse(err))
return
Expand Down
1 change: 1 addition & 0 deletions cashu/cashu.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
)

type Proof struct {
Id string `json:"id"`
Amount int64 `json:"amount"`
Secret string `json:"secret" gorm:"primaryKey"`
C string `json:"C"`
Expand Down
2 changes: 1 addition & 1 deletion crypto/b_dhke.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func ThirdStepAlice(c_ secp256k1.PublicKey, r secp256k1.PrivateKey, A secp256k1.
}

// Verify that secret was signed by bob.
// cuveFunc should be legacyHashToCurve for client version < 0.3.3
// curveFunc should be legacyHashToCurve for client version < 0.3.3
// this could be removed and replaced with static invocation of hashToCurve
func Verify(a secp256k1.PrivateKey, c secp256k1.PublicKey, secretMessage string, curveFunc func(msg []byte) *secp256k1.PublicKey) bool {
var Y, Result secp256k1.JacobianPoint
Expand Down
83 changes: 83 additions & 0 deletions crypto/keyset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package crypto

import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/samber/lo"
"math"
"sort"
"strconv"
"time"
)

const MaxOrder = 64

type KeySet struct {
Id string
DerivationPath string
PublicKeys PublicKeyList `gorm:"-"`
PrivateKeys PrivateKeyList `gorm:"-"`
MintUrl string
ValidFrom time.Time
ValidTo time.Time
FirstSeen time.Time
Active time.Time
}

func NewKeySet(masterKey, derivationPath string) *KeySet {
ks := &KeySet{DerivationPath: derivationPath}
ks.DeriveKeys(masterKey)
ks.DerivePublicKeys()
ks.DeriveKeySetId()
return ks
}

func (k *KeySet) DeriveKeys(masterKey string) {
k.PrivateKeys = deriveKeys(masterKey, k.DerivationPath)
}

func (k *KeySet) DerivePublicKeys() {
k.PublicKeys = derivePublicKeys(k.PrivateKeys)
}

func (k *KeySet) DeriveKeySetId() {
k.Id = deriveKeySetId(k.PublicKeys)
}

// deriveKeys will generate private keys for the mint server
func deriveKeys(masterKey string, derivationPath string) PrivateKeyList {
pk := make(PrivateKeyList, 0)
for i := 0; i < MaxOrder; i++ {
hasher := sha256.New()
hasher.Write([]byte(masterKey + derivationPath + strconv.Itoa(i)))
pk = append(pk, PrivateKey{Amount: int64(math.Pow(2, float64(i))), Key: secp256k1.PrivKeyFromBytes(hasher.Sum(nil)[:32])})
}
sort.Sort(pk)
return pk
}

// derivePublicKeys will generate public keys for the mint server
func derivePublicKeys(pk PrivateKeyList) PublicKeyList {
PublicKeys := make(PublicKeyList, 0)
for _, key := range pk {
PublicKeys = append(PublicKeys, PublicKey{Amount: key.Amount, Key: key.Key.PubKey()})
}
sort.Sort(pk)
return PublicKeys
}

// deriveKeySetId will derive the keySetId from all public key of a keySet
func deriveKeySetId(publicKeys PublicKeyList) string {
var publicKeysConcatenated []byte
// all public keys into concatenated and compressed hex string
lo.ForEach[PublicKey](publicKeys,
func(key PublicKey, _ int) {
publicKeysConcatenated = append(publicKeysConcatenated, []byte(hex.EncodeToString(key.Key.SerializeCompressed()))...)
})
// hash and encode
hasher := sha256.New()
hasher.Write(publicKeysConcatenated)
return base64.StdEncoding.EncodeToString(hasher.Sum(nil))[:12]
}
43 changes: 43 additions & 0 deletions crypto/sort.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package crypto

import "github.com/decred/dcrd/dcrec/secp256k1/v4"

type PublicKey struct {
Amount int64
Key *secp256k1.PublicKey
}

type PublicKeyList []PublicKey

func (s PublicKeyList) ByAmount(amount int64) *PublicKey {
for _, key := range s {
if key.Amount == amount {
return &key
}
}
return nil
}

func (s PrivateKeyList) ByAmount(amount int64) *PrivateKey {
for _, key := range s {
if key.Amount == amount {
return &key
}
}
return nil
}

func (s PublicKeyList) Len() int { return len(s) }
func (s PublicKeyList) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s PublicKeyList) Less(i, j int) bool { return s[i].Amount < s[j].Amount }

type PrivateKey struct {
Amount int64
Key *secp256k1.PrivateKey
}

type PrivateKeyList []PrivateKey

func (p PrivateKeyList) Len() int { return len(p) }
func (p PrivateKeyList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p PrivateKeyList) Less(i, j int) bool { return p[i].Amount < p[j].Amount }
9 changes: 7 additions & 2 deletions db/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package db
import (
"errors"
"github.com/gohumble/cashu-feni/cashu"
"github.com/gohumble/cashu-feni/crypto"
"github.com/gohumble/cashu-feni/lightning"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
Expand Down Expand Up @@ -31,7 +32,7 @@ func createSqliteDatabase() MintStorage {
}

db := SqlDatabase{db: open(sqlite.Open(path.Join(filePath, "database.db")))}
err := db.Migrate(cashu.Proof{}, cashu.Promise{}, cashu.CreateInvoice())
err := db.Migrate(cashu.Proof{}, cashu.Promise{}, crypto.KeySet{}, cashu.CreateInvoice())
if err != nil {
panic(err)
}
Expand All @@ -47,7 +48,7 @@ func open(dialector gorm.Dialector) *gorm.DB {
return orm
}

func (s SqlDatabase) Migrate(proof cashu.Proof, promise cashu.Promise, invoice lightning.Invoice) error {
func (s SqlDatabase) Migrate(proof cashu.Proof, promise cashu.Promise, keySet crypto.KeySet, invoice lightning.Invoice) error {
// do not migrate invoice, if lightning is not enabled
if invoice != nil {
err := s.db.AutoMigrate(invoice)
Expand All @@ -63,6 +64,10 @@ func (s SqlDatabase) Migrate(proof cashu.Proof, promise cashu.Promise, invoice l
if err != nil {
panic(err)
}
err = s.db.AutoMigrate(keySet)
if err != nil {
panic(err)
}
return nil
}

Expand Down
3 changes: 2 additions & 1 deletion db/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package db

import (
"github.com/gohumble/cashu-feni/cashu"
"github.com/gohumble/cashu-feni/crypto"
"github.com/gohumble/cashu-feni/lightning"
)

Expand All @@ -12,5 +13,5 @@ type MintStorage interface {
StoreLightningInvoice(i lightning.Invoice) error
GetLightningInvoice(hash string) (lightning.Invoice, error)
UpdateLightningInvoice(hash string, issued bool) error
Migrate(cashu.Proof, cashu.Promise, lightning.Invoice) error
Migrate(cashu.Proof, cashu.Promise, crypto.KeySet, lightning.Invoice) error
}
Loading

0 comments on commit ab09ae8

Please sign in to comment.