mirror of
https://github.com/luxfi/genesis.git
synced 2026-10-30 16:25:45 +00:00
462 lines
19 KiB
Go
462 lines
19 KiB
Go
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Command venuekeygen generates the D-Chain VENUE validator staking key set
|
|
// for one network and emits it either as a KMS-ready JSON payload (default,
|
|
// stdout) or as individual on-disk artifacts under --out (local inspection
|
|
// only).
|
|
//
|
|
// The venue is a SINGLE-OPERATOR D-Chain node — exactly ONE staking identity
|
|
// per network, DISTINCT from the 5 public luxd validators. A staking identity
|
|
// here is three keys, all generated with the node's own audited primitives
|
|
// (this tool rolls NO crypto of its own):
|
|
//
|
|
// - TLS staking cert+key (staker.crt / staker.key): P-256 ECDSA self-signed
|
|
// cert, via github.com/luxfi/node/staking.NewCertAndKeyBytes. PEM block
|
|
// types "CERTIFICATE" / "PRIVATE KEY" (PKCS#8). This is what luxd loads
|
|
// from --staking-tls-cert-file / --staking-tls-key-file.
|
|
//
|
|
// - BLS signer key (signer.key): a fresh localsigner secret key, serialized
|
|
// with LocalSigner.ToBytes() (raw big-endian secret-key bytes). luxd's
|
|
// getStakingSigner reads the FILE form as RAW bytes and the KMS form as a
|
|
// HEX string, both fed to localsigner.FromBytes. So:
|
|
// - the --out signer.key file is written as RAW bytes (file-loader shape)
|
|
// - the stdout JSON `signer_key` field is HEX (KMS-loader shape)
|
|
// Both round-trip through the same localsigner.FromBytes.
|
|
//
|
|
// - PQ ML-DSA-65 key (mldsa.key / mldsa.pub): NIST Level 3 strict-PQ
|
|
// identity, via staking.NewPQKeyPair, PEM block types
|
|
// "ML-DSA-65 PRIVATE KEY" / "ML-DSA-65 PUBLIC KEY" over
|
|
// PrivateKeyBytes()/PublicKeyBytes() — replicating staking.InitNodePQKeyPair
|
|
// byte-for-byte so staking.LoadPQKeyPair consumes them.
|
|
//
|
|
// The venue's primary NodeID (node_id / --node-id) is the STRICT-PQ NodeID:
|
|
// the venue boots under the PQ securityProfile (devnet profileID=2,
|
|
// testnet/mainnet=1; the operator manifests pass --staking-mldsa-key-file +
|
|
// --staking-mldsa-pub-key-file), so luxd derives the primary NodeID from the
|
|
// ML-DSA-65 pubkey, NOT the TLS cert, via
|
|
// ids.NodeIDSchemeMLDSA65.DeriveMLDSA(ids.Empty, mldsaPubRaw) — the same seam
|
|
// node/node.go:143 → config/node StakingConfig.DeriveNodeID(ids.Empty) runs at
|
|
// boot. THAT NodeID is what goes in the venue's bootstrapper config. The legacy
|
|
// cert-derived value (ids.NodeIDFromCert) is still emitted as node_id_classical
|
|
// for non-PQ / classical-compat profiles, so nothing is lost.
|
|
//
|
|
// Every invocation generates FRESH random keys (crypto/rand via the node
|
|
// primitives). Before any output, the tool round-trips each generated key
|
|
// back through the node's own loaders and fails hard if any does not parse —
|
|
// proving the artifact is consumable by a real luxd.
|
|
//
|
|
// Build:
|
|
//
|
|
// cd ~/work/lux/genesis && GOWORK=off go build -o /tmp/venuekeygen ./cmd/venuekeygen/
|
|
//
|
|
// Usage:
|
|
//
|
|
// venuekeygen --net <mainnet|testnet|devnet|localnet> [--out <dir>] [--node-id]
|
|
//
|
|
// # Default: print the KMS-ready JSON payload to stdout, kms-put commands to stderr
|
|
// venuekeygen --net devnet --node-id
|
|
//
|
|
// # Also write local PEM/raw artifacts for inspection (NOT for git)
|
|
// venuekeygen --net devnet --out /tmp/venue-devnet
|
|
//
|
|
// The stdout JSON object embeds the node's KMSStakingKeys shape
|
|
// (tls_key / tls_cert / signer_key) PLUS the ML-DSA PEMs and the derived
|
|
// NodeID, so the whole object is the exact value an operator stores under the
|
|
// venue secret. The per-artifact `kms put` commands printed to stderr store
|
|
// each individual key under org=lux, path <net>/dchain-venue/<artifact>.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/luxfi/crypto/bls"
|
|
"github.com/luxfi/crypto/bls/signer/localsigner"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/node/staking"
|
|
)
|
|
|
|
// PEM block types for the ML-DSA-65 strict-PQ identity. These MUST match
|
|
// staking.InitNodePQKeyPair / staking.LoadPQKeyPair exactly, or luxd will
|
|
// refuse the artifact at config-load. Mirrored here (not imported) because
|
|
// the node package does not export them, and we replicate the exact encoding.
|
|
const (
|
|
mldsaPrivPEMType = "ML-DSA-65 PRIVATE KEY"
|
|
mldsaPubPEMType = "ML-DSA-65 PUBLIC KEY"
|
|
)
|
|
|
|
// venueSecretBase is the per-artifact KMS path segment under which the venue
|
|
// stores its keys. The full path is "<net>/dchain-venue/<artifact>" — the
|
|
// network is the leading segment because the kms CLI has no --env flag (env
|
|
// is encoded in the path); org=lux selects the Lux KMS tenant via --org.
|
|
const venueSecretBase = "dchain-venue"
|
|
|
|
// venueArtifacts are the on-disk filenames written under --out and the
|
|
// trailing KMS key names, in stable order.
|
|
var venueArtifacts = []string{
|
|
"staker.crt",
|
|
"staker.key",
|
|
"signer.key",
|
|
"mldsa.key",
|
|
"mldsa.pub",
|
|
}
|
|
|
|
// payload is the stdout JSON document. It embeds the node's KMSStakingKeys
|
|
// json shape verbatim (tls_key / tls_cert / signer_key) so the node's KMS
|
|
// loader (staking.FetchFromKMS → json.Unmarshal into KMSStakingKeys) can
|
|
// consume the same bytes, and adds the strict-PQ ML-DSA material plus the
|
|
// derived classical NodeID.
|
|
//
|
|
// signer_key is HEX of LocalSigner.ToBytes() — the exact form
|
|
// getStakingSigner expects on the KMS path (hex.DecodeString →
|
|
// localsigner.FromBytes).
|
|
type payload struct {
|
|
staking.KMSStakingKeys // tls_key, tls_cert, signer_key (json:"...")
|
|
MLDSAKey string `json:"mldsa_key"` // PEM "ML-DSA-65 PRIVATE KEY"
|
|
MLDSAPub string `json:"mldsa_pub"` // PEM "ML-DSA-65 PUBLIC KEY"
|
|
// NodeID is the strict-PQ ML-DSA-65 NodeID
|
|
// (ids.NodeIDSchemeMLDSA65.DeriveMLDSA(ids.Empty, mldsaPubRaw)) — the value
|
|
// luxd computes for this venue under the PQ securityProfile. This is the
|
|
// NodeID that belongs in the venue's bootstrapper config.
|
|
NodeID string `json:"node_id"`
|
|
// NodeIDClassical is the legacy cert-derived NodeID (ids.NodeIDFromCert),
|
|
// retained for non-PQ / classical-compat profiles. NOT used by this venue.
|
|
NodeIDClassical string `json:"node_id_classical"`
|
|
}
|
|
|
|
// keyset holds the raw generated artifact bytes for one venue identity,
|
|
// before they are shaped into JSON or written to disk.
|
|
type keyset struct {
|
|
stakerCertPEM []byte // PEM "CERTIFICATE"
|
|
stakerKeyPEM []byte // PEM "PRIVATE KEY" (PKCS#8 P-256)
|
|
signerKeyRaw []byte // raw LocalSigner.ToBytes() (file form)
|
|
mldsaKeyPEM []byte // PEM "ML-DSA-65 PRIVATE KEY"
|
|
mldsaPubPEM []byte // PEM "ML-DSA-65 PUBLIC KEY"
|
|
mldsaPubRaw []byte // raw ML-DSA-65 pubkey bytes (the strict-PQ NodeID seed)
|
|
// nodeID is the venue's REAL primary NodeID: under the strict-PQ
|
|
// securityProfile (all four Lux networks; the operator manifests pass
|
|
// --staking-mldsa-key-file/--staking-mldsa-pub-key-file), luxd derives the
|
|
// primary NodeID from the ML-DSA-65 pubkey, NOT the TLS cert. See
|
|
// nodeIDStrictPQ for the exact derivation + node-source citation.
|
|
nodeID ids.NodeID
|
|
// nodeIDClassical is the legacy cert-derived NodeID (ids.NodeIDFromCert),
|
|
// kept for non-PQ profiles / classical-compat. It is NOT the value luxd
|
|
// uses for this venue.
|
|
nodeIDClassical ids.NodeID
|
|
blsPubHex string // compressed BLS public key, hex (logged only)
|
|
}
|
|
|
|
func main() {
|
|
net := flag.String("net", "", "network: mainnet|testnet|devnet|localnet (required)")
|
|
out := flag.String("out", "", "if set, ALSO write staker.crt/staker.key/signer.key/mldsa.key/mldsa.pub into this dir (0600, local inspection only — NOT for git)")
|
|
nodeID := flag.Bool("node-id", false, "also print the venue's strict-PQ ML-DSA-65 NodeID in bare bootstrap form")
|
|
flag.Parse()
|
|
|
|
netSlug, err := normalizeNet(*net)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "venuekeygen: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
ks, err := generate()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "venuekeygen: generate: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Self-check: prove every artifact round-trips through the node's own
|
|
// loaders before we emit anything. A failure here means the artifact
|
|
// would not be consumable by a real luxd, so we must not ship it.
|
|
if err := selfCheck(ks); err != nil {
|
|
fmt.Fprintf(os.Stderr, "venuekeygen: self-check failed (artifact not luxd-consumable): %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if *out != "" {
|
|
if err := writeArtifacts(*out, ks); err != nil {
|
|
fmt.Fprintf(os.Stderr, "venuekeygen: write artifacts: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Fprintf(os.Stderr, "venuekeygen: wrote local artifacts to %s (0600) — these are SECRETS, do NOT commit / gitignore this dir\n", *out)
|
|
}
|
|
|
|
doc := buildPayload(ks)
|
|
body, err := json.MarshalIndent(doc, "", " ")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "venuekeygen: marshal payload: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
// stdout stays clean JSON — the exact value to store in KMS.
|
|
fmt.Println(string(body))
|
|
|
|
// Everything operator-facing goes to stderr so stdout is pipeable JSON.
|
|
fmt.Fprintf(os.Stderr, "\nvenuekeygen: D-Chain venue staking identity for net=%s\n", netSlug)
|
|
fmt.Fprintf(os.Stderr, "venuekeygen: NodeID=%s\n", ks.nodeID)
|
|
fmt.Fprintf(os.Stderr, "venuekeygen: node_id is the strict-PQ ML-DSA-65 NodeID (DeriveMLDSA(ids.Empty,pub)) — the value luxd computes under the PQ securityProfile; node_id_classical=%s is the legacy cert-derived value for non-PQ profiles.\n", ks.nodeIDClassical)
|
|
fmt.Fprintf(os.Stderr, "venuekeygen: BLS pubkey (compressed, hex)=%s\n", ks.blsPubHex)
|
|
if *nodeID {
|
|
// Also print the NodeID to stdout-adjacent stderr in the bare form a
|
|
// bootstrapper config wants. (stdout itself carries it in JSON.) This is
|
|
// the strict-PQ NodeID — the one that matters because the venue runs
|
|
// under the PQ securityProfile.
|
|
fmt.Fprintf(os.Stderr, "venuekeygen: bootstrap NodeID -> %s\n", ks.nodeID)
|
|
}
|
|
printKMSCommands(netSlug)
|
|
}
|
|
|
|
// generate produces one fresh venue staking identity using only the node's
|
|
// audited primitives. crypto/rand is the entropy source inside each.
|
|
func generate() (*keyset, error) {
|
|
// TLS staking cert + key (P-256 ECDSA), node's canonical generator.
|
|
certPEM, keyPEM, err := staking.NewCertAndKeyBytes()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("new TLS cert/key: %w", err)
|
|
}
|
|
|
|
// BLS signer key — node's localsigner. ToBytes() is the on-disk file form.
|
|
signer, err := localsigner.New()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("new BLS signer: %w", err)
|
|
}
|
|
signerRaw := signer.ToBytes()
|
|
blsPubHex := hex.EncodeToString(bls.PublicKeyToCompressedBytes(signer.PublicKey()))
|
|
|
|
// PQ ML-DSA-65 key pair — node's strict-PQ generator.
|
|
pq, err := staking.NewPQKeyPair()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("new ML-DSA-65 key pair: %w", err)
|
|
}
|
|
mldsaPubRaw := pq.PublicKeyBytes()
|
|
mldsaKeyPEM, err := encodePEM(mldsaPrivPEMType, pq.PrivateKeyBytes())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("encode ML-DSA private PEM: %w", err)
|
|
}
|
|
mldsaPubPEM, err := encodePEM(mldsaPubPEMType, mldsaPubRaw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("encode ML-DSA public PEM: %w", err)
|
|
}
|
|
|
|
// Strict-PQ NodeID — the venue's REAL primary NodeID. Derived from the
|
|
// SAME raw ML-DSA-65 pubkey bytes that are PEM-encoded into mldsa.pub.
|
|
pqNID, err := nodeIDStrictPQ(mldsaPubRaw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("derive strict-PQ NodeID: %w", err)
|
|
}
|
|
|
|
// Classical cert-derived NodeID — kept for non-PQ / classical-compat
|
|
// profiles only. Not the value luxd uses for this venue.
|
|
classicalNID, err := nodeIDFromCertPEM(certPEM, keyPEM)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("derive classical NodeID: %w", err)
|
|
}
|
|
|
|
return &keyset{
|
|
stakerCertPEM: certPEM,
|
|
stakerKeyPEM: keyPEM,
|
|
signerKeyRaw: signerRaw,
|
|
mldsaKeyPEM: mldsaKeyPEM,
|
|
mldsaPubPEM: mldsaPubPEM,
|
|
mldsaPubRaw: mldsaPubRaw,
|
|
nodeID: pqNID,
|
|
nodeIDClassical: classicalNID,
|
|
blsPubHex: blsPubHex,
|
|
}, nil
|
|
}
|
|
|
|
// nodeIDStrictPQ derives the venue's primary NodeID the way luxd does under the
|
|
// strict-PQ securityProfile: from the raw ML-DSA-65 pubkey bytes, bound to the
|
|
// primary-network chain id (ids.Empty).
|
|
//
|
|
// This is byte-identical to what the node computes at boot. The single source
|
|
// of truth is the node's StakingConfig.DeriveNodeID seam:
|
|
//
|
|
// config/node/config.go:298-301 (strict-PQ branch, when StakingMLDSAPub set):
|
|
// id, _, _ := ids.NodeIDSchemeMLDSA65.DeriveMLDSA(chainID, c.StakingMLDSAPub)
|
|
// node/node.go:143 (boot call site):
|
|
// derivedNodeID, _ := config.StakingConfig.DeriveNodeID(ids.Empty)
|
|
//
|
|
// We call DeriveMLDSA directly (rather than constructing a config/node
|
|
// StakingConfig) because that package pulls the full node-config dependency
|
|
// surface (chains/network/server) — heavyweight and unnecessary for one
|
|
// derivation. The pubKey argument is the RAW ML-DSA pubkey bytes
|
|
// (PublicKeyBytes()); DeriveMLDSA applies its own SP 800-185 framing, so it
|
|
// must NOT be PEM-wrapped.
|
|
func nodeIDStrictPQ(mldsaPubRaw []byte) (ids.NodeID, error) {
|
|
id, _, err := ids.NodeIDSchemeMLDSA65.DeriveMLDSA(ids.Empty, mldsaPubRaw)
|
|
return id, err
|
|
}
|
|
|
|
// selfCheck loads every generated artifact back through the node's own
|
|
// loaders and asserts the NodeID is reproducible. Any failure means the
|
|
// artifact is not luxd-consumable.
|
|
func selfCheck(ks *keyset) error {
|
|
// Strict-PQ NodeID — the venue's REAL primary NodeID. Must be non-empty and
|
|
// STABLE: re-deriving from the same ML-DSA pubkey bytes yields the same id.
|
|
if ks.nodeID == ids.EmptyNodeID {
|
|
return errors.New("strict-PQ NodeID is empty")
|
|
}
|
|
rePQ, err := nodeIDStrictPQ(ks.mldsaPubRaw)
|
|
if err != nil {
|
|
return fmt.Errorf("strict-PQ NodeID re-derive: %w", err)
|
|
}
|
|
if rePQ != ks.nodeID {
|
|
return fmt.Errorf("strict-PQ NodeID not reproducible: %s != %s", rePQ, ks.nodeID)
|
|
}
|
|
|
|
// TLS: parse via the node loader and re-derive the classical NodeID.
|
|
tlsCert, err := staking.LoadTLSCertFromBytes(ks.stakerKeyPEM, ks.stakerCertPEM)
|
|
if err != nil {
|
|
return fmt.Errorf("TLS round-trip: %w", err)
|
|
}
|
|
if tlsCert.Leaf == nil {
|
|
return errors.New("TLS round-trip: nil leaf")
|
|
}
|
|
reClassical := ids.NodeIDFromCert(&ids.Certificate{
|
|
Raw: tlsCert.Leaf.Raw,
|
|
PublicKey: tlsCert.Leaf.PublicKey,
|
|
})
|
|
if reClassical != ks.nodeIDClassical {
|
|
return fmt.Errorf("classical NodeID not reproducible: %s != %s", reClassical, ks.nodeIDClassical)
|
|
}
|
|
|
|
// The two derivations MUST differ: the strict-PQ NodeID comes from the
|
|
// ML-DSA pubkey, the classical one from the TLS cert. If they collide,
|
|
// something is wired wrong (e.g. the PQ branch fell back to the cert).
|
|
if ks.nodeID == ks.nodeIDClassical {
|
|
return errors.New("strict-PQ NodeID equals classical NodeID (PQ derivation not applied)")
|
|
}
|
|
|
|
// BLS: parse the raw file form back through localsigner.
|
|
if _, err := localsigner.FromBytes(ks.signerKeyRaw); err != nil {
|
|
return fmt.Errorf("BLS signer round-trip (file form): %w", err)
|
|
}
|
|
// BLS: parse the hex KMS form back through localsigner (same code path the
|
|
// node uses on the KMS branch).
|
|
rawFromHex, err := hex.DecodeString(hex.EncodeToString(ks.signerKeyRaw))
|
|
if err != nil {
|
|
return fmt.Errorf("BLS signer hex decode: %w", err)
|
|
}
|
|
if !bytes.Equal(rawFromHex, ks.signerKeyRaw) {
|
|
return errors.New("BLS signer hex/raw mismatch")
|
|
}
|
|
if _, err := localsigner.FromBytes(rawFromHex); err != nil {
|
|
return fmt.Errorf("BLS signer round-trip (KMS hex form): %w", err)
|
|
}
|
|
|
|
// ML-DSA: load both PEMs back through the node loader via temp files
|
|
// (LoadPQKeyPair is the file-based API the node uses).
|
|
tmp, err := os.MkdirTemp("", "venuekeygen-selfcheck-")
|
|
if err != nil {
|
|
return fmt.Errorf("temp dir: %w", err)
|
|
}
|
|
defer os.RemoveAll(tmp)
|
|
keyPath := filepath.Join(tmp, "mldsa.key")
|
|
pubPath := filepath.Join(tmp, "mldsa.pub")
|
|
if err := os.WriteFile(keyPath, ks.mldsaKeyPEM, 0o600); err != nil {
|
|
return fmt.Errorf("write temp ML-DSA key: %w", err)
|
|
}
|
|
if err := os.WriteFile(pubPath, ks.mldsaPubPEM, 0o600); err != nil {
|
|
return fmt.Errorf("write temp ML-DSA pub: %w", err)
|
|
}
|
|
if _, err := staking.LoadPQKeyPair(keyPath, pubPath); err != nil {
|
|
return fmt.Errorf("ML-DSA round-trip: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// buildPayload shapes a verified keyset into the stdout JSON document.
|
|
func buildPayload(ks *keyset) payload {
|
|
return payload{
|
|
KMSStakingKeys: staking.KMSStakingKeys{
|
|
TLSKey: string(ks.stakerKeyPEM),
|
|
TLSCert: string(ks.stakerCertPEM),
|
|
SignerKey: hex.EncodeToString(ks.signerKeyRaw), // KMS hex form
|
|
},
|
|
MLDSAKey: string(ks.mldsaKeyPEM),
|
|
MLDSAPub: string(ks.mldsaPubPEM),
|
|
NodeID: ks.nodeID.String(), // strict-PQ ML-DSA-65 NodeID
|
|
NodeIDClassical: ks.nodeIDClassical.String(), // legacy cert-derived NodeID
|
|
}
|
|
}
|
|
|
|
// writeArtifacts writes the five on-disk artifacts under dir with 0600 perms.
|
|
// signer.key is RAW bytes (the file-loader shape getStakingSigner reads); the
|
|
// rest are PEM. These are SECRETS for local inspection only.
|
|
func writeArtifacts(dir string, ks *keyset) error {
|
|
if err := os.MkdirAll(dir, 0o700); err != nil {
|
|
return fmt.Errorf("mkdir %s: %w", dir, err)
|
|
}
|
|
files := map[string][]byte{
|
|
"staker.crt": ks.stakerCertPEM,
|
|
"staker.key": ks.stakerKeyPEM,
|
|
"signer.key": ks.signerKeyRaw, // RAW, matches node file loader
|
|
"mldsa.key": ks.mldsaKeyPEM,
|
|
"mldsa.pub": ks.mldsaPubPEM,
|
|
}
|
|
for name, b := range files {
|
|
p := filepath.Join(dir, name)
|
|
if err := os.WriteFile(p, b, 0o600); err != nil {
|
|
return fmt.Errorf("write %s: %w", p, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// printKMSCommands prints, to stderr, the exact `kms put` invocations the
|
|
// operator runs to store each artifact. The kms CLI has no --env flag, so the
|
|
// network is the leading path segment; --org lux selects the Lux KMS tenant.
|
|
// Operators pipe each artifact into --stdin to keep secret material off argv.
|
|
func printKMSCommands(netSlug string) {
|
|
fmt.Fprintf(os.Stderr, "\nvenuekeygen: store each artifact in Lux KMS (org=lux), e.g. from an --out dir:\n")
|
|
for _, art := range venueArtifacts {
|
|
path := fmt.Sprintf("%s/%s/%s", netSlug, venueSecretBase, art)
|
|
fmt.Fprintf(os.Stderr, " kms put %s --stdin --org lux < %s\n", path, art)
|
|
}
|
|
fmt.Fprintf(os.Stderr, "venuekeygen: (note: the kms CLI has no --env flag; net=%s is the leading path segment)\n", netSlug)
|
|
}
|
|
|
|
// normalizeNet validates and canonicalizes the --net value.
|
|
func normalizeNet(net string) (string, error) {
|
|
switch net {
|
|
case "mainnet", "testnet", "devnet", "localnet":
|
|
return net, nil
|
|
case "":
|
|
return "", errors.New("--net is required (mainnet|testnet|devnet|localnet)")
|
|
default:
|
|
return "", fmt.Errorf("unknown --net %q (expected mainnet|testnet|devnet|localnet)", net)
|
|
}
|
|
}
|
|
|
|
// encodePEM wraps body in a PEM block of the given type.
|
|
func encodePEM(blockType string, body []byte) ([]byte, error) {
|
|
var buf bytes.Buffer
|
|
if err := pem.Encode(&buf, &pem.Block{Type: blockType, Bytes: body}); err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// nodeIDFromCertPEM parses a staking cert+key PEM pair via the node's TLS
|
|
// loader and derives the classical NodeID the same way luxd does.
|
|
func nodeIDFromCertPEM(certPEM, keyPEM []byte) (ids.NodeID, error) {
|
|
cert, err := staking.LoadTLSCertFromBytes(keyPEM, certPEM)
|
|
if err != nil {
|
|
return ids.EmptyNodeID, err
|
|
}
|
|
if cert.Leaf == nil {
|
|
return ids.EmptyNodeID, errors.New("nil cert leaf")
|
|
}
|
|
return ids.NodeIDFromCert(&ids.Certificate{
|
|
Raw: cert.Leaf.Raw,
|
|
PublicKey: cert.Leaf.PublicKey,
|
|
}), nil
|
|
}
|