mirror of
https://github.com/luxfi/vm.git
synced 2026-10-30 15:13:40 +00:00
Wire format: u16 version | u32 count | (20B short_id)* Forward-only on-disk schema. Encoding is byte-equal to the prior linearcodec output for []ids.ShortID (both linearcodec and this hand-roll emit u16 version + length-prefixed fixed-size array). Drops Codec + LegacyCodec singletons; user.go now calls marshalAddresses / unmarshalAddresses directly. keystore package no longer depends on github.com/luxfi/codec. Tests green.
189 lines
4.6 KiB
Go
189 lines
4.6 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package keystore
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/luxfi/crypto/secp256k1"
|
|
"github.com/luxfi/database"
|
|
"github.com/luxfi/database/encdb"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/math/set"
|
|
"github.com/luxfi/utxo/secp256k1fx"
|
|
"github.com/luxfi/vm/api/keystore"
|
|
)
|
|
|
|
// Max number of addresses allowed for a single keystore user
|
|
const maxKeystoreAddresses = 5000
|
|
|
|
var (
|
|
// Key in the database whose corresponding value is the list of addresses
|
|
// this user controls
|
|
addressesKey = ids.Empty[:]
|
|
|
|
errMaxAddresses = fmt.Errorf("keystore user has reached its limit of %d addresses", maxKeystoreAddresses)
|
|
|
|
_ User = (*user)(nil)
|
|
)
|
|
|
|
type User interface {
|
|
io.Closer
|
|
|
|
// Get the addresses controlled by this user
|
|
GetAddresses() ([]ids.ShortID, error)
|
|
|
|
// PutKeys persists [privKeys]
|
|
PutKeys(privKeys ...*secp256k1.PrivateKey) error
|
|
|
|
// GetKey returns the private key that controls the given address
|
|
GetKey(address ids.ShortID) (*secp256k1.PrivateKey, error)
|
|
}
|
|
|
|
type user struct {
|
|
db *encdb.Database
|
|
}
|
|
|
|
// NewUserFromKeystore tracks a keystore user from the provided keystore
|
|
func NewUserFromKeystore(ks keystore.BlockchainKeystore, username, password string) (User, error) {
|
|
db, err := ks.GetDatabase(username, password)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("problem retrieving user %q: %w", username, err)
|
|
}
|
|
return NewUserFromDB(db), nil
|
|
}
|
|
|
|
// NewUserFromDB tracks a keystore user from a database
|
|
func NewUserFromDB(db *encdb.Database) User {
|
|
return &user{db: db}
|
|
}
|
|
|
|
func (u *user) GetAddresses() ([]ids.ShortID, error) {
|
|
// Get user's addresses
|
|
addressBytes, err := u.db.Get(addressesKey)
|
|
if err == database.ErrNotFound {
|
|
// If user has no addresses, return empty list
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return unmarshalAddresses(addressBytes)
|
|
}
|
|
|
|
func (u *user) PutKeys(privKeys ...*secp256k1.PrivateKey) error {
|
|
toStore := make([]*secp256k1.PrivateKey, 0, len(privKeys))
|
|
for _, privKey := range privKeys {
|
|
address := privKey.PublicKey().Address() // address the privKey controls
|
|
hasAddress, err := u.db.Has(address.Bytes())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !hasAddress {
|
|
toStore = append(toStore, privKey)
|
|
}
|
|
}
|
|
|
|
// there's nothing to store
|
|
if len(toStore) == 0 {
|
|
return nil
|
|
}
|
|
|
|
addresses, err := u.GetAddresses()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(toStore) > maxKeystoreAddresses || len(addresses) > maxKeystoreAddresses-len(toStore) {
|
|
return errMaxAddresses
|
|
}
|
|
|
|
for _, privKey := range toStore {
|
|
pk := privKey.PublicKey()
|
|
// Convert public key to Lux address using hash160
|
|
pkBytes := pk.Bytes()
|
|
addressBytes := secp256k1.PubkeyBytesToAddress(pkBytes)
|
|
address, err := ids.ToShortID(addressBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Address --> private key
|
|
if err := u.db.Put(address[:], privKey.Bytes()); err != nil {
|
|
return err
|
|
}
|
|
addresses = append(addresses, address)
|
|
}
|
|
|
|
addressBytes, err := marshalAddresses(addresses)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return u.db.Put(addressesKey, addressBytes)
|
|
}
|
|
|
|
func (u *user) GetKey(address ids.ShortID) (*secp256k1.PrivateKey, error) {
|
|
bytes, err := u.db.Get(address.Bytes())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return secp256k1.ToPrivateKey(bytes)
|
|
}
|
|
|
|
func (u *user) Close() error {
|
|
return u.db.Close()
|
|
}
|
|
|
|
// Create and store a new key that will be controlled by this user.
|
|
func NewKey(u User) (*secp256k1.PrivateKey, error) {
|
|
keys, err := NewKeys(u, 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return keys[0], nil
|
|
}
|
|
|
|
// Create and store [numKeys] new keys that will be controlled by this user.
|
|
func NewKeys(u User, numKeys int) ([]*secp256k1.PrivateKey, error) {
|
|
keys := make([]*secp256k1.PrivateKey, numKeys)
|
|
for i := range keys {
|
|
sk, err := secp256k1.NewPrivateKey()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
keys[i] = sk
|
|
}
|
|
return keys, u.PutKeys(keys...)
|
|
}
|
|
|
|
// Keychain returns a new keychain from the [user].
|
|
// If [addresses] is non-empty it fetches only the keys in addresses. If a key
|
|
// is missing, it will be ignored.
|
|
// If [addresses] is empty, then it will create a keychain using every address
|
|
// in the provided [user].
|
|
func GetKeychain(u User, addresses set.Set[ids.ShortID]) (*secp256k1fx.Keychain, error) {
|
|
addrsList := addresses.List()
|
|
if len(addrsList) == 0 {
|
|
var err error
|
|
addrsList, err = u.GetAddresses()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
kc := secp256k1fx.NewKeychain()
|
|
for _, addr := range addrsList {
|
|
sk, err := u.GetKey(addr)
|
|
if err == database.ErrNotFound {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("problem retrieving private key for address %s: %w", addr, err)
|
|
}
|
|
kc.Add(sk)
|
|
}
|
|
return kc, nil
|
|
}
|