mirror of
https://github.com/luxfi/keys.git
synced 2026-07-26 23:58:11 +00:00
initial commit
This commit is contained in:
+299
@@ -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()
|
||||||
|
}
|
||||||
@@ -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 <count> [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 <name>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
showKey(ks, args[1])
|
||||||
|
|
||||||
|
case "allocations", "alloc":
|
||||||
|
if len(args) < 2 {
|
||||||
|
fmt.Println("Usage: keys allocations <network-id> [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 <keys-dir>] <command> [args]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
generate <count> [prefix] Generate new validator keys
|
||||||
|
list List all keys
|
||||||
|
show <name> Show key details
|
||||||
|
allocations <network-id> Generate P-chain allocations JSON
|
||||||
|
upgrade Add BLS/EC keys to existing staking keys
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--dir <path> 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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]
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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=
|
||||||
@@ -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[:])
|
||||||
|
}
|
||||||
+161
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user