Documentation
¶
Overview ¶
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)
Index ¶
- Constants
- Variables
- func HybridBoundDigest(pk *HybridPublicKey, msg []byte) ([]byte, error)
- func HybridPublicKeyBytes(pk *HybridPublicKey) ([]byte, error)
- func HybridVerify(pk *HybridPublicKey, msg []byte, sig *HybridSignature) error
- func LoadMnemonic(ctx context.Context, addr, env, path string) (string, error)
- func LoadMnemonicFromKMS(ctx context.Context, addr, env, path string) (string, error)
- func ServiceChainIDForCluster(clusterSeed string) ids.ID
- func SplitSecretPath(full string) (dir, name string)
- func VerifyServiceEnvelope(pubKey []byte, fullDigest ids.FullDigest, envelope, sig []byte) error
- type Allocation
- type AllocationBuilder
- func (ab *AllocationBuilder) Build() (*GenesisAllocations, error)
- func (ab *AllocationBuilder) WithAmount(amount uint64) *AllocationBuilder
- func (ab *AllocationBuilder) WithFeeAccount(index int, extra uint64) *AllocationBuilder
- func (ab *AllocationBuilder) WithImmediateUnlock() *AllocationBuilder
- func (ab *AllocationBuilder) WithNoVesting() *AllocationBuilder
- func (ab *AllocationBuilder) WithVesting(start uint64, interval uint64, periods int) *AllocationBuilder
- type CChainAlloc
- type GenesisAllocations
- func GenerateAndAllocate(keyStore *KeyStore, networkID uint32, count int, prefix string, ...) (*GenesisAllocations, error)
- func LoadAndAllocate(keyStore *KeyStore, networkID uint32, amountPerKey uint64) (*GenesisAllocations, error)
- func MainnetAllocations(networkID uint32, keys []*ValidatorKey) (*GenesisAllocations, error)
- func QuickAllocations(networkID uint32, keys []*ValidatorKey, amountPerKey uint64) (*GenesisAllocations, error)
- func TestnetAllocations(networkID uint32, keys []*ValidatorKey) (*GenesisAllocations, error)
- type HybridIdentity
- type HybridPrivateKey
- type HybridPublicKey
- type HybridSignature
- type KeyStore
- func (ks *KeyStore) BaseDir() string
- func (ks *KeyStore) GenerateMultiple(count int, prefix string) ([]*ValidatorKey, error)
- func (ks *KeyStore) List() ([]string, error)
- func (ks *KeyStore) Load(name string) (*ValidatorKey, error)
- func (ks *KeyStore) LoadAll() ([]*ValidatorKey, error)
- func (ks *KeyStore) Save(name string, vk *ValidatorKey) error
- type LockedAmount
- type MnemonicReader
- type ServiceIdentity
- type Signer
- type Staker
- type ValidatorKey
Constants ¶
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 )
Unit constants for LUX amounts
const BIP44Purpose = 44
BIP44Purpose is the BIP-44 purpose constant. Pinned to 44 even though service identities don't carry funds — staying inside the BIP-44 purpose tree keeps the same mnemonic interoperable with the Lux wallet's existing derivation tree.
const CoinTypeEVM = 60
CoinTypeEVM is the SLIP-0044 coin_type for EVM chains (60', shared with Ethereum). C-Chain and any non-Lux L1 EVM derive under this tree.
const CoinTypeUTXO = 9000
CoinTypeUTXO is the SLIP-0044 coin_type for the UTXO-layout DAG-like chains — X-Chain (AVM) + P-Chain. Service identities derive under m/44'/9000'/<serviceIndex>'/0'/0' so they share the coin-type tree with X/P staking keys but never collide with account-zero derivations (serviceIndex is hashed from servicePath, never 0 in practice for any well-formed path).
const EnvelopeDomain = "lux-svc-envelope-v1"
EnvelopeDomain is the customisation prefix mixed into every envelope signature so signatures from one envelope shape (a KMS opcode call) cannot be replayed against another (e.g. a future RPC over the same wire). Pinned at v1.
const HybridBoundDigestLen = 48
HybridBoundDigestLen is the byte length of the m_bound digest the scheme uses internally. 48 bytes = SHAKE256-384, the same hash size as ids.FullDigest — matches the Lux convention for identity digests.
const HybridClassicalDomain = "lux-hybrid-classical-secp256k1-v1"
HybridClassicalDomain is the SHAKE256 customisation string for the secp256k1 component's scalar derivation. Pinned at v1.
const HybridClassicalLeafIndex uint32 = 0
HybridClassicalLeafIndex is the BIP-32 leaf index for the secp256k1 (classical) component of a hybrid validator identity. Pinned at 0 so it coincides with the existing ML-DSA-65-only service identity leaf — a hybrid identity is a superset, never a different tree position.
const HybridPQDomain = "lux-hybrid-pq-mldsa65-v1"
HybridPQDomain is the SHAKE256 customisation string for the ML-DSA-65 component's seed derivation. Pinned at v1. Distinct from serviceIdentityDomain so a hybrid identity's PQ key is NEVER the same byte-string as the legacy ML-DSA-only identity at the same path — the two derivations are cryptographically separated.
const HybridPQLeafIndex uint32 = 1
HybridPQLeafIndex is the BIP-32 leaf index for the ML-DSA-65 (PQ) component of a hybrid validator identity. Pinned at 1 so the PQ key derives at a sibling sub-path m/44'/9000'/serviceIndex'/0'/1' — distinct from the classical leaf and from the legacy ML-DSA-only leaf at index 0. The legacy path is preserved exactly; the new PQ leaf is purely additive.
const HybridSigDomain = "lux-hybrid-sig-v1"
HybridSigDomain is the canonical domain-separation string for the Lux hybrid signature scheme. Pinned at v1; bumping invalidates every prior hybrid signature, which is the correct behaviour for a hardfork of the binding encoding. The same string is used both as the H_bind prefix and as the ML-DSA-65 context.
Variables ¶
var ( // ErrHybridNilKey — either component of the hybrid key is nil. // Refused early; we never want a half-hybrid signing path. ErrHybridNilKey = errors.New("keys: hybrid key has nil component") // ErrHybridClassicalSign — secp256k1 signing failed. The error // wraps the underlying secp256k1 error. ErrHybridClassicalSign = errors.New("keys: hybrid classical sign failed") // ErrHybridPQSign — ML-DSA-65 signing failed. The error wraps // the underlying mldsa error. ErrHybridPQSign = errors.New("keys: hybrid PQ sign failed") // ErrHybridClassicalVerify — secp256k1 verification failed. // A valid hybrid signature requires BOTH components to verify. ErrHybridClassicalVerify = errors.New("keys: hybrid classical verification failed") // ErrHybridPQVerify — ML-DSA-65 verification failed. A valid // hybrid signature requires BOTH components to verify. ErrHybridPQVerify = errors.New("keys: hybrid PQ verification failed") // ErrHybridNilSig — signature struct or one of its components is // nil/empty. Refused before any expensive verification. ErrHybridNilSig = errors.New("keys: hybrid signature has nil component") )
Typed errors. errors.Is friendly.
var ErrInvalidServicePath = errors.New("keys: service path is required")
ErrInvalidServicePath is returned when the servicePath argument is empty after trim. Empty paths would collapse every service to the same NodeID, which would silently mask configuration drift.
var ServiceChainID = mustHashChainID("lux-service-identity")
ServiceChainID is the well-known chain identifier under which all service NodeIDs are derived. Distinct from any L1 chain ID so a service NodeID never accidentally validates against a chain's validator-set commitment. Set once and never bumped — the empty "service" string is the canonical seed.
Use the helper ServiceChainIDForCluster if a deployment ever needs per-cluster service NodeID separation. The default (empty seed) is what every Hanzo cluster uses today.
Functions ¶
func HybridBoundDigest ¶ added in v1.1.0
func HybridBoundDigest(pk *HybridPublicKey, msg []byte) ([]byte, error)
HybridBoundDigest is the exported helper computing m_bound. Same algorithm as the internal boundDigest; exported so an out-of-band verifier (e.g. an on-chain precompile or a different language's implementation) can reconstruct the bound message verbatim.
Pure function: no I/O, no randomness.
func HybridPublicKeyBytes ¶ added in v1.1.0
func HybridPublicKeyBytes(pk *HybridPublicKey) ([]byte, error)
HybridPublicKeyBytes returns the canonical wire encoding of the joint public key:
left_encode(8·|pk_classical|) || pk_classical left_encode(8·|pk_pq|) || pk_pq
This is the same framing m_bound uses for the joint pubkey, minus the domain prefix and msg. The encoding is unambiguous and can be reversed (each field's length is recoverable from its left_encode header).
Used to compute a hybrid NodeID via the existing ids.NodeIDScheme derivation — the scheme produces NodeID = SHAKE256-384("NODE_ID_V1" || chainID || scheme_byte || pubkey)[:20], so passing the wire-form hybrid pubkey here yields a NodeID committed to BOTH components.
func HybridVerify ¶ added in v1.1.0
func HybridVerify(pk *HybridPublicKey, msg []byte, sig *HybridSignature) error
HybridVerify checks a BBF-bound joint signature. Returns nil on success; returns a typed error identifying which component failed (or ErrHybridNilKey/ErrHybridNilSig for input errors).
BOTH components MUST verify. This is the AND-mode binding: a signature where only one component verifies is a forgery (or a substitution attempt) and MUST be refused.
The verification is order-independent: classical-first matches the natural reading of the construction, but a verifier could equally check PQ first. Either way, BOTH must pass.
func LoadMnemonic ¶
LoadMnemonic returns the BIP-39 mnemonic for the calling service. MNEMONIC env wins when set; otherwise dials KMS over native ZAP at `addr` and reads the secret at `path` under `env`.
ctx cancellable context
addr KMS host:port (e.g. "liquid-kms.liquidity.svc:9999")
env KMS env scope ("mainnet" | "testnet" | "devnet")
path KMS secret path (e.g. "/mnemonic" or "/foo/master")
Returns the validated BIP-39 phrase. Caller is responsible for keeping it on the goroutine stack and not logging / persisting it.
func LoadMnemonicFromKMS ¶
LoadMnemonicFromKMS is the production-only path (no MNEMONIC env short-circuit). Useful when a caller wants to force the KMS read regardless of ambient env — e.g., a regression test pinning the production code path.
func ServiceChainIDForCluster ¶ added in v1.0.10
ServiceChainIDForCluster is a future hook for per-cluster NodeID separation. Today every Hanzo cluster shares ServiceChainID; if a future deployment ever needs to isolate two clusters' NodeID spaces (e.g. lux-mainnet vs hanzo-prod), the operator can override ServiceChainID at boot via this helper. Pure function — no global state mutated on call.
func SplitSecretPath ¶
SplitSecretPath turns "/foo/bar/baz" into ("/foo/bar/", "baz"). When there is no '/' or only a leading one, the directory is "" and the whole remainder is the name (e.g., "/mnemonic" → ("", "mnemonic")).
Exported so every consumer can address secrets with the same convention — one and only one addressing scheme across mnemonic, staking keys, gas-payer keys, etc.
func VerifyServiceEnvelope ¶ added in v1.0.10
func VerifyServiceEnvelope(pubKey []byte, fullDigest ids.FullDigest, envelope, sig []byte) error
VerifyServiceEnvelope verifies an ML-DSA-65 signature against an envelope produced by Sign. The caller supplies the signer's FullDigest (the 48-byte commitment carried in the envelope header) and public key bytes — both authenticated by the consensus layer.
Pure function: no I/O, no time dependency.
Types ¶
type Allocation ¶
type Allocation struct {
// ETHAddr is the C-chain compatible address (0x...)
ETHAddr string `json:"evmAddr"`
// LUXAddr is the P/X-chain address (P-lux1...)
LUXAddr string `json:"utxoAddr"`
// 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"`
}
Allocation represents a P-chain genesis allocation
type AllocationBuilder ¶
type AllocationBuilder struct {
// contains filtered or unexported fields
}
AllocationBuilder helps build genesis allocations from validator keys
func NewAllocationBuilder ¶
func NewAllocationBuilder(networkID uint32, keys []*ValidatorKey) *AllocationBuilder
NewAllocationBuilder creates a new builder for the given keys
func (*AllocationBuilder) Build ¶
func (ab *AllocationBuilder) Build() (*GenesisAllocations, error)
Build creates the genesis allocations
func (*AllocationBuilder) WithAmount ¶
func (ab *AllocationBuilder) WithAmount(amount uint64) *AllocationBuilder
WithAmount sets the amount per validator
func (*AllocationBuilder) WithFeeAccount ¶
func (ab *AllocationBuilder) WithFeeAccount(index int, extra uint64) *AllocationBuilder
WithFeeAccount sets which validator gets extra funds for fees
func (*AllocationBuilder) WithImmediateUnlock ¶
func (ab *AllocationBuilder) WithImmediateUnlock() *AllocationBuilder
WithImmediateUnlock makes funds immediately unlocked (locktime=0)
func (*AllocationBuilder) WithNoVesting ¶
func (ab *AllocationBuilder) WithNoVesting() *AllocationBuilder
WithNoVesting makes all funds immediately available
func (*AllocationBuilder) WithVesting ¶
func (ab *AllocationBuilder) WithVesting(start uint64, interval uint64, periods int) *AllocationBuilder
WithVesting configures the vesting schedule
type CChainAlloc ¶
type CChainAlloc struct {
Balance string `json:"balance"` // Hex-encoded wei amount
}
CChainAlloc represents a C-chain genesis allocation
type GenesisAllocations ¶
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"`
}
GenesisAllocations contains all allocations for network genesis
func GenerateAndAllocate ¶
func GenerateAndAllocate(keyStore *KeyStore, networkID uint32, count int, prefix string, amountPerKey uint64) (*GenesisAllocations, error)
GenerateAndAllocate generates keys and creates allocations in one step
func LoadAndAllocate ¶
func LoadAndAllocate(keyStore *KeyStore, networkID uint32, amountPerKey uint64) (*GenesisAllocations, error)
LoadAndAllocate loads existing keys and creates allocations
func MainnetAllocations ¶
func MainnetAllocations(networkID uint32, keys []*ValidatorKey) (*GenesisAllocations, error)
MainnetAllocations creates allocations suitable for mainnet (100-year vesting)
func QuickAllocations ¶
func QuickAllocations(networkID uint32, keys []*ValidatorKey, amountPerKey uint64) (*GenesisAllocations, error)
QuickAllocations creates allocations with immediate unlock for testing
func TestnetAllocations ¶
func TestnetAllocations(networkID uint32, keys []*ValidatorKey) (*GenesisAllocations, error)
TestnetAllocations creates allocations suitable for testnet (no vesting)
type HybridIdentity ¶ added in v1.1.0
type HybridIdentity struct {
// ServicePath is the canonical path string (verbatim) used to
// derive both hybrid components. Stored for diagnostics.
ServicePath string
// NodeID is the 20-byte canonical NodeID derived under
// NodeIDSchemeMLDSA65 over the wire-form hybrid public key.
NodeID ids.NodeID
// TypedNodeID is the wire-form NodeID (scheme byte || NodeID).
TypedNodeID ids.TypedNodeID
// FullDigest is the 48-byte SHAKE256-384 commitment to the
// hybrid identity. Bound into envelope signatures.
FullDigest ids.FullDigest
// PublicKey is the joint public key value. Both components are
// always populated.
PublicKey *HybridPublicKey
// PublicKeyBytes is the canonical wire encoding of the joint
// pubkey — useful for logging, storage, and on-chain commitment.
// Same value HybridPublicKeyBytes(PublicKey) returns.
PublicKeyBytes []byte
// contains filtered or unexported fields
}
HybridIdentity binds a mnemonic-derived BBF-bound hybrid signing key (secp256k1 + ML-DSA-65) to its canonical NodeID. The struct owns the joint private key; call Wipe() when done.
The NodeID is committed to the wire-form joint pubkey via the existing ids.NodeIDSchemeMLDSA65 derivation — chosen because the PQ component is the binding primitive (an adversary that holds only the classical key cannot mint a NodeID's matching FullDigest). Per cryptographer review the single-SHAKE-256-384 derivation is sound; no BTC-style double-hash is added.
func DeriveHybridIdentity ¶ added in v1.1.0
func DeriveHybridIdentity(mnemonic, servicePath string) (*HybridIdentity, error)
DeriveHybridIdentity is the canonical constructor for a BBF-bound hybrid validator identity. mnemonic must be a valid BIP-39 phrase; servicePath must be non-empty. Returns the derived HybridIdentity ready to Sign().
Derivation tree:
classical: m/44' / 9000' / serviceIndex' / 0' / 0' (secp256k1) pq: m/44' / 9000' / serviceIndex' / 0' / 1' (ML-DSA-65)
The two leaves share the same hardened branches up to the role node; the only branch that distinguishes them is the leaf index (0 vs 1). Both leaves are hardened. The classical leaf at index 0 COINCIDES with the legacy ML-DSA-only service identity path — this is intentional: a hybrid identity is a superset that adds a PQ component, never a different tree position. The classical leaf is then KDF-mixed with HybridClassicalDomain so the secp256k1 scalar is cryptographically separated from any ML-DSA-only seed at the same BIP-32 leaf — they share the leaf but not the seed.
Pure function: given the same (mnemonic, servicePath) you get the same HybridIdentity, byte-for-byte. No I/O, no randomness, no clock reads.
func (*HybridIdentity) Sign ¶ added in v1.1.0
func (h *HybridIdentity) Sign(msg []byte) (*HybridSignature, error)
Sign produces a BBF-bound joint signature over msg. Delegates to HybridSign — the identity owns the joint private key.
func (*HybridIdentity) Wipe ¶ added in v1.1.0
func (h *HybridIdentity) Wipe()
Wipe zeroes the joint private key in place. Idempotent. Safe to call from a defer on a nil receiver.
type HybridPrivateKey ¶ added in v1.1.0
type HybridPrivateKey struct {
// Classical is the secp256k1 signing key (32 bytes scalar).
Classical *secp.PrivateKey
// PQ is the ML-DSA-65 signing key (FIPS 204).
PQ *mldsa.PrivateKey
}
HybridPrivateKey is the joint signing key of the BBF-bound hybrid scheme. Owns both private components; the only legal use is internal Sign(). Call Wipe() when done.
func (*HybridPrivateKey) Public ¶ added in v1.1.0
func (sk *HybridPrivateKey) Public() *HybridPublicKey
HybridPublic extracts the joint public key from the joint private key. Pure function: no allocation other than the returned struct.
func (*HybridPrivateKey) Wipe ¶ added in v1.1.0
func (sk *HybridPrivateKey) Wipe()
Wipe zeroes both private components in place. Idempotent. Safe to call from a defer on a nil receiver.
type HybridPublicKey ¶ added in v1.1.0
type HybridPublicKey struct {
// Classical is the secp256k1 verification key. 33-byte compressed
// SEC1 encoding on the wire. This is the same primitive the
// existing Lux validator set uses for P/X identity.
Classical *secp.PublicKey
// PQ is the ML-DSA-65 verification key (FIPS 204). Bytes-on-the-
// wire form is the standard ML-DSA-65 public key encoding.
PQ *mldsa.PublicKey
}
HybridPublicKey is the joint pubkey of the BBF-bound hybrid scheme. Both components are required for any verification; a half-hybrid is a programmer error.
type HybridSignature ¶ added in v1.1.0
type HybridSignature struct {
// Classical is the secp256k1 recoverable signature (65 bytes).
Classical []byte
// PQ is the ML-DSA-65 signature (FIPS 204 standard size).
PQ []byte
}
HybridSignature is the joint signature of the BBF-bound hybrid scheme. BOTH components are required for verification. Neither is independently useful — that is the binding the scheme provides.
func HybridSign ¶ added in v1.1.0
func HybridSign(sk *HybridPrivateKey, msg []byte, randSource io.Reader) (*HybridSignature, error)
HybridSign produces a BBF-bound joint signature over msg under the given joint signing key. The classical signature uses secp256k1 SignHash over m_bound; the PQ signature uses ML-DSA-65 SignCtx with HybridSigDomain as the context.
Why two domain-separated bindings:
(1) m_bound's prefix binds the SHAKE256 input to this scheme.
(2) The ML-DSA context binds the FIPS 204 signature itself to
this scheme — preventing cross-protocol replay of a future
hybrid signature against any other ML-DSA-65 surface (KMS
envelope, P-Chain block, etc.).
rand is the randomness source for ML-DSA-65 hedged signing. If nil, crypto/rand is used. The secp256k1 signature uses RFC 6979 deterministic-k — no randomness needed.
type KeyStore ¶
type KeyStore struct {
// contains filtered or unexported fields
}
KeyStore manages validator keys with filesystem persistence
func NewKeyStore ¶
NewKeyStore creates a new key store at the given directory
func (*KeyStore) GenerateMultiple ¶
func (ks *KeyStore) GenerateMultiple(count int, prefix string) ([]*ValidatorKey, error)
GenerateMultiple generates multiple validator keys
func (*KeyStore) Load ¶
func (ks *KeyStore) Load(name string) (*ValidatorKey, error)
Load reads a validator key from the filesystem
func (*KeyStore) LoadAll ¶
func (ks *KeyStore) LoadAll() ([]*ValidatorKey, error)
LoadAll loads all validator keys from the store
type LockedAmount ¶
LockedAmount represents a locked amount with unlock time
type MnemonicReader ¶
type MnemonicReader interface {
GetAt(ctx context.Context, path, name, env string) (string, error)
Close()
}
MnemonicReader is the minimum surface LoadMnemonicFromKMS needs from a KMS client. The real *zapclient.Client satisfies it; tests inject fakes via the dialKMS seam below.
type ServiceIdentity ¶ added in v1.0.10
type ServiceIdentity struct {
// ServicePath is the canonical path string (verbatim) used to derive
// the BIP-32 hardened index. Stored for diagnostics; the
// authoritative input is the derived NodeID.
ServicePath string
// NodeID is the 20-byte canonical NodeID derived under
// NodeIDSchemeMLDSA65. Map-key safe.
NodeID ids.NodeID
// TypedNodeID is the wire-form NodeID (scheme byte || NodeID).
// Travels in envelope headers so the receiver knows which verifier
// to dispatch.
TypedNodeID ids.TypedNodeID
// FullDigest is the 48-byte SHAKE256-384 commitment to the
// identity. Bound into envelope signatures to prevent cross-scheme
// confusion attacks.
FullDigest ids.FullDigest
// PublicKey is the ML-DSA-65 public key bytes.
PublicKey []byte
// contains filtered or unexported fields
}
ServiceIdentity binds a mnemonic-derived ML-DSA-65 signing key to its canonical NodeID. The struct owns the private key bytes; call Wipe() when done.
Safe for concurrent use after construction — every field is read-only after NewServiceIdentity returns. Wipe is the only mutating method and the caller serialises it (typically a single defer).
func NewServiceIdentity ¶ added in v1.0.10
func NewServiceIdentity(mnemonic, servicePath string) (*ServiceIdentity, error)
NewServiceIdentity is the canonical constructor. mnemonic must be a valid BIP-39 phrase; servicePath must be non-empty. Returns the derived ServiceIdentity ready to Sign().
Pure function: given the same (mnemonic, servicePath) you get the same NodeID, byte-for-byte. No I/O, no randomness, no clock reads.
func (*ServiceIdentity) Sign ¶ added in v1.0.10
func (s *ServiceIdentity) Sign(envelope []byte) ([]byte, error)
Sign produces a deterministic ML-DSA-65 signature over the envelope digest. The caller is responsible for serialising the envelope into canonical bytes before calling Sign — see SignEnvelope for the canonical (method, path, payload, timestamp, nonce) shape.
The signed bytes are the SHAKE256 digest of:
left_encode(|domain|·8) || EnvelopeDomain || left_encode(|full_digest|·8) || FullDigest || left_encode(|envelope|·8) || envelope
Binding the FullDigest into the prehash means a verifier always rejects an envelope signed by a different identity — even a key with the same NodeID prefix.
func (*ServiceIdentity) Wipe ¶ added in v1.0.10
func (s *ServiceIdentity) Wipe()
Wipe zeroes the private key in place. Idempotent. Safe to call from a defer.
type Signer ¶
type Signer struct {
PublicKey string `json:"publicKey"`
ProofOfPossession string `json:"proofOfPossession"`
}
Signer contains BLS key information for a validator
type Staker ¶
type Staker struct {
NodeID string `json:"nodeID"`
RewardAddress string `json:"rewardAddress"`
DelegationFee uint32 `json:"delegationFee"`
Signer *Signer `json:"signer,omitempty"`
}
Staker represents an initial validator in genesis
type ValidatorKey ¶
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)
}
ValidatorKey contains all keys needed for a validator node
func DeriveValidatorFromMnemonic ¶
func DeriveValidatorFromMnemonic(mnemonic string, accountIndex uint32) (*ValidatorKey, error)
DeriveValidatorFromMnemonic derives a single validator key from mnemonic at given index. All keys (EC, TLS, BLS) are now derived deterministically from the mnemonic.
func DeriveValidatorsFromMnemonic ¶
func DeriveValidatorsFromMnemonic(mnemonic string, count int) ([]*ValidatorKey, error)
DeriveValidatorsFromMnemonic derives N validator keys from a BIP39 mnemonic. Each validator uses BIP44 path m/44'/60'/0'/0/{index} for the EC key. TLS staking certs and BLS keys are generated fresh (not deterministic from mnemonic). This is designed for runtime use - no files are written to disk.
func GenerateValidatorKey ¶
func GenerateValidatorKey() (*ValidatorKey, error)
GenerateValidatorKey creates a complete set of validator keys
func LoadFromDir ¶
func LoadFromDir(nodeDir string) (*ValidatorKey, error)
LoadFromDir loads a validator key from a specific directory
func (*ValidatorKey) BLSKeyBase64 ¶
func (vk *ValidatorKey) BLSKeyBase64() string
BLSKeyBase64 returns the BLS secret key as base64 (for node config)
func (*ValidatorKey) BLSPoPHex ¶
func (vk *ValidatorKey) BLSPoPHex() string
BLSPoPHex returns the BLS proof of possession as hex with 0x prefix
func (*ValidatorKey) BLSPublicKeyHex ¶
func (vk *ValidatorKey) BLSPublicKeyHex() string
BLSPublicKeyHex returns the BLS public key as hex with 0x prefix
func (*ValidatorKey) CChainAddrHex ¶
func (vk *ValidatorKey) CChainAddrHex() string
CChainAddrHex returns the C-chain address as hex with 0x prefix