mirror of
https://github.com/luxfi/sdk.git
synced 2026-10-30 15:19:23 +00:00
Lux Partners Limited is not the entity behind this work. Every place the old name stood — copyright lines, site footers, brand config, legal and compliance text — now names the company that actually holds it. lux.fund is the exception and is not renamed to match: that site belongs to Lux Assets Management LLC, a different company, and it says so. Two kinds of line were left for a person rather than rewritten. A dated news item recording that Lux Partners Limited was established in the Isle of Man is a historical fact, and renaming it would falsify the record. And a sentence asserting that the company is domiciled in the Isle of Man is a claim about a jurisdiction, not a name — an Inc or an LLC is not an Isle of Man company, so swapping the name there would assert something untrue. Co-authored-by: Hanzo Dev <[email protected]>
188 lines
5.9 KiB
Go
188 lines
5.9 KiB
Go
// Copyright (C) 2025, Lux Industries Inc All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package keychain provides key management and keychain utilities.
|
|
package keychain
|
|
|
|
import (
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/keychain"
|
|
"github.com/luxfi/sdk/ledger"
|
|
"github.com/luxfi/sdk/network"
|
|
)
|
|
|
|
// ledgerAdapter adapts sdk/ledger.LedgerDevice to keychain.Ledger interface
|
|
type ledgerAdapter struct {
|
|
device *ledger.LedgerDevice
|
|
}
|
|
|
|
func (la *ledgerAdapter) Address(displayHRP string, addressIndex uint32) (ids.ShortID, error) {
|
|
return la.device.Address(displayHRP, addressIndex)
|
|
}
|
|
|
|
// GetAddresses returns addresses for the given indices (implements keychain.Ledger)
|
|
func (la *ledgerAdapter) GetAddresses(addressIndices []uint32) ([]ids.ShortID, error) {
|
|
return la.device.Addresses(addressIndices)
|
|
}
|
|
|
|
// SignHash signs a hash with a single address index (implements keychain.Ledger)
|
|
func (la *ledgerAdapter) SignHash(hash []byte, addressIndex uint32) ([]byte, error) {
|
|
sigs, err := la.device.SignHash(hash, []uint32{addressIndex})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(sigs) == 0 {
|
|
return nil, fmt.Errorf("no signature returned")
|
|
}
|
|
return sigs[0], nil
|
|
}
|
|
|
|
// Sign signs data with a single address index (implements keychain.Ledger)
|
|
func (la *ledgerAdapter) Sign(data []byte, addressIndex uint32) ([]byte, error) {
|
|
sigs, err := la.device.Sign(data, []uint32{addressIndex})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(sigs) == 0 {
|
|
return nil, fmt.Errorf("no signature returned")
|
|
}
|
|
return sigs[0], nil
|
|
}
|
|
|
|
// SignTransaction signs a transaction with multiple address indices (implements keychain.Ledger)
|
|
func (la *ledgerAdapter) SignTransaction(rawUnsignedHash []byte, addressIndices []uint32) ([][]byte, error) {
|
|
return la.device.Sign(rawUnsignedHash, addressIndices)
|
|
}
|
|
|
|
func (la *ledgerAdapter) Disconnect() error {
|
|
return la.device.Disconnect()
|
|
}
|
|
|
|
type Keychain struct {
|
|
ledgerKc keychain.Keychain // Ledger keychain from ledger-lux-go
|
|
network network.Network
|
|
Ledger *Ledger
|
|
}
|
|
|
|
// LedgerParams is an input to NewKeyChain if a new keychain is to be created using Ledger
|
|
//
|
|
// To view Ledger addresses and their balances, you can use Lux CLI and use the command
|
|
// lux key list --ledger [0,1,2,3,4]
|
|
// The example command above will list the first five addresses in your Ledger
|
|
//
|
|
// To transfer funds between addresses in Ledger, refer to https://docs.lux.network/tooling/cli-transfer-funds/how-to-transfer-funds
|
|
type LedgerParams struct {
|
|
// LedgerAddresses specify which addresses in Ledger should be in the Keychain
|
|
// NewKeyChain will then look for the indexes of the specified addresses and add the indexes
|
|
// into LedgerIndices in Ledger
|
|
LedgerAddresses []string
|
|
|
|
// RequiredFunds is the minimum total LUX that the selected addresses from Ledger should contain.
|
|
// NewKeychain will then look through all indexes of all addresses in the Ledger until
|
|
// sufficient LUX balance is reached.
|
|
// For example if Ledger's index 0 and index 1 each contains 0.1 LUX and RequiredFunds is
|
|
// 0.2 LUX, LedgerIndices will have value of [0,1]
|
|
RequiredFunds uint64
|
|
}
|
|
|
|
// Ledger is part of the output of NewKeyChain if a new keychain is to be created using Ledger
|
|
type Ledger struct {
|
|
// LedgerDevice is the main interface of interacting with the Ledger Device
|
|
LedgerDevice *ledger.LedgerDevice
|
|
|
|
// LedgerIndices contain indexes of the addresses selected from Ledger
|
|
LedgerIndices []uint32
|
|
}
|
|
|
|
// NewKeychain generates a new key pair from either a stored key path or Ledger.
|
|
// For stored keys, NewKeychain will generate a new key pair in the provided keyPath if no .pk
|
|
// file currently exists in the provided path.
|
|
func NewKeychain(
|
|
network network.Network,
|
|
keyPath string,
|
|
ledgerInfo *LedgerParams,
|
|
) (*Keychain, error) {
|
|
if ledgerInfo != nil {
|
|
if keyPath != "" {
|
|
return nil, fmt.Errorf("keychain can only created either from key path or ledger, not both")
|
|
}
|
|
dev, err := ledger.New()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
kc := Keychain{
|
|
Ledger: &Ledger{
|
|
LedgerDevice: dev,
|
|
},
|
|
network: network,
|
|
}
|
|
if ledgerInfo.RequiredFunds > 0 {
|
|
if err := kc.AddLedgerFunds(ledgerInfo.RequiredFunds); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if len(ledgerInfo.LedgerAddresses) > 0 {
|
|
if err := kc.AddLedgerAddresses(ledgerInfo.LedgerAddresses); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if len(kc.Ledger.LedgerIndices) == 0 {
|
|
return nil, fmt.Errorf("keychain currently does not contain any addresses from ledger")
|
|
}
|
|
return &kc, nil
|
|
}
|
|
// Create empty keychain struct
|
|
// File-based key loading not yet implemented
|
|
// ledgerKc remains nil for non-ledger keychains
|
|
kc := Keychain{
|
|
network: network,
|
|
}
|
|
return &kc, nil
|
|
}
|
|
|
|
func (kc *Keychain) LedgerEnabled() bool {
|
|
return kc.Ledger != nil && kc.Ledger.LedgerDevice != nil
|
|
}
|
|
|
|
func (kc *Keychain) AddLedgerIndices(indices []uint32) error {
|
|
if kc.LedgerEnabled() {
|
|
kc.Ledger.LedgerIndices = append(kc.Ledger.LedgerIndices, indices...)
|
|
slices.Sort(kc.Ledger.LedgerIndices)
|
|
kc.Ledger.LedgerIndices = slices.Compact(kc.Ledger.LedgerIndices)
|
|
adapter := &ledgerAdapter{device: kc.Ledger.LedgerDevice}
|
|
newKc, err := keychain.NewLedgerKeychainFromIndices(adapter, kc.Ledger.LedgerIndices)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
kc.ledgerKc = newKc
|
|
return nil
|
|
}
|
|
return fmt.Errorf("keychain is not ledger enabled")
|
|
}
|
|
|
|
func (kc *Keychain) AddLedgerAddresses(addresses []string) error {
|
|
if kc.LedgerEnabled() {
|
|
indices, err := kc.Ledger.LedgerDevice.FindAddresses(addresses, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return kc.AddLedgerIndices(slices.Collect(maps.Values(indices)))
|
|
}
|
|
return fmt.Errorf("keychain is not ledger enabled")
|
|
}
|
|
|
|
func (kc *Keychain) AddLedgerFunds(amount uint64) error {
|
|
if kc.LedgerEnabled() {
|
|
indices, err := kc.Ledger.LedgerDevice.FindFunds(kc.network, amount, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return kc.AddLedgerIndices(indices)
|
|
}
|
|
return fmt.Errorf("keychain is not ledger enabled")
|
|
}
|