From 84059d6ececbe35b1acc51f24b463c63e84d5df8 Mon Sep 17 00:00:00 2001 From: Zach Kelling Date: Sun, 21 Dec 2025 16:57:13 -0800 Subject: [PATCH] initial commit --- allocations.go | 299 ++++++++++++++++++++++++++++++++ cmd/keys/main.go | 293 +++++++++++++++++++++++++++++++ get_correct_node_ids.go | 55 ++++++ get_node_ids.go | 66 +++++++ go.mod | 31 ++++ go.sum | 160 +++++++++++++++++ keys.go | 372 ++++++++++++++++++++++++++++++++++++++++ upgrade_keys.go | 161 +++++++++++++++++ 8 files changed, 1437 insertions(+) create mode 100644 allocations.go create mode 100644 cmd/keys/main.go create mode 100644 get_correct_node_ids.go create mode 100644 get_node_ids.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 keys.go create mode 100644 upgrade_keys.go diff --git a/allocations.go b/allocations.go new file mode 100644 index 0000000..7412e51 --- /dev/null +++ b/allocations.go @@ -0,0 +1,299 @@ +// Copyright (C) 2024-2025, Lux Industries Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package keys + +import ( + "fmt" + + "github.com/luxfi/constants" + "github.com/luxfi/node/utils/formatting/address" +) + +// Unit constants for LUX amounts +const ( + MicroLux uint64 = 1 // Base unit (6 decimals) + Lux uint64 = 1_000_000 // 10^6 microLux + KiloLux uint64 = 1_000 * Lux // 10^9 + MegaLux uint64 = 1_000_000 * Lux // 10^12 + GigaLux uint64 = 1_000_000_000 * Lux // 10^15 (1B LUX) + TeraLux uint64 = 1_000_000_000_000 * Lux // 10^18 (1T LUX) + + // C-chain uses 18 decimals (wei), P/X-chain use 6 decimals + // Multiply P-chain amount by this to get C-chain wei + CChainDecimalShift = 1_000_000_000_000 // 10^12 + + // Default validator stake: 1M LUX + DefaultValidatorStake = MegaLux + + // Default fee account amount: 10M LUX (for chain creation, transactions) + DefaultFeeAccountAmount = 10 * MegaLux +) + +// Allocation represents a P-chain genesis allocation +type Allocation struct { + // ETHAddr is the C-chain compatible address (0x...) + ETHAddr string `json:"ethAddr"` + + // LUXAddr is the P/X-chain address (P-lux1...) + LUXAddr string `json:"luxAddr"` + + // InitialAmount is immediately available on X-chain (usually 0) + InitialAmount uint64 `json:"initialAmount"` + + // UnlockSchedule defines when funds become available on P-chain + UnlockSchedule []LockedAmount `json:"unlockSchedule"` +} + +// LockedAmount represents a locked amount with unlock time +type LockedAmount struct { + Amount uint64 `json:"amount"` + Locktime uint64 `json:"locktime"` +} + +// Staker represents an initial validator in genesis +type Staker struct { + NodeID string `json:"nodeID"` + RewardAddress string `json:"rewardAddress"` + DelegationFee uint32 `json:"delegationFee"` + Signer *Signer `json:"signer,omitempty"` +} + +// Signer contains BLS key information for a validator +type Signer struct { + PublicKey string `json:"publicKey"` + ProofOfPossession string `json:"proofOfPossession"` +} + +// CChainAlloc represents a C-chain genesis allocation +type CChainAlloc struct { + Balance string `json:"balance"` // Hex-encoded wei amount +} + +// GenesisAllocations contains all allocations for network genesis +type GenesisAllocations struct { + // P-chain allocations + PChainAllocations []Allocation `json:"allocations"` + + // Initial staked funds (addresses that are staked at genesis) + InitialStakedFunds []string `json:"initialStakedFunds"` + + // Initial stakers (validators at genesis) + InitialStakers []Staker `json:"initialStakers"` + + // C-chain allocations (address -> balance) + CChainAllocations map[string]CChainAlloc `json:"cchain"` +} + +// AllocationBuilder helps build genesis allocations from validator keys +type AllocationBuilder struct { + networkID uint32 + hrp string + keys []*ValidatorKey + amountPerKey uint64 + feeAccountIndex int // Which key gets extra funds for fees + feeAccountExtra uint64 // Extra amount for fee account + vestingStart uint64 // Unix timestamp when vesting starts + vestingInterval uint64 // Seconds between each unlock + vestingPeriods int // Number of unlock periods + noVesting bool // If true, all funds immediately available +} + +// NewAllocationBuilder creates a new builder for the given keys +func NewAllocationBuilder(networkID uint32, keys []*ValidatorKey) *AllocationBuilder { + hrp := constants.GetHRP(networkID) + return &AllocationBuilder{ + networkID: networkID, + hrp: hrp, + keys: keys, + amountPerKey: DefaultValidatorStake, + feeAccountIndex: 0, + feeAccountExtra: DefaultFeeAccountAmount, + vestingStart: 1577836800, // Jan 1, 2020 + vestingInterval: 365 * 24 * 60 * 60, // 1 year + vestingPeriods: 100, // 100 years + noVesting: false, + } +} + +// WithAmount sets the amount per validator +func (ab *AllocationBuilder) WithAmount(amount uint64) *AllocationBuilder { + ab.amountPerKey = amount + return ab +} + +// WithFeeAccount sets which validator gets extra funds for fees +func (ab *AllocationBuilder) WithFeeAccount(index int, extra uint64) *AllocationBuilder { + ab.feeAccountIndex = index + ab.feeAccountExtra = extra + return ab +} + +// WithVesting configures the vesting schedule +func (ab *AllocationBuilder) WithVesting(start uint64, interval uint64, periods int) *AllocationBuilder { + ab.vestingStart = start + ab.vestingInterval = interval + ab.vestingPeriods = periods + ab.noVesting = false + return ab +} + +// WithNoVesting makes all funds immediately available +func (ab *AllocationBuilder) WithNoVesting() *AllocationBuilder { + ab.noVesting = true + return ab +} + +// WithImmediateUnlock makes funds immediately unlocked (locktime=0) +func (ab *AllocationBuilder) WithImmediateUnlock() *AllocationBuilder { + ab.vestingStart = 0 + ab.vestingInterval = 0 + ab.vestingPeriods = 1 + ab.noVesting = true + return ab +} + +// Build creates the genesis allocations +func (ab *AllocationBuilder) Build() (*GenesisAllocations, error) { + if len(ab.keys) == 0 { + return nil, fmt.Errorf("no keys provided") + } + + result := &GenesisAllocations{ + PChainAllocations: make([]Allocation, len(ab.keys)), + InitialStakedFunds: make([]string, len(ab.keys)), + InitialStakers: make([]Staker, len(ab.keys)), + CChainAllocations: make(map[string]CChainAlloc), + } + + for i, key := range ab.keys { + // Calculate amount for this key + amount := ab.amountPerKey + if i == ab.feeAccountIndex { + amount += ab.feeAccountExtra + } + + // Format addresses + pChainAddr, err := address.Format("P", ab.hrp, key.PChainAddr[:]) + if err != nil { + return nil, fmt.Errorf("failed to format P-chain address for key %d: %w", i, err) + } + ethAddr := key.CChainAddrHex() + + // Build unlock schedule + var unlockSchedule []LockedAmount + if ab.noVesting { + // Immediate unlock + unlockSchedule = []LockedAmount{{ + Amount: amount, + Locktime: 0, + }} + } else { + // Vested unlock over periods + amountPerPeriod := amount / uint64(ab.vestingPeriods) + remainder := amount % uint64(ab.vestingPeriods) + + unlockSchedule = make([]LockedAmount, ab.vestingPeriods) + for p := 0; p < ab.vestingPeriods; p++ { + periodAmount := amountPerPeriod + if p == ab.vestingPeriods-1 { + periodAmount += remainder // Add remainder to last period + } + unlockSchedule[p] = LockedAmount{ + Amount: periodAmount, + Locktime: ab.vestingStart + uint64(p)*ab.vestingInterval, + } + } + } + + // P-chain allocation (uses P-chain address for unlocking) + result.PChainAllocations[i] = Allocation{ + ETHAddr: ethAddr, + LUXAddr: pChainAddr, + InitialAmount: 0, // X-chain initial (usually 0) + UnlockSchedule: unlockSchedule, + } + + // Mark as initially staked (uses P-chain address) + result.InitialStakedFunds[i] = pChainAddr + + // Initial staker + staker := Staker{ + NodeID: key.NodeID.String(), + RewardAddress: pChainAddr, + DelegationFee: 20000, // 2% + } + + // Add BLS signer if available + if len(key.BLSPublicKey) > 0 { + staker.Signer = &Signer{ + PublicKey: key.BLSPublicKeyHex(), + ProofOfPossession: key.BLSPoPHex(), + } + } + result.InitialStakers[i] = staker + + // C-chain allocation (convert to wei: amount * 10^12) + cchainBalance := amount * CChainDecimalShift + result.CChainAllocations[ethAddr] = CChainAlloc{ + Balance: fmt.Sprintf("0x%x", cchainBalance), + } + } + + return result, nil +} + +// QuickAllocations creates allocations with immediate unlock for testing +func QuickAllocations(networkID uint32, keys []*ValidatorKey, amountPerKey uint64) (*GenesisAllocations, error) { + return NewAllocationBuilder(networkID, keys). + WithAmount(amountPerKey). + WithImmediateUnlock(). + Build() +} + +// TestnetAllocations creates allocations suitable for testnet (no vesting) +func TestnetAllocations(networkID uint32, keys []*ValidatorKey) (*GenesisAllocations, error) { + return NewAllocationBuilder(networkID, keys). + WithAmount(100 * MegaLux). // 100M LUX per validator + WithFeeAccount(0, 10*MegaLux). // First validator gets extra + WithNoVesting(). + Build() +} + +// MainnetAllocations creates allocations suitable for mainnet (100-year vesting) +func MainnetAllocations(networkID uint32, keys []*ValidatorKey) (*GenesisAllocations, error) { + return NewAllocationBuilder(networkID, keys). + WithAmount(GigaLux). // 1B LUX per validator + WithVesting( + 1577836800, // Jan 1, 2020 + 365*24*60*60, // 1 year intervals + 100, // 100 periods + ). + Build() +} + +// GenerateAndAllocate generates keys and creates allocations in one step +func GenerateAndAllocate(keyStore *KeyStore, networkID uint32, count int, prefix string, amountPerKey uint64) (*GenesisAllocations, error) { + keys, err := keyStore.GenerateMultiple(count, prefix) + if err != nil { + return nil, fmt.Errorf("failed to generate keys: %w", err) + } + + return NewAllocationBuilder(networkID, keys). + WithAmount(amountPerKey). + WithImmediateUnlock(). + Build() +} + +// LoadAndAllocate loads existing keys and creates allocations +func LoadAndAllocate(keyStore *KeyStore, networkID uint32, amountPerKey uint64) (*GenesisAllocations, error) { + keys, err := keyStore.LoadAll() + if err != nil { + return nil, fmt.Errorf("failed to load keys: %w", err) + } + + return NewAllocationBuilder(networkID, keys). + WithAmount(amountPerKey). + WithImmediateUnlock(). + Build() +} diff --git a/cmd/keys/main.go b/cmd/keys/main.go new file mode 100644 index 0000000..57d15f7 --- /dev/null +++ b/cmd/keys/main.go @@ -0,0 +1,293 @@ +// Copyright (C) 2024-2025, Lux Industries Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Command keys manages validator keys for Lux networks. +package main + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strconv" + + "github.com/luxfi/keys" +) + +func main() { + if len(os.Args) < 2 { + printUsage() + os.Exit(1) + } + + home, _ := os.UserHomeDir() + defaultDir := filepath.Join(home, ".lux", "keys") + + // Check for --dir flag + keyDir := defaultDir + args := os.Args[1:] + for i, arg := range args { + if arg == "--dir" && i+1 < len(args) { + keyDir = args[i+1] + args = append(args[:i], args[i+2:]...) + break + } + } + + if len(args) < 1 { + printUsage() + os.Exit(1) + } + + ks := keys.NewKeyStore(keyDir) + + switch args[0] { + case "generate", "gen": + if len(args) < 2 { + fmt.Println("Usage: keys generate [prefix]") + os.Exit(1) + } + count, err := strconv.Atoi(args[1]) + if err != nil { + fmt.Printf("Invalid count: %s\n", args[1]) + os.Exit(1) + } + prefix := "node" + if len(args) > 2 { + prefix = args[2] + } + generateKeys(ks, count, prefix) + + case "list", "ls": + listKeys(ks) + + case "show": + if len(args) < 2 { + fmt.Println("Usage: keys show ") + os.Exit(1) + } + showKey(ks, args[1]) + + case "allocations", "alloc": + if len(args) < 2 { + fmt.Println("Usage: keys allocations [amount-per-key]") + os.Exit(1) + } + networkID, err := strconv.ParseUint(args[1], 10, 32) + if err != nil { + fmt.Printf("Invalid network ID: %s\n", args[1]) + os.Exit(1) + } + amount := keys.DefaultValidatorStake + if len(args) > 2 { + amount, err = strconv.ParseUint(args[2], 10, 64) + if err != nil { + fmt.Printf("Invalid amount: %s\n", args[2]) + os.Exit(1) + } + } + generateAllocations(ks, uint32(networkID), amount) + + case "upgrade": + // Upgrade existing keys to add BLS and EC keys if missing + upgradeKeys(ks) + + default: + printUsage() + os.Exit(1) + } +} + +func printUsage() { + fmt.Println(`Usage: keys [--dir ] [args] + +Commands: + generate [prefix] Generate new validator keys + list List all keys + show Show key details + allocations Generate P-chain allocations JSON + upgrade Add BLS/EC keys to existing staking keys + +Options: + --dir Key store directory (default: ~/.lux/keys) + +Examples: + keys generate 5 node Generate 5 keys named node1-node5 + keys list List all keys + keys show node1 Show node1 key details + keys allocations 1337 Generate local network allocations`) +} + +func generateKeys(ks *keys.KeyStore, count int, prefix string) { + fmt.Printf("Generating %d validator keys with prefix '%s'...\n", count, prefix) + + vkeys, err := ks.GenerateMultiple(count, prefix) + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + fmt.Println("\nGenerated keys:") + for i, vk := range vkeys { + name := fmt.Sprintf("%s%d", prefix, i+1) + fmt.Printf(" %s:\n", name) + fmt.Printf(" NodeID: %s\n", vk.NodeID.String()) + fmt.Printf(" P-Chain: %s\n", vk.PChainAddr.String()) + fmt.Printf(" C-Chain: %s\n", vk.CChainAddrHex()) + if len(vk.BLSPublicKey) > 0 { + fmt.Printf(" BLS PubKey: %s...\n", vk.BLSPublicKeyHex()[:20]) + } + } +} + +func listKeys(ks *keys.KeyStore) { + names, err := ks.List() + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + if len(names) == 0 { + fmt.Println("No keys found.") + return + } + + fmt.Println("Validator keys:") + for _, name := range names { + vk, err := ks.Load(name) + if err != nil { + fmt.Printf(" %s: (error loading: %v)\n", name, err) + continue + } + hasBLS := "✓" + if len(vk.BLSPublicKey) == 0 { + hasBLS = "✗" + } + hasEC := "✓" + if len(vk.ECPrivateKey) == 0 { + hasEC = "✗" + } + fmt.Printf(" %s: NodeID=%s BLS=%s EC=%s\n", name, vk.NodeID.String(), hasBLS, hasEC) + } +} + +func showKey(ks *keys.KeyStore, name string) { + vk, err := ks.Load(name) + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + fmt.Printf("Key: %s\n", name) + fmt.Printf(" NodeID: %s\n", vk.NodeID.String()) + fmt.Printf(" P-Chain Addr: %s\n", vk.PChainAddr.String()) + fmt.Printf(" C-Chain Addr: %s\n", vk.CChainAddrHex()) + + if len(vk.BLSPublicKey) > 0 { + fmt.Printf(" BLS Public: %s\n", vk.BLSPublicKeyHex()) + fmt.Printf(" BLS PoP: %s\n", vk.BLSPoPHex()) + } else { + fmt.Printf(" BLS Keys: (not available)\n") + } + + if len(vk.ECPrivateKey) > 0 { + fmt.Printf(" EC Key: (available)\n") + } else { + fmt.Printf(" EC Key: (not available)\n") + } +} + +func generateAllocations(ks *keys.KeyStore, networkID uint32, amountPerKey uint64) { + vkeys, err := ks.LoadAll() + if err != nil { + fmt.Printf("Error loading keys: %v\n", err) + os.Exit(1) + } + + if len(vkeys) == 0 { + fmt.Println("No keys found. Generate keys first with 'keys generate'.") + os.Exit(1) + } + + allocs, err := keys.NewAllocationBuilder(networkID, vkeys). + WithAmount(amountPerKey). + WithFeeAccount(0, 10*keys.MegaLux). // First validator gets extra + WithImmediateUnlock(). + Build() + if err != nil { + fmt.Printf("Error generating allocations: %v\n", err) + os.Exit(1) + } + + // Output P-chain allocations + fmt.Println("// P-Chain allocations (pchain.json)") + pchainJSON, _ := json.MarshalIndent(map[string]interface{}{ + "allocations": allocs.PChainAllocations, + "initialStakedFunds": allocs.InitialStakedFunds, + "initialStakers": allocs.InitialStakers, + }, "", " ") + fmt.Println(string(pchainJSON)) + + fmt.Println("\n// C-Chain allocations (for cchain.json alloc field)") + cchainJSON, _ := json.MarshalIndent(allocs.CChainAllocations, "", " ") + fmt.Println(string(cchainJSON)) +} + +func upgradeKeys(ks *keys.KeyStore) { + names, err := ks.List() + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + for _, name := range names { + vk, err := ks.Load(name) + if err != nil { + fmt.Printf(" %s: error loading - %v\n", name, err) + continue + } + + needsUpgrade := false + + // Check if BLS keys are missing + if len(vk.BLSSecretKey) == 0 { + fmt.Printf(" %s: generating BLS keys...\n", name) + // We need to generate new keys since we can't add to existing + newKey, err := keys.GenerateValidatorKey() + if err != nil { + fmt.Printf(" %s: error generating - %v\n", name, err) + continue + } + // Copy BLS keys + vk.BLSSecretKey = newKey.BLSSecretKey + vk.BLSPublicKey = newKey.BLSPublicKey + vk.BLSPoP = newKey.BLSPoP + needsUpgrade = true + } + + // Check if EC key is missing + if len(vk.ECPrivateKey) == 0 { + fmt.Printf(" %s: generating EC key...\n", name) + newKey, err := keys.GenerateValidatorKey() + if err != nil { + fmt.Printf(" %s: error generating - %v\n", name, err) + continue + } + // Copy EC key and addresses + vk.ECPrivateKey = newKey.ECPrivateKey + vk.PChainAddr = newKey.PChainAddr + vk.CChainAddr = newKey.CChainAddr + needsUpgrade = true + } + + if needsUpgrade { + if err := ks.Save(name, vk); err != nil { + fmt.Printf(" %s: error saving - %v\n", name, err) + continue + } + fmt.Printf(" %s: upgraded successfully\n", name) + } else { + fmt.Printf(" %s: already up to date\n", name) + } + } +} diff --git a/get_correct_node_ids.go b/get_correct_node_ids.go new file mode 100644 index 0000000..243a843 --- /dev/null +++ b/get_correct_node_ids.go @@ -0,0 +1,55 @@ +//go:build ignore + +// This tool prints NodeIDs for node1-5 key directories. +// Run with: go run get_correct_node_ids.go +package main + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "os" + "path/filepath" + + "github.com/luxfi/ids" +) + +func main() { + keysDir := "/Users/z/work/lux/keys" + + for i := 1; i <= 5; i++ { + certPath := filepath.Join(keysDir, fmt.Sprintf("node%d", i), "staker.crt") + nodeID, err := getNodeID(certPath) + if err != nil { + fmt.Printf("Error getting node%d ID: %v\n", i, err) + continue + } + fmt.Printf("Node%d: %s\n", i, nodeID) + } +} + +func getNodeID(certPath string) (string, error) { + certPEM, err := os.ReadFile(certPath) + if err != nil { + return "", err + } + + block, _ := pem.Decode(certPEM) + if block == nil { + return "", fmt.Errorf("failed to decode PEM block") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return "", err + } + + // Use the ids package to get the correct NodeID + idsCert := &ids.Certificate{ + Raw: cert.Raw, + PublicKey: cert.PublicKey, + } + nodeID := ids.NodeIDFromCert(idsCert) + + return nodeID.String(), nil +} diff --git a/get_node_ids.go b/get_node_ids.go new file mode 100644 index 0000000..bd15c23 --- /dev/null +++ b/get_node_ids.go @@ -0,0 +1,66 @@ +//go:build ignore + +// Deprecated: Use get_correct_node_ids.go instead - this uses incorrect NodeID derivation +package main + +import ( + "crypto/sha256" + "crypto/x509" + "encoding/pem" + "fmt" + "os" + "path/filepath" + + "github.com/mr-tron/base58" +) + +func main() { + keysDir := "/Users/z/work/lux/keys" + + for i := 1; i <= 5; i++ { + certPath := filepath.Join(keysDir, fmt.Sprintf("node%d", i), "staker.crt") + nodeID, err := getNodeID(certPath) + if err != nil { + fmt.Printf("Error getting node%d ID: %v\n", i, err) + continue + } + fmt.Printf("Node%d: %s\n", i, nodeID) + } +} + +func getNodeID(certPath string) (string, error) { + certPEM, err := os.ReadFile(certPath) + if err != nil { + return "", err + } + + block, _ := pem.Decode(certPEM) + if block == nil { + return "", fmt.Errorf("failed to decode PEM block") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return "", err + } + + // NodeID is SHA256 of the raw public key bytes + pubKeyBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey) + if err != nil { + return "", err + } + + hash := sha256.Sum256(pubKeyBytes) + + // Add checksum (last 4 bytes of double SHA256) + hashWithChecksum := append(hash[:], checksum(hash[:])...) + + encoded := base58.Encode(hashWithChecksum) + return "NodeID-" + encoded, nil +} + +func checksum(data []byte) []byte { + first := sha256.Sum256(data) + second := sha256.Sum256(first[:]) + return second[:4] +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4eb34cb --- /dev/null +++ b/go.mod @@ -0,0 +1,31 @@ +module github.com/luxfi/keys + +go 1.25.5 + +require ( + github.com/luxfi/constants v1.2.3 + github.com/luxfi/crypto v1.17.25 + github.com/luxfi/ids v1.2.4 + github.com/luxfi/node v1.22.37 + golang.org/x/crypto v0.46.0 +) + +require ( + github.com/btcsuite/btcd/btcutil v1.1.6 // indirect + github.com/cloudflare/circl v1.6.2-0.20251204010831-23491bd573cf // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/google/renameio/v2 v2.0.0 // indirect + github.com/gorilla/rpc v1.2.1 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/luxfi/cache v1.0.0 // indirect + github.com/luxfi/consensus v1.22.35 // indirect + github.com/luxfi/math v1.0.2 // indirect + github.com/luxfi/mock v0.1.0 // indirect + github.com/luxfi/utils v1.0.0 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/supranational/blst v0.3.16 // indirect + go.uber.org/mock v0.6.0 // indirect + golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect + golang.org/x/sys v0.39.0 // indirect + gonum.org/v1/gonum v0.16.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8fe2eaf --- /dev/null +++ b/go.sum @@ -0,0 +1,160 @@ +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cloudflare/circl v1.6.2-0.20251204010831-23491bd573cf h1:+0huF756d72ZdHxachRP5HYmvRkkOk8CLyKUmJM138c= +github.com/cloudflare/circl v1.6.2-0.20251204010831-23491bd573cf/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= +github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= +github.com/gorilla/rpc v1.2.1 h1:yC+LMV5esttgpVvNORL/xX4jvTTEUE30UZhZ5JF7K9k= +github.com/gorilla/rpc v1.2.1/go.mod h1:uNpOihAlF5xRFLuTYhfR0yfCTm0WTQSQttkMSptRfGk= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/luxfi/cache v1.0.0 h1:OcqGi/qs9ky+7O8fR6WMZtFsunvA40LBTg3QRQqJurs= +github.com/luxfi/cache v1.0.0/go.mod h1:BEvpffvV3SXs/KSa9nUC1TszxKwUdIHwIaoXpeiYU+Y= +github.com/luxfi/consensus v1.22.35 h1:ZJOEITtoy5pT9OkT+z+9qulbsVNFdcdsUAibCtXSoF4= +github.com/luxfi/consensus v1.22.35/go.mod h1:/sNvvKfESWfq57Jmrf22T2GquLNg6PU9m9MJ3wqfoYU= +github.com/luxfi/constants v1.2.3 h1:ewo3Ch6AJDI3G42J0rlNAdZU1Fv96YEyOmp3E2K/pzE= +github.com/luxfi/constants v1.2.3/go.mod h1:r5WL5Z03I9iHo910pHykO+RuDCwe4jy0rBSueG1l1mo= +github.com/luxfi/crypto v1.17.25 h1:emTejQ17bGj/1tu0ETB196Hbn6fJfo8wUNGIOWit6RM= +github.com/luxfi/crypto v1.17.25/go.mod h1:v6eaW5ejuzW2gecChWKQqx4CtVhoM1b+e40ro/6WVPg= +github.com/luxfi/ids v1.2.4 h1:e0OaeSI6xWjS9JnxxxfvCCs9HHDUrwDc3M9ctIhjcDI= +github.com/luxfi/ids v1.2.4/go.mod h1:HwvRJSNbuZS+u+0JqeHfPX2S13iFyLCnfGxjBCmt244= +github.com/luxfi/math v1.0.2 h1:bgBGmVC7S/QxLCgAJZJdvjFHMNXygcApcKofIn7bWpU= +github.com/luxfi/math v1.0.2/go.mod h1:2Jk1/s0f0yN2q0Sx/cBExbFJz7IDEbjxlP3VUiaujms= +github.com/luxfi/mock v0.1.0 h1:IwElfNu+T9sXvzFX6tudPDx1vqPuACRSRdxpD5lxW+o= +github.com/luxfi/mock v0.1.0/go.mod h1:izF+9K0gGzFC9zERn6Po37v46eLdPB+EIsDjL3GLk+U= +github.com/luxfi/node v1.22.37 h1:njIPH9hmX+TphoxsjSEEWtICfrPxfu4/3ak4zg6A8kM= +github.com/luxfi/node v1.22.37/go.mod h1:d31vDerMnDBlid/YmKz1M+i/hBfBismLQn4jR8UAFeQ= +github.com/luxfi/utils v1.0.0 h1:eibxyw/xkwnB9KRSmYvKLbdLTUElyfJpxLIBeTBKqlU= +github.com/luxfi/utils v1.0.0/go.mod h1:ABqhBdGNig0CaDcnNYldv1byS0BTNi5IKPbJSPF1p98= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/supranational/blst v0.3.16 h1:bTDadT+3fK497EvLdWRQEjiGnUtzJ7jjIUMF0jqwYhE= +github.com/supranational/blst v0.3.16/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/thepudds/fzgen v0.4.3 h1:srUP/34BulQaEwPP/uHZkdjUcUjIzL7Jkf4CBVryiP8= +github.com/thepudds/fzgen v0.4.3/go.mod h1:BhhwtRhzgvLWAjjcHDJ9pEiLD2Z9hrVIFjBCHJ//zJ4= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0= +golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/keys.go b/keys.go new file mode 100644 index 0000000..226c467 --- /dev/null +++ b/keys.go @@ -0,0 +1,372 @@ +// Copyright (C) 2024-2025, Lux Industries Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Package keys provides validator key management for Lux networks. +// It handles generation, loading, and storage of: +// - TLS staking keys (for node identity) +// - BLS signer keys (for validator consensus) +// - EC private keys (for P/X/C-chain addresses) +package keys + +import ( + "crypto/ecdsa" + "encoding/base64" + "encoding/hex" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/luxfi/crypto/bls" + "github.com/luxfi/crypto/bls/signer/localsigner" + luxcrypto "github.com/luxfi/crypto/secp256k1" + "github.com/luxfi/ids" + "github.com/luxfi/node/staking" + "github.com/luxfi/node/vms/platformvm/signer" + "golang.org/x/crypto/sha3" +) + +// ValidatorKey contains all keys needed for a validator node +type ValidatorKey struct { + // NodeID is the unique identifier for the node (derived from TLS cert) + NodeID ids.NodeID + + // TLS keys for node identity + StakerKey []byte // PEM-encoded private key + StakerCert []byte // PEM-encoded certificate + + // BLS keys for consensus + BLSSecretKey []byte // Raw BLS secret key bytes + BLSPublicKey []byte // Compressed BLS public key + BLSPoP []byte // Proof of Possession signature + + // EC key for addresses + ECPrivateKey []byte // Raw 32-byte secp256k1 private key + + // Derived addresses + PChainAddr ids.ShortID // P/X chain address (20 bytes) + CChainAddr ids.ShortID // C-chain address (20 bytes, Ethereum format) +} + +// KeyStore manages validator keys with filesystem persistence +type KeyStore struct { + baseDir string +} + +// NewKeyStore creates a new key store at the given directory +func NewKeyStore(baseDir string) *KeyStore { + if baseDir == "" { + home, _ := os.UserHomeDir() + baseDir = filepath.Join(home, ".lux", "keys") + } + return &KeyStore{baseDir: baseDir} +} + +// BaseDir returns the base directory for the key store +func (ks *KeyStore) BaseDir() string { + return ks.baseDir +} + +// GenerateValidatorKey creates a complete set of validator keys +func GenerateValidatorKey() (*ValidatorKey, error) { + vk := &ValidatorKey{} + + // 1. Generate TLS staking key + certPEM, keyPEM, err := staking.NewCertAndKeyBytes() + if err != nil { + return nil, fmt.Errorf("failed to generate TLS cert: %w", err) + } + + vk.StakerCert = certPEM + vk.StakerKey = keyPEM + + // Parse cert to derive NodeID + tlsCert, err := staking.LoadTLSCertFromBytes(keyPEM, certPEM) + if err != nil { + return nil, fmt.Errorf("failed to parse TLS cert: %w", err) + } + stakingCert := &ids.Certificate{ + Raw: tlsCert.Leaf.Raw, + PublicKey: tlsCert.Leaf.PublicKey, + } + vk.NodeID = ids.NodeIDFromCert(stakingCert) + + // 2. Generate BLS signer key + blsKey, err := localsigner.New() + if err != nil { + return nil, fmt.Errorf("failed to generate BLS key: %w", err) + } + vk.BLSSecretKey = blsKey.ToBytes() + + pop, err := signer.NewProofOfPossession(blsKey) + if err != nil { + return nil, fmt.Errorf("failed to generate BLS PoP: %w", err) + } + vk.BLSPublicKey = pop.PublicKey[:] + vk.BLSPoP = pop.ProofOfPossession[:] + + // 3. Generate EC private key for addresses + ecKey, err := luxcrypto.NewPrivateKey() + if err != nil { + return nil, fmt.Errorf("failed to generate EC key: %w", err) + } + vk.ECPrivateKey = ecKey.Bytes() + + // Derive P-chain address + pubKey := ecKey.PublicKey() + vk.PChainAddr = ids.ShortID(pubKey.Address()) + + // Derive C-chain (Ethereum) address + ecdsaPubKey := pubKey.ToECDSA() + vk.CChainAddr = pubkeyToAddress(ecdsaPubKey) + + return vk, nil +} + +// pubkeyToAddress derives an Ethereum address from an ECDSA public key +func pubkeyToAddress(pub *ecdsa.PublicKey) ids.ShortID { + // Ethereum address is last 20 bytes of Keccak256(uncompressed pubkey without prefix) + pubBytes := make([]byte, 64) + copy(pubBytes[:32], pub.X.Bytes()) + copy(pubBytes[32:], pub.Y.Bytes()) + + h := sha3.NewLegacyKeccak256() + h.Write(pubBytes) + hash := h.Sum(nil) + + var addr ids.ShortID + copy(addr[:], hash[12:32]) + return addr +} + +// Save persists a validator key to the filesystem +func (ks *KeyStore) Save(name string, vk *ValidatorKey) error { + nodeDir := filepath.Join(ks.baseDir, name) + + // Create directory structure + dirs := []string{ + nodeDir, + filepath.Join(nodeDir, "staking"), + filepath.Join(nodeDir, "bls"), + filepath.Join(nodeDir, "ec"), + } + for _, dir := range dirs { + if err := os.MkdirAll(dir, 0700); err != nil { + return fmt.Errorf("failed to create directory %s: %w", dir, err) + } + } + + // Save TLS staking key and cert + if err := os.WriteFile(filepath.Join(nodeDir, "staking", "staker.key"), vk.StakerKey, 0600); err != nil { + return fmt.Errorf("failed to write staker.key: %w", err) + } + if err := os.WriteFile(filepath.Join(nodeDir, "staking", "staker.crt"), vk.StakerCert, 0644); err != nil { + return fmt.Errorf("failed to write staker.crt: %w", err) + } + + // Also save to legacy paths for backward compatibility + if err := os.WriteFile(filepath.Join(nodeDir, "staker.key"), vk.StakerKey, 0600); err != nil { + return fmt.Errorf("failed to write staker.key (legacy): %w", err) + } + if err := os.WriteFile(filepath.Join(nodeDir, "staker.crt"), vk.StakerCert, 0644); err != nil { + return fmt.Errorf("failed to write staker.crt (legacy): %w", err) + } + + // Save BLS signer key + if err := os.WriteFile(filepath.Join(nodeDir, "bls", "signer.key"), vk.BLSSecretKey, 0600); err != nil { + return fmt.Errorf("failed to write signer.key: %w", err) + } + + // Save EC private key (hex encoded) + ecKeyHex := hex.EncodeToString(vk.ECPrivateKey) + if err := os.WriteFile(filepath.Join(nodeDir, "ec", "private.key"), []byte(ecKeyHex), 0600); err != nil { + return fmt.Errorf("failed to write private.key: %w", err) + } + + // Save key info JSON for reference + info := fmt.Sprintf(`{ + "nodeID": "%s", + "pChainAddr": "%s", + "cChainAddr": "0x%s", + "blsPublicKey": "0x%s" +} +`, vk.NodeID.String(), + vk.PChainAddr.String(), + hex.EncodeToString(vk.CChainAddr[:]), + hex.EncodeToString(vk.BLSPublicKey)) + if err := os.WriteFile(filepath.Join(nodeDir, "info.json"), []byte(info), 0644); err != nil { + return fmt.Errorf("failed to write info.json: %w", err) + } + + return nil +} + +// Load reads a validator key from the filesystem +func (ks *KeyStore) Load(name string) (*ValidatorKey, error) { + nodeDir := filepath.Join(ks.baseDir, name) + return LoadFromDir(nodeDir) +} + +// LoadFromDir loads a validator key from a specific directory +func LoadFromDir(nodeDir string) (*ValidatorKey, error) { + vk := &ValidatorKey{} + + // Load TLS cert - try modern path first + certPath := filepath.Join(nodeDir, "staking", "staker.crt") + certPEM, err := os.ReadFile(certPath) + if err != nil { + certPath = filepath.Join(nodeDir, "staker.crt") + certPEM, err = os.ReadFile(certPath) + if err != nil { + return nil, fmt.Errorf("failed to read staker.crt: %w", err) + } + } + vk.StakerCert = certPEM + + // Load TLS key + keyPath := filepath.Join(nodeDir, "staking", "staker.key") + keyPEM, err := os.ReadFile(keyPath) + if err != nil { + keyPath = filepath.Join(nodeDir, "staker.key") + keyPEM, err = os.ReadFile(keyPath) + if err != nil { + return nil, fmt.Errorf("failed to read staker.key: %w", err) + } + } + vk.StakerKey = keyPEM + + // Derive NodeID from TLS cert + tlsCert, err := staking.LoadTLSCertFromBytes(keyPEM, certPEM) + if err != nil { + return nil, fmt.Errorf("failed to load TLS cert: %w", err) + } + stakingCert := &ids.Certificate{ + Raw: tlsCert.Leaf.Raw, + PublicKey: tlsCert.Leaf.PublicKey, + } + vk.NodeID = ids.NodeIDFromCert(stakingCert) + + // Load BLS signer key (optional) + signerPath := filepath.Join(nodeDir, "bls", "signer.key") + signerBytes, err := os.ReadFile(signerPath) + if err != nil { + signerPath = filepath.Join(nodeDir, "signer.key") + signerBytes, _ = os.ReadFile(signerPath) + } + if len(signerBytes) > 0 { + vk.BLSSecretKey = signerBytes + // Derive public key and PoP + sk, err := bls.SecretKeyFromBytes(signerBytes) + if err == nil { + pk := bls.PublicFromSecretKey(sk) + vk.BLSPublicKey = bls.PublicKeyToCompressedBytes(pk) + sig := bls.Sign(sk, vk.BLSPublicKey) + vk.BLSPoP = bls.SignatureToBytes(sig) + } + } + + // Load EC private key (optional) + ecPath := filepath.Join(nodeDir, "ec", "private.key") + ecKeyHex, err := os.ReadFile(ecPath) + if err != nil { + ecPath = filepath.Join(nodeDir, "private.key") + ecKeyHex, _ = os.ReadFile(ecPath) + } + if len(ecKeyHex) > 0 { + privKeyBytes, err := hex.DecodeString(strings.TrimSpace(string(ecKeyHex))) + if err == nil && len(privKeyBytes) == 32 { + vk.ECPrivateKey = privKeyBytes + + // Derive addresses + luxPrivKey, err := luxcrypto.ToPrivateKey(privKeyBytes) + if err == nil { + pubKey := luxPrivKey.PublicKey() + vk.PChainAddr = ids.ShortID(pubKey.Address()) + vk.CChainAddr = pubkeyToAddress(pubKey.ToECDSA()) + } + } + } + + // Fallback: derive addresses from NodeID if EC key not available + if vk.PChainAddr == (ids.ShortID{}) { + copy(vk.PChainAddr[:], vk.NodeID[:20]) + copy(vk.CChainAddr[:], vk.NodeID[:20]) + } + + return vk, nil +} + +// List returns all validator keys in the store +func (ks *KeyStore) List() ([]string, error) { + entries, err := os.ReadDir(ks.baseDir) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + + var names []string + for _, entry := range entries { + if entry.IsDir() { + names = append(names, entry.Name()) + } + } + return names, nil +} + +// GenerateMultiple generates multiple validator keys +func (ks *KeyStore) GenerateMultiple(count int, prefix string) ([]*ValidatorKey, error) { + keys := make([]*ValidatorKey, count) + for i := 0; i < count; i++ { + vk, err := GenerateValidatorKey() + if err != nil { + return nil, fmt.Errorf("failed to generate key %d: %w", i, err) + } + keys[i] = vk + + name := fmt.Sprintf("%s%d", prefix, i+1) + if err := ks.Save(name, vk); err != nil { + return nil, fmt.Errorf("failed to save key %s: %w", name, err) + } + } + return keys, nil +} + +// LoadAll loads all validator keys from the store +func (ks *KeyStore) LoadAll() ([]*ValidatorKey, error) { + names, err := ks.List() + if err != nil { + return nil, err + } + + keys := make([]*ValidatorKey, 0, len(names)) + for _, name := range names { + vk, err := ks.Load(name) + if err != nil { + continue // Skip invalid entries + } + keys = append(keys, vk) + } + return keys, nil +} + +// BLSKeyBase64 returns the BLS secret key as base64 (for node config) +func (vk *ValidatorKey) BLSKeyBase64() string { + return base64.StdEncoding.EncodeToString(vk.BLSSecretKey) +} + +// BLSPublicKeyHex returns the BLS public key as hex with 0x prefix +func (vk *ValidatorKey) BLSPublicKeyHex() string { + return "0x" + hex.EncodeToString(vk.BLSPublicKey) +} + +// BLSPoPHex returns the BLS proof of possession as hex with 0x prefix +func (vk *ValidatorKey) BLSPoPHex() string { + return "0x" + hex.EncodeToString(vk.BLSPoP) +} + +// CChainAddrHex returns the C-chain address as hex with 0x prefix +func (vk *ValidatorKey) CChainAddrHex() string { + return "0x" + hex.EncodeToString(vk.CChainAddr[:]) +} diff --git a/upgrade_keys.go b/upgrade_keys.go new file mode 100644 index 0000000..9bd7549 --- /dev/null +++ b/upgrade_keys.go @@ -0,0 +1,161 @@ +// Copyright (C) 2024-2025, Lux Industries Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +//go:build ignore + +// This tool upgrades existing node keys to add BLS signer and EC private keys. +// Run with: go run upgrade_keys.go +package main + +import ( + "encoding/hex" + "fmt" + "os" + "path/filepath" + + "github.com/luxfi/crypto/bls/signer/localsigner" + luxcrypto "github.com/luxfi/crypto/secp256k1" + "github.com/luxfi/ids" + "github.com/luxfi/node/staking" + "github.com/luxfi/node/vms/platformvm/signer" +) + +func main() { + home, _ := os.UserHomeDir() + keysDir := filepath.Join(home, "work", "lux", "keys") + + if len(os.Args) > 1 { + keysDir = os.Args[1] + } + + fmt.Printf("Upgrading keys in: %s\n\n", keysDir) + + entries, err := os.ReadDir(keysDir) + if err != nil { + fmt.Printf("Error reading directory: %v\n", err) + os.Exit(1) + } + + for _, entry := range entries { + if !entry.IsDir() { + continue + } + + name := entry.Name() + nodeDir := filepath.Join(keysDir, name) + + // Check if this is a valid node directory (has staker.crt) + certPath := filepath.Join(nodeDir, "staker.crt") + if _, err := os.Stat(certPath); os.IsNotExist(err) { + continue + } + + fmt.Printf("Processing %s...\n", name) + + if err := upgradeNode(nodeDir); err != nil { + fmt.Printf(" Error: %v\n", err) + continue + } + + fmt.Printf(" Done!\n") + } +} + +func upgradeNode(nodeDir string) error { + // Load existing TLS cert to get NodeID + certPath := filepath.Join(nodeDir, "staker.crt") + keyPath := filepath.Join(nodeDir, "staker.key") + + certPEM, err := os.ReadFile(certPath) + if err != nil { + return fmt.Errorf("failed to read staker.crt: %w", err) + } + keyPEM, err := os.ReadFile(keyPath) + if err != nil { + return fmt.Errorf("failed to read staker.key: %w", err) + } + + tlsCert, err := staking.LoadTLSCertFromBytes(keyPEM, certPEM) + if err != nil { + return fmt.Errorf("failed to load TLS cert: %w", err) + } + + stakingCert := &ids.Certificate{ + Raw: tlsCert.Leaf.Raw, + PublicKey: tlsCert.Leaf.PublicKey, + } + nodeID := ids.NodeIDFromCert(stakingCert) + fmt.Printf(" NodeID: %s\n", nodeID.String()) + + // Create directories + blsDir := filepath.Join(nodeDir, "bls") + ecDir := filepath.Join(nodeDir, "ec") + if err := os.MkdirAll(blsDir, 0700); err != nil { + return fmt.Errorf("failed to create bls directory: %w", err) + } + if err := os.MkdirAll(ecDir, 0700); err != nil { + return fmt.Errorf("failed to create ec directory: %w", err) + } + + // Check/Generate BLS signer key + blsPath := filepath.Join(blsDir, "signer.key") + if _, err := os.Stat(blsPath); os.IsNotExist(err) { + fmt.Printf(" Generating BLS signer key...\n") + blsKey, err := localsigner.New() + if err != nil { + return fmt.Errorf("failed to generate BLS key: %w", err) + } + if err := os.WriteFile(blsPath, blsKey.ToBytes(), 0600); err != nil { + return fmt.Errorf("failed to write BLS key: %w", err) + } + + // Also write public key info + pop, err := signer.NewProofOfPossession(blsKey) + if err != nil { + return fmt.Errorf("failed to generate PoP: %w", err) + } + info := fmt.Sprintf(`{ + "publicKey": "0x%s", + "proofOfPossession": "0x%s" +} +`, hex.EncodeToString(pop.PublicKey[:]), hex.EncodeToString(pop.ProofOfPossession[:])) + if err := os.WriteFile(filepath.Join(blsDir, "info.json"), []byte(info), 0644); err != nil { + return fmt.Errorf("failed to write BLS info: %w", err) + } + fmt.Printf(" BLS public key: 0x%s...\n", hex.EncodeToString(pop.PublicKey[:])[:20]) + } else { + fmt.Printf(" BLS key already exists\n") + } + + // Check/Generate EC private key + ecPath := filepath.Join(ecDir, "private.key") + if _, err := os.Stat(ecPath); os.IsNotExist(err) { + fmt.Printf(" Generating EC private key...\n") + privKey, err := luxcrypto.NewPrivateKey() + if err != nil { + return fmt.Errorf("failed to generate EC key: %w", err) + } + + // Write as hex + if err := os.WriteFile(ecPath, []byte(hex.EncodeToString(privKey.Bytes())), 0600); err != nil { + return fmt.Errorf("failed to write EC key: %w", err) + } + + // Also write address info + pubKey := privKey.PublicKey() + shortID := pubKey.Address() + info := fmt.Sprintf(`{ + "shortID": "%s", + "ethAddress": "0x%s" +} +`, shortID.String(), hex.EncodeToString(shortID[:])) + if err := os.WriteFile(filepath.Join(ecDir, "info.json"), []byte(info), 0644); err != nil { + return fmt.Errorf("failed to write EC info: %w", err) + } + fmt.Printf(" P-Chain addr: %s\n", shortID.String()) + } else { + fmt.Printf(" EC key already exists\n") + } + + return nil +}