Files
vm/components/keystore/user.go
zeekay 1db806d65f vm/components/keystore: hand-rolled addresses marshal, drop codec.Manager
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.
2026-06-05 23:55:12 -07:00

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
}