Files
node/vms/components/keystore/user.go
T
zeekayandHanzo Dev 2d227dd3aa Full-codec-kill wave 2: proposervm + xsvm + components + evm + platformvm-residual → native ZAP
Kills pcodecs from 3 complete subsystems (verified go test ./... = 155 ok / 0 FAIL):
- proposervm: block/state/summary struct-is-wire (blockwire.go + statewire.go;
  deleted block/state/summary codec.go). blockKind bytes: reserved=0 signed=1
  option=2. Epoch inlined in unsigned object. proposer/windower chainSource seed
  = binary.LittleEndian (byte-identical to old wrappers.Packer.UnpackLong).
- example/xsvm: tx/block/genesis native Marshal (deleted 3 codec.go); create-chain
  example uses genesis.Marshal().
- components: message.Tx native (deleted codec.go); keystore codec shell removed;
  index pcodecs.LongLen → local const.
- evm/{predicate,lp176}: off pcodecs.
- platformvm residual: state metadata + genesis + metrics + signer + stakeable
  native, vestigial serialize tags removed.

REMAINING (careful solo, agents weekly-capped): warp (reverted — agent left an
ID≠Marshal consensus inconsistency in ChainToL1Conversion; needs proper review,
not a golden-regen) + xvm (reverted — barely started). pcodecs package stays
until those 2 land. /ext/→/v1/ endpoint change also pending.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-11 17:19:20 -07:00

212 lines
5.4 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/node/service/keystore"
"github.com/luxfi/utxo/secp256k1fx"
)
// 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 parseAddresses(addressBytes)
}
// marshalAddresses encodes the user's controlled addresses as the flat
// concatenation of their 20-byte values. ids.ShortID is fixed-width, so the
// count is implied by len/ShortIDLen — no length prefix or codec is needed.
func marshalAddresses(addresses []ids.ShortID) []byte {
b := make([]byte, 0, len(addresses)*ids.ShortIDLen)
for i := range addresses {
b = append(b, addresses[i][:]...)
}
return b
}
// parseAddresses is the inverse of marshalAddresses.
func parseAddresses(b []byte) ([]ids.ShortID, error) {
if len(b)%ids.ShortIDLen != 0 {
return nil, fmt.Errorf("keystore: address blob length %d is not a multiple of %d", len(b), ids.ShortIDLen)
}
n := len(b) / ids.ShortIDLen
if n == 0 {
return nil, nil
}
addresses := make([]ids.ShortID, n)
for i := 0; i < n; i++ {
copy(addresses[i][:], b[i*ids.ShortIDLen:])
}
return addresses, nil
}
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)
}
return u.db.Put(addressesKey, marshalAddresses(addresses))
}
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
}