44 Commits
Author SHA1 Message Date
zeekay 4e13893d5a chore: sync working tree
Commits 4 outstanding change(s) that were sitting uncommitted.
No build artifacts and no secrets in the changeset (both checked).
2026-07-26 10:13:27 -07:00
zeekayandHanzo Dev 2466a1a1fc genesis: header said KMS_WORKSPACE_ID, code reads KMS_ORG
The KMS fetch was migrated to the canonical /v1/kms/orgs/{org}/secrets path, but
the file header and the usage example were left describing the Infisical v3 raw
API and telling operators to export KMS_WORKSPACE_ID — a variable this program
does not read. Following the header got you a tool that could not resolve the
org and failed with no obvious reason.

Both now match resolveMnemonic: KMS_ENDPOINT + KMS_TOKEN + KMS_ORG, fetching
GET ${KMS_ENDPOINT}/v1/kms/orgs/<org>/secrets/lux/<env>/staking/mnemonic.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-26 08:13:48 -07:00
zeekayandHanzo Dev 81209d2fda createchaintx: read the mnemonic from luxfi/kms, not Infisical
fetchKMSMnemonic called Infisical v3 raw secrets:

  GET ${KMS_ENDPOINT}/api/v3/secrets/raw?workspaceId=..&environment=prod&secretPath=..

That endpoint is not served by kms.lux.network or kms.hanzo.ai. Both answer it
with the console SPA HTML, so the JSON decode failed and no mnemonic was ever
retrievable this way — while the function, its env vars and its log line all
called it KMS.

Now it speaks the one documented HTTP surface, which luxfi/kms describes as the
only way to read a secret:

  GET ${KMS_ENDPOINT}/v1/kms/orgs/<org>/secrets/lux/<env>/staking/mnemonic
  Authorization: Bearer ${KMS_TOKEN}   (IAM-signed, resolved to <org>)
  -> {"secret": {"value": "..."}}

The server splits the path at its last slash into (path, name), so the secret
path still disambiguates the luxd env. ?env= is the KMS environment slug and
defaults to "default", matching the luxfi/kms client KMS_ENV.

KMS_WORKSPACE_ID is gone — an Infisical concept with no meaning here. Scope is
the org in the path: KMS_ORG, defaulting to lux. No compat shim; the old call
never worked.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-25 16:38:38 -07:00
zeekayandHanzo Dev 6a1f34e6e1 infra: s3.lux.network → s3.lux.cloud (correct snapshot endpoint for RLP re-import)
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-02 12:45:29 -07:00
zeekayandHanzo Dev 13cf8b0d21 chore: migrate luxd HTTP routes /ext -> /v1
Drop the Avalanche-heritage /ext prefix; /v1 is the single canonical route
surface (one way, no backward compat). The node's baseURL is the source of
truth; clients, SDKs, CLI, indexer, maker, genesis, netrunner, and the
k8s/compose/gateway/explorer configs are updated to match.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-01 11:40:25 -07:00
zeekayandHanzo Dev fdc2e24299 createchaintx: --brands subset filter (process a named subset of brand L2s)
--brands=hanzo,zoo issues CreateChainTx only for the named brands, in
canonical list order (deterministic chain ordering). Unknown brand names
are a hard error (a typo must not silently create a different chain set).
Pure stdlib; zero new deps. Default (empty) = all five brands as before.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-06-30 16:06:42 -07:00
zeekay 464d35f07d genesis: retire brand-L2-in-luxd default in bootstrap-chain
bootstrap-chain defaulted --track-chain-ids=hanzo,zoo,pars,spc — it created
brand L2 EVMs INSIDE luxd, the dead WAY-1 that produced the phantom brand
chains which decayed into dangling chain-aliases. Per brand_sovereign_node_one_way
luxd runs the LUX primary network ONLY; brands run their own sovereign node.

- default --track-chain-ids is now empty
- hard-refuse brand names (hanzo/zoo/pars/spc/osage) before any P-chain spend
- doc updated; retires alongside cmd/createchaintx (deleted on
  decomplect/retire-brand-l2-in-luxd)
2026-06-28 17:27:04 -07:00
zeekay fcb7acd636 decomplect: retire pqcrypto bundle (0x9003) — standalone mldsa/mlkem/slhdsa is the one way
Network reboot, no backward compat: delete the AI-slop pqcrypto bundle + every
gate pinning it (pqcryptoConfig genesis activation, relaunch-safety test pin).
ML-KEM stays via standalone mlkem (0x012201). pqcryptoConfig removed from all
upgrade.json/yaml; 0x9003 retired, never reuse.
2026-06-27 16:08:48 -07:00
zeekay 10d6203817 genesis/cmd: derivekey + derive-scan (LightMnemonic→funded localnet account, non-ewoq) 2026-06-17 19:29:32 -07:00
Hanzo AI 76e9d86b6e chore: update 2026-06-10 13:40:17 -07:00
Hanzo AI 0abb293d3f chore: update 2026-06-10 13:34:21 -07:00
Hanzo AI fb53885497 go: 1.26.3 → 1.26.4 (security: crypto/x509, mime, net/textproto) 2026-06-06 22:09:18 -07:00
Hanzo AI c629280b97 deps: bump luxfi/vm v1.2.0, sampler v1.1.0, staking v1.5.0 (relicense)
Picks up the 2026-06-06 relicensing:
- luxfi/vm v1.2.0 → Lux Ecosystem License v1.2 (was Lux Research v1.0).
  License-only retag of the extracted runtime; patent reservation
  preserved for runtime optimization surfaces, royalty-free for
  Descending Chains.
- luxfi/sampler v1.1.0 → BSD-3-Clause (was Lux Research v1.0).
  License restored to match the luxfi/node provenance (originally
  extracted from node/utils/sampler).
- luxfi/staking v1.5.0 → BSD-3-Clause (was Lux Research v1.0).
  License restored to match the luxfi/node provenance.

No code changes in this commit — go.mod/go.sum only.
2026-06-06 19:02:28 -07:00
Hanzo AI d5bf6e461b deps: bump p2p v1.21.1, node v1.29.3, sdk v1.17.6, vm v1.1.11 (Wave 2G-Cascade)
cmd: also drop the local codec replace directive — codec v1.1.5 has the
canonical zapcodec extracted to its own module per Wave 2G-Archive.
2026-06-06 06:31:34 -07:00
Hanzo AI 10a38d286d cmd: bump utxo to v0.3.7 + add local codec replace for zapcodec
Required for bootstrap-chain wire-compat with luxd v1.28.29. The
running node's wallet/network/primary uses utxo.ParseUTXO ZAP
dispatcher; the previous v0.3.4 emitted incompatible TypeKind/ShapeKind
discriminators (`wire: ShapeKind discriminator does not match expected
primitive shape`). Bump aligns the wire format.

Also adds local-codec replace because the published luxfi/codec@v1.1.4
does not contain the zapcodec package — that landed locally for the
2026-06 Wave 1D refactor and luxfi/node references it via the
zapcodec dispatch path.

inventory/brand-l1-2026-06-05-reset.json: post-reset canonical brand
L1 IDs. Supersedes brand-l1-2026-06-05.json (initial deploy with
orphan-prone naming).
2026-06-05 12:38:22 -07:00
Hanzo AI ff32c62328 cmd/rlp-export: admin_exportChain CLI wrapper (Task #118)
Symmetric with cmd/rlp-import. Thin HTTP client that:

  1. Idempotency precheck reads <output-file>.sentinel on the local PVC
     and short-circuits when Status=done + range matches — luxd is never
     contacted in the warm-cron case.
  2. Health-probes ${LuxdRPC}/ext/health (30s cap).
  3. POSTs admin_exportChain via /ext/bc/${chain}/rpc with KMS_AUTH_TOKEN
     bearer auth (same env-var contract as rlp-import).
  4. Maps Status → exit code:
       ok | noop      → 0
       interrupted    → 4 (operator re-runs with from-height=HighestExported+1)
       bad input      → 1
       luxd unreach   → 2
       admin RPC err  → 3

Lives in the cmd/ nested module per the existing rlp-import precedent.
Builds with GOWORK=off; Makefile target `make rlp-export` does this for you.

Tests cover: flag parsing, "latest" resolution, all four idempotency
fall-through branches, happy path + noop + interrupted server responses,
luxd unreachable, admin RPC error, KMS_AUTH_TOKEN bearer header
plumbing. 14 tests, all green.

Symmetric Makefile additions: `make rlp-export`, `make rlp-import`,
`make cmds`, `make test-rlp-export`.
2026-06-05 00:29:10 -07:00
Hanzo AI 6c3ba05cf8 cmd/rlp-import: operator-driven RLP import binary (Task #122)
Standalone Go binary that runs the six-step tenant RLP import lifecycle
in a single process: sentinel check → fetch → sha256 verify → wipe
chainData → POST admin.importChain → touch sentinel. Replaces the shell
loop split between luxd-startup.yaml's main container and the operator's
tenant-import init container.

The binary is invoked by luxfi/operator as a K8s Job — one per
LuxNetwork.spec.tenantImports[i]. Exit codes are wired for K8s backoff:
1=bad input/SHA mismatch/404, 2=luxd unreachable (transient retry),
3=admin.importChain RPC error. Content-hash addressing matches
TenantImportSpec.ContentHash byte-for-byte so the operator and the
binary compute identical sentinel paths.

Tests cover all six lifecycle steps + every failure exit code.
2026-06-05 00:23:27 -07:00
Hanzo AI 8a10a3873e cmd/bootstrap-chain: read LUX_MNEMONIC env (one var, one way)
The earlier MNEMONIC unification commit (4383ad4) missed this CLI tool;
it was still reading the legacy MNEMONIC env name directly via
os.Getenv. Per the "one var, one way" rule the canonical name is
LUX_MNEMONIC across every code path in the genesis repo.

Updated in cmd/bootstrap-chain/main.go:
- Package-doc usage example: MNEMONIC="..." -> LUX_MNEMONIC="..."
- --print-addr-only flag help: scrub MNEMONIC -> LUX_MNEMONIC
- --key-file flag help + comments: MNEMONIC -> LUX_MNEMONIC
- loadKey() reads os.Getenv("LUX_MNEMONIC") instead of "MNEMONIC"
- Error message references LUX_MNEMONIC

No backcompat fallback: callers using the legacy MNEMONIC env get the
clear "LUX_MNEMONIC env var or --key-file required" error.

Verification:
- go build ./...           -> clean
- go vet ./...             -> clean
- go test ./... -count=1   -> ok configs, ok pkg/genesis
2026-06-02 22:43:57 -07:00
Hanzo AI 30f300f9ea genesis: scrub Avalanche residue from pkg + cmd + LLM.md prose
Comment-only scrub of upstream-fork residue in user-facing docstrings,
CLI help text, and LLM.md prose. No behavior change; no wire-format
change.

pkg/genesis/keys.go:
- DefaultNumAccounts doc: drop "AvalancheJS" from canonical-BIP44 wallet
  list (was "Core, MetaMask, AvalancheJS, lux key derive" -> now
  "Lux Wallet, MetaMask, lux key derive").
- LoadKeysFromMnemonic doc: drop "Avalanche/Lux BIP44 path" (just
  "Lux BIP44 path") and "Core Wallet, AvalancheJS" wallet list (now
  "MetaMask, Lux Wallet").
- LoadKeysFromMnemonic inner loop comment: same scrub
  ("Standard Avalanche/Lux secp256k1 child" -> "Standard Lux ...";
  "matches Core Wallet, MetaMask, cast" -> "matches Lux Wallet, ...").
- BuildBIP44WalletAllocations doc: drop "SLIP-0044 coin type Lux
  inherits from its upstream lineage" prose. Replaced with brand-
  neutral "BIP44 coin type Lux uses by canonical convention".

cmd/genesis/main.go:
- bip44-wallet-keys flag help: drop "SLIP-0044 coin type 9000" ->
  "BIP44 coin type 9000 canonical convention".

LLM.md:
- "Fortuna Timestamp Fix" prose: drop "Fortuna, Etna, and Granite
  upgrades" naming. Replaced with "pre-Quasar Edition upgrades"
  framing. The JSON field names (etnaTimestamp / fortunaTimestamp /
  graniteTimestamp) are preserved in the example block because they
  are the wire format consumed by luxfi/evm.

Intentionally kept (per task scope and forward-compat realities):
- params_test.go:33 + params.go:303 "avax HRP" rejection guards —
  these are protective security tests/comments, not residue. The whole
  point is to refuse "avax"-HRP bech32 addresses synthesized from a
  canonical lux validator's 20 bytes. Stripping the "Avalanche" prose
  would obscure why the guard exists.
- pkg/genesis/types.go:229 EtnaTimestamp uint64 field — this Go field
  is a downstream mirror of the wire-format identifier defined at the
  source of truth in luxfi/evm/params/extras/network_upgrades.go and
  consumed by ~95 JSON genesis blobs, K8s startup manifests, academy
  docs, and migration tooling. Renaming the genesis-repo mirror alone
  would break JSON round-trip with every other consumer in the
  ecosystem. A wire-format rename of EtnaTimestamp -> QuasarTimestamp
  must originate in luxfi/evm and migrate every embedded genesis
  config + every k8s manifest + every doc in one coordinated PR series.
  That migration is OUT OF SCOPE for this commit and is reported back
  to the dispatcher; see external-caller surprises below.

Verification:
- go build ./...           -> clean
- go vet ./...             -> clean
- go test ./... -count=1   -> ok configs, ok pkg/genesis
- grep '[Aa]valanche\|AvalancheJS\|Core Wallet\|SLIP-0044' pkg/genesis
  cmd LLM.md
    -> only 2 lines remain (params.go:303 + params_test.go:33),
       both inside the protective HRP-rejection guard prose.
2026-06-02 22:41:28 -07:00
Hanzo AI 4383ad4583 genesis: unify mnemonic env to LUX_MNEMONIC + kill vesting docstring rot
One env, one way. The genesis mnemonic is now read from exactly one
env var: LUX_MNEMONIC. The prior two-env fallback chain
(MNEMONIC > LIGHT_MNEMONIC) is gone. Per-env isolation is by a
DIFFERENT mnemonic per env (loaded from KMS), not by env-var name.

pkg/genesis/keys.go:
- New MnemonicEnvVar = "LUX_MNEMONIC" constant.
- getMnemonicEnv() reads only LUX_MNEMONIC; no fallback.
- LightMnemonic constant kept (it's the well-known dev seed VALUE;
  pass it as the value of LUX_MNEMONIC for local nets).
- IsProductionNetwork docstring updated to spell out the canonical 4:
  mainnet(1) / testnet(2) / devnet(3) refuse public mnemonics;
  local(1337)+ allows LightMnemonic.
- Docstring rot purged: the prior "50M free + 50M vesting at 1%/year
  over 100y" claim was a lie — buildConfigFromKeyInfos never wired the
  100-period schedule (the code admits the schedule "overflows zapdb
  batch limit"). Replaced with the truth: 1000 × 50M LUX on X-Chain
  AND P-Chain, immediate spend; C-Chain is treasury-only (2T LUX).
- Dead code deleted: StakingStartTime, UnlockInterval consts,
  buildUnlockSchedule() function.

pkg/genesis/allocations.go:
- VestingConfig struct removed.
- DefaultVesting() removed.
- WithVesting() builder method removed.
- Vesting branches in PChain() / PChainMap() collapsed to immediate-only.
- MainnetAllocations no longer calls WithVesting(DefaultVesting()).

pkg/genesis/validator_keys.go:
- VestingPeriods const removed.
- GeneratePChainAllocationsWithVesting() removed.

pkg/genesis/networks.go (new):
- Canonical primary-network-id table: MainnetID / TestnetID / DevnetID
  / LocalID = 1 / 2 / 3 / 1337. Matching C-Chain (EVM) chain IDs:
  96369 / 96368 / 96370 / 31337.
- IsCanonicalPrimaryNetwork helper.

cmd/checkkeys/main.go:
- Reads LUX_MNEMONIC via genesis.MnemonicEnvVar (no fallback).

cmd/derive100/main.go:
- Usage hint updated: mnemonic-env: LUX_MNEMONIC.

cmd/genesis/main.go:
- Flag help, env-var docs, examples, and error messages all reference
  LUX_MNEMONIC.

pkg/genesis/light_mnemonic_guard_test.go:
- All t.Setenv calls use LUX_MNEMONIC.
- Test labels reference LightMnemonic (the constant) not the dead env name.

configs/getgenesis_test.go:
- Comment updated: LightMnemonic instead of LIGHT_MNEMONIC.

CHANGELOG.md + LLM.md:
- Documented the unification + the dead-code removal.

The validator stake-lock (3-entry 5y/10y/20y schedule attached to the
validator's UTXO inside buildConfigFromKeyInfos at keys.go:1077-1083)
is unchanged — that locked-stake bucket is the only UnlockSchedule in
the canonical genesis path and the ProtocolVM needs it.

External-caller sweep: grepped ~/work/lux, ~/work/hanzo, ~/work/zoo,
~/work/luxfi for genesis.VestingConfig / genesis.DefaultVesting /
genesis.GeneratePChainAllocationsWithVesting / genesis.StakingStartTime
/ genesis.UnlockInterval — zero hits. Safe to delete. (luxfi/keys at
~/work/lux/keys has its own MainnetAllocations copy — separate repo,
not affected.)

Verification:
- go build ./... → clean
- go vet ./... → clean
- go test ./... -count=1 → ok configs, ok pkg/genesis
- grep '[^_X]MNEMONIC\|LIGHT_MNEMONIC' under pkg/genesis cmd configs →
  zero matches (only CHANGELOG history + Python script local vars).
- grep VestingConfig|buildUnlockSchedule|GeneratePChainAllocationsWithVesting
  under source → zero matches.
2026-06-02 22:21:27 -07:00
Hanzo AI 6046cdb590 cmd/bootstrap-chain: decomplect chain.name from brand identity
The CreateChainTx `name` argument is a P-chain-unique identifier — caller's
choice. The brand alias ("hanzo", "zoo", "spc", "pars") is user-facing and
belongs at the chain-aliases ConfigMap layer. Conflating the two forced
the {brand}fix-{env}/ symlink workaround when re-bootstrapping over stale
P-chain chain rows from prior CreateChainTx attempts.

Default chain.name is now content-addressed: "<brand>-<8hex>" where
8hex = hex(SHA256("<brand>|<env>|"||genesisBytes))[:4]. Properties:

  - Idempotent: same canonical genesis → same chain.name → re-runs detect
    the existing chain via platform.getBlockchains and skip CreateChainTx.
  - Collision-free: different genesis bytes (any change to chainId, alloc,
    config) → different chain.name → CreateChainTx succeeds against
    P-chain instead of colliding with the previous stale row.
  - Brand-rooted: "hanzo-3f4a1c8b" stays humanly legible without conflating
    the chain.name with the user-facing brand alias.

Spec struct renamed: Name → Brand (user-facing) + ChainTxName (P-chain ID).
Result JSON gains "brand" and "chainTxName" fields so downstream
consumers (chain-aliases CM, explorer-chains CM, gateway routes) can pin
on the brand value without parsing the chain.name suffix.

Override path: --chain-name-override "<brand>=<name>,..." pins explicit
names for legacy reattachment scenarios.

4 new tests pin the contract:
  - Deterministic (same input → same output)
  - DifferentGenesisDifferentName (bytes change → name changes)
  - DifferentBrandDifferentName (brand-scoped)
  - DifferentEnvDifferentName (env-scoped)

All 14 tests pass.
2026-06-02 20:46:09 -07:00
Hanzo AI 57470516de cmd/bootstrap-chain: Track D regression gate — refuse genesis with embedded stateRoot/genesisHash
Close the Track D root cause that produced empty 0xa6aecc6c... state
roots on testnet brand L2s (hanzo, zoo, spc, pars). Pre-existing brand
L2 JSONs under configs/<brand>-<env>/genesis.json are already clean,
but nothing prevented a future generator (or a hand-edit cribbed from
the primary network's testnet/genesis.json embedded cChainGenesis blob)
from reintroducing those fields.

The fields override the result of luxfi/geth/core/genesis.go::
Genesis.Commit's flushAlloc — a stale value silently displaces the
canonical alloc trie, then the L2 boots with block 0 stateRoot pointing
at a trie node that was never persisted. That's the exact failure mode
producing "missing trie node 0x2d1cedac..." errors on the existing
testnet hanzo L2.

Changes:
- cmd/bootstrap-chain/main.go: hard-refuse any genesis carrying a
  non-empty stateRoot or genesisHash before any CreateChainTx burns
  LUX. Error names the specific geth/core/genesis.go lines and the
  Track D context so the operator can fix at source.
- cmd/bootstrap-chain/main_test.go: TestBrandL2GenesisHasNoStateRoot-
  OrGenesisHash walks every <brand>-<env>/genesis.json under configs/
  and asserts the absence of both fields. Test self-checks that at
  least one brand L2 dir was found so a path bug can't silently pass.

The historical Lux primary C-Chain header workaround that needed these
overrides is dead — the empirical block-0 hash now lives in
configs/mainnet/cchain.json. Brand L2 JSONs MUST stay clean.

Authorized by 2026-06-02 destructive recovery sweep — Track D root
cause closure.
2026-06-02 19:03:10 -07:00
Hanzo AI d712fad7f9 cmd/bootstrap-chain: add --key-file flag (raw secp256k1 alt to MNEMONIC+BIP44)
Operators that already hold a 32-byte hex secp256k1 deployer key on disk
(e.g. ~/work/lux/keys/deployer-test-dev/private.key used to fund the
test+dev primary genesis allocations) can pass --key-file <path> instead
of MNEMONIC + --bip44-idx. MNEMONIC path remains the default.

Drove the brand-L2 CreateChainTx registration for testnet + devnet
(closes universe #168) — 8 chains, all bootstrapped 3/3 pods per env.

go.mod: add local replace github.com/luxfi/node => ../../node to pick up
the wallet's P-only fail-soft (lux/node@HEAD). Drop the replace once
luxfi/node tags the change. tidy refreshed transitive deps (accel v1.1.4
→ v1.1.8, local genesis v1.12.19 → v1.13.8) — these are no-op for the
bootstrap-chain logic.
2026-06-02 13:49:08 -07:00
Hanzo AI b69c33a8f8 scrub: subnet/l2 → chain (canonical vocabulary, forward-only)
Wire-format codec IDs unchanged. CLI aliases deleted; chain is the
command. No backwards-compat shims, no deprecation comments.
2026-05-31 14:49:54 -07:00
Hanzo AI 4d38f62b94 cmd/bootstrap-l2: pin luxfi/node v1.27.24 → v1.28.0 (codec-v1 wallet)
Pre-v1.28.0 nodes wrote P-chain UTXOs at wire-version=0. v1.28.0
ships the multi-version codec (luxfi/node@c589251a1d) and writes
all new UTXOs at wire-version=1 by default. The bootstrap-l2 tool
embeds the luxfi/node wallet to build/sign CreateNetworkTx and
CreateChainTx; the v1.27.24 pin's wallet codec only knew v0, so
syncing the wallet against a fresh v1.28.0 node failed with:

  wallet sync: unknown codec version

Bump cmd/go.mod to luxfi/node v1.28.0, which pulls in the v0+v1
codec register and decodes a v1.28.0 node's UTXOs correctly.

Verified on lux-devnet: rebuilt bootstrap-l2 then ran:
  /work/bootstrap-l2 -uri=http://luxd-0.luxd-headless.lux-devnet.svc.cluster.local:9650 \
    -hrp=dev -bip44-idx=5 -track-chain-ids=hanzo,zoo,pars,spc \
    -configs-dir=/work/configs -chain-settle-delay=180s -probe-timeout=1800s

All 4 L2 EVMs bootstrapped:
  hanzo → i3AxuEfLAqzAvabgpYvXC7QCvqXnJAHr3AYiVzfK2FDauyJSa
  zoo   → 5YP3iV6ytRjtscF8dcXWQTqd8dwjAaoZnW6oNVfDM3d2mwwvS
  pars  → JxDPdhGde9PSrZ3nLwe7shwZaXEFBoAJ8V9rGqbcKXy9FEQEm
  spc   → LHU4rNBLr1Zi2pNJXL47djkCmmyDSgTXmaB4oNQBYyGfqPtg9

Transitive bumps:
  github.com/luxfi/consensus v1.25.0 → v1.25.2
  github.com/luxfi/genesis    v1.12.15 → v1.12.19
2026-05-31 02:08:17 -07:00
Hanzo DevandGitHub 0f6bec3e2f decomplect: split cmd/ to nested module — root has no luxfi/node (#11)
* decomplect: move SecurityProfile Resolve to pkg/genesis/security

pkg/genesis core types (SecurityProfile struct) stay free of
luxfi/consensus dep. The verification gate (ResolveProfile + hash
comparison) moves to pkg/genesis/security which imports both.

Result: downstream consumers that only need to parse canonical
genesis data (luxd v1.23.x line, tools, indexers) can do so without
dragging luxfi/consensus + luxfi/threshold + luxfi/validators
(Corona→Corona refactor) into their go.mod.

go list -deps ./pkg/genesis → no luxfi/consensus.
go test ./pkg/genesis/security/ → all 5 F102 verification tests pass.

Closes the dep cascade that blocked the v1.23.x backport of
evmAddr/utxoAddr canonical genesis. One and one way only:
data lives in pkg/genesis, verification lives in pkg/genesis/security,
consensus consumes both — never the reverse.

Closes #93 Phase 1.

* decomplect: split cmd/ into nested module (no luxfi/node in root go.mod)

The Phase 1 split (SecurityProfile struct in core, Resolve in
pkg/genesis/security subpackage) addressed package-level dep cycles,
but the genesis root go.mod still required luxfi/node v1.27.24
because cmd/bootstrap-l2 + cmd/derive100 import node directly for
wallet/tx-builder bits.

This commit moves cmd/ to a nested module
(github.com/luxfi/genesis/cmd) with its own go.mod that holds the
luxfi/node dep. Root go.mod no longer requires luxfi/node, breaking
the MVS cascade that was forcing luxd v1.23.x consumers to upgrade
28 packages just to bump genesis.

Result:
- go list -m all on root no longer surfaces luxfi/node directly
  (still transitively visible at v1.23.x-compatible versions, not the
  v1.27.x bloat)
- cmd/ retains the same source layout; tools still build via cmd/go.mod
- pkg/genesis core API unchanged
- pkg/genesis/security gate unchanged

Two-module separation:
- github.com/luxfi/genesis        — data types + verification primitives
- github.com/luxfi/genesis/cmd    — tools that need luxfi/node

One direction, no reverse import.

Closes the structural blocker for v1.23.x backport of canonical
evmAddr/utxoAddr.
2026-05-30 17:28:13 -07:00
Hanzo DevandGitHub 514e996737 refactor: XAssetID → UTXOAssetID (genesis builder + bootstrap-l2) (#9) 2026-05-30 14:30:59 -07:00
Hanzo DevandGitHub 08e9e6904e chore: update 2026-05-29 23:33:13 -07:00
Hanzo DevandGitHub 770c12f3ab chore(genesis): kill subnet — rename to chainID across genesis tool surface (#6)
Per CLAUDE.md "translate upstream Lux's 'subnet' out immediately":
zero remaining `subnet|Subnet|SUBNET` occurrences in lux/genesis Go +
JSON files. All eleven were comment-level cruft or one struct field
that hadn't been renamed yet.

## Changes

`cmd/bootstrap-l2/main.go`:
- Top-of-file vocabulary block updated — kills the "upstream
  CreateSubnetTx" + "ConvertSubnetToL1Tx" framing in favor of the
  canonical three-name trinity: `chainID` (chain-owner network),
  `blockchainID` (blockchain's own ID), `evmChainID` (EIP-155).
- `platformBlockchain.SubnetID` → `ChainID`; JSON tag
  `"subnetID"` → `"chainID"`.
- `resultChain.ChainOwnerID` → `ChainID`; JSON tag
  `"chainOwnerId"` → `"chainId"`.
- Inline struct in `existingByName` map: `ChainOwnerID` →
  `ChainID`, log line reads `chainID=` instead of `ownerID=`.
- All callsites updated.

`cmd/genesis/main.go`, `pkg/genesis/keys.go`,
`pkg/genesis/allocations.go`:
- Four comment-level rephrases: "subnet-bootstrap CLI" → "chain-
  bootstrap CLI", "subnet creation operations" → "chain creation
  operations".

## Output JSON wire change

The bootstrap result file emitted by `--output` now uses
`{chainId, blockchainId, evmChainId}` per chain instead of
`{chainOwnerId, blockchainId, evmChainId}`. Downstream consumers
(universe operator, chainboot, anything that reads
devnet-l2-live.json) MUST update their parsers — this is a
breaking wire change in the file format, intentional.

The platform.getBlockchains JSON-RPC method's wire format stays
upstream-shaped on the way IN; our parser now decodes from
`"chainID"` (post-rename in lux/node `APIBlockchain`). If running
against an older lqd that still emits `"subnetID"`, the field
silently zero-values — which is the correct safety behavior (the
caller will fall through to the create path and the operator gets
a re-create-attempt failure rather than a silent ghost reuse).

## Verified clean

  grep -rnE "subnet|Subnet|SUBNET" --include="*.go" --include="*.json"
  # zero matches in lux/genesis

Build clean. 9 normalizeAllocKey tests still pass.
2026-05-29 21:07:57 -07:00
Hanzo DevandGitHub 45008b7205 fix(genesis): canonical 0x prefix on all devnet alloc keys + fail-loud bootstrap-l2 (#72) (#5)
* feat(genesis): bootstrap-l2 — durable replacement for /tmp script

Recreates the v1.27.x ConvertSubnetToL1 / CreateChain pipeline previously
maintained as /tmp/bootstrap-l2-v127. New tool at cmd/bootstrap-l2/:

- Reads MNEMONIC from env, derives BIP44 m/44'/9000'/0'/0/<idx>
- For each chain in --chains: IssueCreateNetworkTx → wallet re-sync →
  IssueCreateChainTx (vmID nyGCobireNhxFB7iM5bxV74hAY6j9nQX6wizxfWomnMMtztkr)
  → IssueAddChainValidatorTx for every primary validator
- Probes info.isBootstrapped(chain=<bcID>) + eth_blockNumber>0 per chain;
  if either misses the deadline the whole tool exits non-zero (no partial
  state propagated to YAML/CMs)
- Emits a single JSON document {chains:[{name, subnetId, blockchainId,
  evmChainId, firstBlockHex, bootstrappedAt}, ...]} for downstream
  YAML/CM updates
- --print-addr-only mode to verify funding before spending

go.mod: bump luxfi/node v1.23.36 → v1.27.21 to match cluster runtime
(devnet runs luxd v1.27.21; pre-rename wallet codecs would produce txs the
nodes can't decode). Only existing genesis use of luxfi/node is the
address formatter — no behavior change for other cmds.

Designed for the four canonical Lux L2 EVMs (hanzo, zoo, pars, spc)
but the chain list is data-driven via --chains and --configs-dir.

* feat(genesis): bootstrap-l2 default VM ID → mgj786NP... (native subnet-evm)

Devnet luxd-0 /data/plugins contains exactly two subnet-evm plugin IDs
(mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6 and
ag3GReYPNuSR17rUP8acMdZipQBikdXNRKDyFszAysmy3vDXE), neither matching the
brand-namespaced alias nyGCobireNhxFB7iM5bxV74hAY6j9nQX6wizxfWomnMMtztkr
recorded in the 2026-05-27 chain-aliases ConfigMap annotation.

That alias required a runtime symlink (created post-startup, lost on PVC
remount) — the same fragile path that's been the historical source of
L2-EVM bootstrap failures. Mainnet's lux-mainnet/luxd-startup.yaml uses
mgj786NP... directly and `cp`'s it as the ag3GR... duplicate; we mirror
that pattern here so the plugin is *always* present without a startup
script writing into a PVC.

The chain-aliases CM annotation about nyGCobir... is now stale and will be
rewritten in the post-bootstrap update.

* feat(genesis): bootstrap-l2 — --existing-subnet-ids to resume after partial bootstrap

When CreateNetworkTx succeeds but the subsequent CreateChainTx fails (or
the tool gets killed mid-flight), the subnet still exists on-chain and
the fee was burned. Re-running the tool without --existing-subnet-ids
would burn another CreateNetwork fee.

Format: --existing-subnet-ids=hanzo:2PkWqv...,zoo:abc...

For each named chain, skip IssueCreateNetworkTx and pass the supplied
subnet ID straight into IssueCreateChainTx. Chains not in the map
continue to create a fresh subnet.

Also surfaces a real operational pattern: load-balanced services like
svc/luxd-headless can pin a kubectl port-forward to a lagging pod whose
state is behind the chain head; the tool must still complete by
re-using the subnet visible on the leader pod.

* feat(genesis): bootstrap-l2 — EVM heartbeat tx + decoupled bootstrap probe

Acceptance criterion 'eth_blockNumber > 0' on a fresh subnet-evm chain
requires injecting the first tx (subnet-evm only seals blocks when a tx
arrives). Adding two new flags:

  --evm-heartbeat-key=<hex>   LUX_PRIVATE_KEY hex (0x9011E888...).
                               Sends a 0-value self-tx after the chain
                               reports info.isBootstrapped=true; the first
                               tx rolls block 1.

  --probe-bootstrap-only      Skip the eth_blockNumber>0 wait. Used when
                               an out-of-band heartbeat (chain-heartbeat
                               CronJob) will roll block 1 later.

The probe is also split into waitBootstrap + probeChain so the heartbeat
fires after isBootstrapped but before eth_blockNumber checks (otherwise
eth_sendRawTransaction returns 'chain not bootstrapped').

Signing path: luxfi/crypto/secp256k1 → ecdsa.PrivateKey → geth/core/types
SignTx with EIP-155 signer scoped to each chain's evmChainID. EVM address
derivation goes through luxfi/crypto/secp256k1.PubkeyToAddress so we never
import an upstream go-ethereum crypto package.

* feat(genesis): bootstrap-l2 — preset chain ID (chain:subnetID:chainID) for resume

Extends --existing-subnet-ids parser to accept an optional third field
<chainID>. When present, both IssueCreateNetworkTx and IssueCreateChainTx
are skipped; the tool only waits for isBootstrapped, sends the heartbeat
(if --evm-heartbeat-key is set), and probes eth_blockNumber.

This is the post-partial-failure resume pattern: if hanzo's subnet+chain
were created successfully in a prior run but zoo onwards failed (or were
never attempted), pass

  --existing-subnet-ids=hanzo:2PkWqv...:rHiiTB...,zoo:abc...

and the tool will skip the hanzo P-chain spend, treat hanzo as already
created, and continue with the next chain.

Also gates the AddChainValidatorTx and post-chain wallet re-sync on
presetChainID being unset, since both are unnecessary in resume mode.

* fix(genesis): canonical 0x prefix on all devnet alloc keys + fail-loud bootstrap-l2 (#72)

Two-part fix for the alloc-key shape that bootstrap-l2 consumes:

## 1. Genesis configs patched at source (4 files)

Every alloc key in:
  - configs/hanzo-devnet/genesis.json
  - configs/pars-devnet/genesis.json
  - configs/spc-devnet/genesis.json
  - configs/zoo-devnet/genesis.json

now matches the canonical /^0x[0-9a-fA-F]{40}$/ shape. The EVM
genesis loader rejects unprefixed keys; previously bootstrap-l2's
in-memory repair was masking the issue. Fixing at source makes the
configs self-describing and stops the "works for me but won't load
on a fresh node" failure mode.

## 2. bootstrap-l2 normalization is now fail-loud (cmd/bootstrap-l2/main.go)

Pre-flight repair upgrades from silent fix to:

  - Normalize 0X → 0x (preserve body case for EIP-55 checksum signal)
  - Add missing 0x prefix (repair, log count)
  - Reject anything that doesn't match /^0x[0-9a-fA-F]{40}$/ AFTER
    repair via log.Fatalf with up to 5 sample keys

Per the user's directive:
  > "normalize alloc keys on load/write or fail loudly before chain
  >  creation. I prefer normalization plus a validation log, not
  >  silent mutation with no signal."

Log line now shows both healthy and repaired counts:
  [devnet] hanzo: alloc keys ok=2 0x-repaired=0 (in-memory only, file unchanged)

A genuinely malformed key (non-hex, wrong length) aborts the entire
bootstrap before any CreateNetworkTx burns LUX:
  [devnet] hanzo: 1 malformed alloc keys (require canonical
                   /^0x[0-9a-fA-F]{40}$/); sample: ["0xZZZ..."]

## 3. Single source of truth: normalizeAllocKey() helper

New pure function returns `(canonical, repaired, valid)`. Every
consumer (the in-memory repair, future validation tooling, any
on-disk rewriter) routes through this one function. Routes:

  - body-case PRESERVED (EIP-55 checksum)
  - prefix-case NORMALIZED (`0X` → `0x`)
  - shape-validated (length 40, hex only)
  - idempotent

## Tests

9 new tests in cmd/bootstrap-l2/main_test.go:

  - already-canonical roundtrip
  - missing-prefix repair flag
  - 0X-prefix normalization
  - body-case preservation (EIP-55 invariant)
  - short body rejection (39/38/empty)
  - long body rejection (41)
  - non-hex rejection (g, _, space)
  - empty string rejection
  - idempotency under repeated normalize calls

All pass. Build clean.
2026-05-29 20:02:37 -07:00
Hanzo AI 28e520ef7e decomplect: drop evmcrypto/utxocrypto aliases — use crypto + secp256k1 by name
dropping aliases on github.com/luxfi/crypto and github.com/luxfi/crypto/secp256k1.
no name collisions with stdlib (validator_keys.go's crypto/rand is rand, not crypto).
also purges remaining ethAddr / luxAddr / luxKey local vars (renamed to addr /
utxoAddr / secpKey).
2026-05-22 21:05:17 -07:00
Hanzo AI 8948b4e1cb EVMAddr (Go) + UTXOAddr (Go) — pure chain-model naming, no eth/lux branding
Renames Go struct field ETHAddr → EVMAddr across types.go, keys.go,
allocations.go, validator_keys.go, config.go, builder/builder.go,
cmd/genesis, cmd/checkkeys. JSON tags stayed evmAddr already (from
v1.12.6). Result: matched pair EVMAddr+UTXOAddr on both Go and JSON
sides.

Kept ParseETHAddress as the input-parsing function name — it describes
what it parses (an Ethereum-format hex address), not the field it
populates (an EVMAddr).

LLM.md updated to state the canonical pair plainly; no rename history.
2026-05-22 20:37:30 -07:00
Hanzo AI 9eed1b8791 no backcompat — canonical address fields are ethAddr + utxoAddr
Renames Allocation.EVMAddr → ETHAddr (json tag `ethAddr`) and
KeyInfo.EVMAddr → ETHAddr across pkg/genesis, builder, cmd/genesis,
cmd/checkkeys. AllocationJSON drops the legacy `evmAddr` tag entirely
— there is no aliasing, no UnmarshalJSON normalizer, no fallback.
JSON files and Go code share one canonical pair: ethAddr + utxoAddr.

configs/getgenesis_test.go updated to assert against the canonical
field. parseAllocations error message says "invalid eth address"
to match the new identifier.

Tests:
  ok  github.com/luxfi/genesis/builder
  ok  github.com/luxfi/genesis/configs
  ok  github.com/luxfi/genesis/pkg/genesis
2026-05-21 12:51:40 -07:00
Hanzo AI 526ce8b641 kill F12/F30 nid-hardened derivation paths (no slop)
Removed:
- LoadKeysFromMnemonic's `nid uint32` parameter (was ignored)
- BuildWalletAllocations (replaced by canonical BuildBIP44WalletAllocations)
- BuildWalletKeyHex (no callers after removing the nid-hardened scheme)
- deriveLuxAccount helper (m/44'/9000'/nid'/...) — only used by removed code
- genesis_hd_branches_test.go entire file (tested abandoned NID-hardening
  requirement; canonical Avalanche/Lux BIP44 is intentionally nid-stable
  so the same mnemonic gives the same addresses in every wallet)

TestTransferPChain now skips by default — opts in via LUX_LIVE_TESTS=1
(it hits a live api.lux-dev.network endpoint, not appropriate for unit-test
runs that fail when CF returns 522).

cmd/checkkeys/main.go: dropped unused NETWORK_ID env handling.
cmd/genesis/main.go: BuildWalletAllocations → BuildBIP44WalletAllocations.

All tests pass in clean offline mode now:
  configs    OK (TestPrimaryChainShards_PerChainCanonicalChainID green)
  pkg/genesis OK
  builder    OK
2026-05-21 12:30:15 -07:00
Hanzo AI 09558bb2e3 rename ETHAddr→EVMAddr, LUXAddr→UTXOAddr (uniform UTXO/EVM split)
Address types now reflect the actual two-address model that exists across
all chains:
  - EVMAddr (0x H160) — C-Chain and every other EVM chain
  - UTXOAddr (bech32, P-/X- prefix interchangeable) — P-Chain and X-Chain

The old names (ETHAddr / LUXAddr / avaxAddr / xAddr) confused two
orthogonal axes (Ethereum-vs-Lux brand × address-encoding-type) with
each other. UTXO/EVM is the actual technical distinction.

Backward compat:
  - JSON: AllocationJSON.UnmarshalJSON accepts evmAddr/ethAddr +
    utxoAddr/xAddr/luxAddr/avaxAddr (legacy upstream Avalanche).
  - Existing config files: bumped with both new + legacy fields present.

Builds + tests green on luxfi/genesis and luxfi/node/genesis/builder.

Also: each allocation gets 50M LUX on X (initialAmount) + 50M LUX on P
(unlockSchedule locktime=0). For 1000 accounts: 50B on each chain →
100B total per env across P+X. mainnet legacy retained at-is (1.08M
historic blocks reference original asset IDs).
2026-05-20 16:10:50 -07:00
Hanzo AI 037147d3bd deps: bump consensus v1.23.5 -> v1.23.25 + gofmt cleanup
Closes the v1.9.14 CI build failure: pkg/genesis/security_profile_test.go
references consensusconfig.StrictPQ()/ProfileStrictPQ which were only
introduced when the Lux prefix was dropped from profile identifiers.
That symbol first ships in consensus v1.23.25.

Also fixes the gofmt drift on builder/builder.go, cmd/genesis/main.go,
and pkg/genesis/keys.go that was failing the Test job.
2026-05-16 17:59:42 -07:00
Hanzo DevandGitHub 25610026e3 env: drop LUX_ prefix from env vars (noise) (#1)
Renames LUX_-prefixed env vars to canonical, non-noisy forms:

- LUX_MNEMONIC          dropped from getMnemonicEnv() lookup chain.
                         Priority is now MNEMONIC > LIGHT_MNEMONIC.
                         All callers, doc comments, and test
                         t.Setenv("LUX_MNEMONIC", "") clears removed.
- LUX_NETWORK_ID        -> NETWORK_ID
- LUX_GENESIS_DIR       -> GENESIS_DIR
- LUX_KEYS_DIR          -> KEYS_DIR  (doc fix; code already used KEYS_DIR)
- LUX_BOOTSTRAPPERS_FILE-> BOOTSTRAPPERS_FILE
- LUX_PCHAIN_ALLOCS     -> PCHAIN_ALLOCS
- LUX_PCHAIN_ALLOCS_FILE-> PCHAIN_ALLOCS_FILE

Doc string mentions of past-but-still-supported `LUX_X` env var names
("set MNEMONIC or LUX_MNEMONIC env var") are also dropped — the env
var is just MNEMONIC now.

LUX_DISABLE_CCHAIN / LUX_CCHAIN_GENESIS_FILE history left in LLM.md
(historical changelog table) and chain_shards_test.go (comment
explicitly documenting the data-driven replacement contract). These
are documenting past behavior, per task convention for historical
references.

BREAKING: external callers/operators must update env var names. Per
CLAUDE.md no-backwards-compatibility rule, the old forms are removed.
2026-05-15 16:10:32 -07:00
Hanzo AI a4b05b40f5 genesis: shard-driven primary chain set (X opt-in like C/Q/Z)
Make every primary-network chain (X/C/D/Q/A/B/T/Z/G/K) opt-in via its
own <x>chain.json shard. Absent shard -> empty ConfigOutput field ->
builder skips the CreateChainTx -> daemon doesn't start that chain.

Lux mainnet/testnet/devnet ship full chain set as shards; localnet and
local ship the same canonical X asset shard. P-only sovereign L1 boot
shape (<tenant> etc.) is achieved by shipping pchain.json+network.json
only — no env knob, no special builder branch.

X-Chain genesis was previously hardcoded inside FromConfig as a literal
{Symbol:"LUX",Name:"Lux",Denomination:9}. Now sourced from xchain.json
shard, which keeps the same canonical asset for Lux but lets a sibling
network (lqd / <tenant>) ship its own descriptor or ship none and
boot a P-only primary network.

builder.FromConfig:
  - HRP hoisted above the X-Chain conditional (used on both branches).
  - X-Chain construction gated on config.XChainGenesis != "".
  - Returns ids.Empty for xAssetID when X is absent.
  - Single chainEntries table replaces the per-chain switch and the
    DefaultChainGenesis stub-data fallback.
  - "platform" alias renamed to "protocol" (PChainAliases / VMAliases /
    Aliases output) — value is "P-Chain", served by ProtocolVM.

configs.loadAllChainShards / readAllChainShards:
  - Single dispatch over primaryChainShardFiles in canonical order
    (X,C,D,Q,A,B,T,Z,G,K). Adding a new primary chain is one entry +
    one slot, not a new env knob and not a new builder branch.

pkg/genesis/types.go:
  - ConfigOutput.XChainGenesis (omitempty) added; threaded through
    Config <-> ConfigOutput conversions.

Tests:
  - builder/builder_test.go: TestFromConfig_ChainSetIsShardDriven pins
    the contract — full mainnet emits 10 chains + non-empty xAssetID;
    same config with all chain shards stripped emits 0 chains + empty
    xAssetID, validators + UTXOs intact.
  - configs/chain_shards_test.go: TestGetGenesis_XChainShardPresent...
    asserts every Lux network embeds the canonical {LUX,Lux,9} asset;
    AbsentShardEmptyOptIn extended to cover xChainGenesis.
2026-05-14 12:32:53 -07:00
Hanzo AI 72b5bbcbb2 configs: LUX_DISABLE_ZCHAIN / LUX_DISABLE_QCHAIN env knobs
Mirror the LUX_DISABLE_CCHAIN three-precedence pattern for Q-Chain
(Quantum VM) and Z-Chain (ZK VM). Downstream forks that run only
P+X on the primary network (<tenant> is the first) set
LUX_DISABLE_QCHAIN=1 and LUX_DISABLE_ZCHAIN=1 to omit those entries
from the marshalled primary genesis, and builder.FromConfig's
'if config.QChainGenesis != ""' / 'ZChainGenesis' guards skip the
entries at build time.

Precedence (identical to C-Chain knob):
  1. LUX_DISABLE_<X>CHAIN=1 → empty (omit from primary genesis)
  2. LUX_<X>CHAIN_GENESIS_FILE=<path> → file contents verbatim
  3. unset → DefaultPlaceholderGenesis

Q-Chain is the post-quantum primitives chain; Z-Chain is the
zero-knowledge primitives chain. Both are Lux Network-specific and
have no place in the primary genesis of downstream L1s.
2026-05-11 14:43:04 -07:00
Hanzo AI 7cc8aa135f ci: add CI + Release workflows; gofmt -s; bump metric v1.4.11→v1.4.12
The repo had no .github/workflows/ at all, so tag pushes never produced
release binaries.

Added:

  .github/workflows/ci.yml       go vet, gofmt -s -d, go test (push/PR to main)
  .github/workflows/release.yml  build genesis for linux|darwin|windows
                                 amd64/arm64 + sha256, attach to GH release

CI runs `go test -skip TestGetGenesisLocalnet ./...` until the embedded
localnet genesis file is regenerated (expected 5e17 wei / 0.5 LUX, got
5e14). All other tests are green.

While here:
  * go.mod bumped Go directive 1.26.1 → 1.26.2 (matches new toolchain
    used by ci.yml / release.yml).
  * luxfi/metric v1.4.11 → v1.4.12. v1.4.11 had a build-tag bug where
    both process_metrics_other.go and process_metrics_windows.go used
    //go:build windows, causing duplicate symbols on GOOS=windows. v1.4.12
    fixes the tag to //go:build !unix && !windows. Required for the
    windows/amd64 leg of the release matrix.
  * `gofmt -s -w` on four pre-existing unformatted files so the new
    gofmt CI gate is green from day one.
2026-05-10 17:41:27 -07:00
Hanzo AI 487513c844 genesis: HD branch hardening + LIGHT_MNEMONIC production refusal (F12/F30/F31/F35)
HIP-0077 §"Identity" HD path alignment. Every public derivation entry
point now takes the network id and derives on the per-network
hardened branches mandated by the spec:

  m/44'/9000'/nid'/0'/i'   -> device_pq_key[i]   (ML-DSA-65)
  m/44'/9000'/nid'/1'/i'   -> device_lux_key[i]  (secp256k1)

All five levels (44', 9000', nid', branch, index) are now hardened on
the secp256k1 branch. Per-network hardening (nid' at the account level)
means the same mnemonic on different network ids derives fully
independent keypairs — no cross-network key reuse possible.

ML-DSA-65 keypair: expand the 32-byte BIP-32 child seed via
SHAKE-256("LUX/HIP-0077/mldsa65" || child_seed) into the 32-byte xi
that FIPS 204 §5.1 KeyGen consumes. Domain-separated so future schemes
sharing the same BIP-32 child seed cannot collide. ML-DSA backend:
cloudflare/circl mldsa65.

LoadKeysFromMnemonicEnvForNetwork blacklists 6 known public mnemonics
(BIP-39 abandon vector, Hardhat default, Trezor demo) and refuses to
proceed in production — closes F31. The CI/dev path still works with
LUX_LIGHT_MNEMONIC=1 explicitly set.

cmd/checkkeys + cmd/genesis: thread the network id through every
call site. cmd/derive100: helper that derives the first 100 PQ+secp
keypairs per network id for the genesis allocation.

Tests: pkg/genesis PASS (HD branches + LIGHT_MNEMONIC guard).
CHANGELOG.md added.

Breaking signature change for LoadKeysFromMnemonic /
LoadKeysFromMnemonicEnv / BuildWalletAllocations / BuildWalletKeyHex
(every entry point now takes nid uint32). Patch-bump.
2026-05-10 17:19:38 -07:00
Hanzo AI 222f6b9e32 feat: BIP44 wallet spending allocations from mnemonic
When LUX_MNEMONIC is set and -wallet-keys > 0, derive secp256k1
keys at m/44'/9000'/0'/0/{i} and add free spending allocations
(no vesting, no lock) with both luxAddr and ethAddr.

Flags:
  -wallet-keys N    Number of mnemonic-derived keys (default 0)
  -wallet-amount N  LUX per key (default 10000)

These keys can be used by the bootstrap tool to create L1 chains
without needing access to node staker TLS cert keys.
2026-04-06 22:56:20 -07:00
Hanzo AI f48fc4d29a chore: remove regenerate-genesis — genesis is permanent, use cmd/genesis for new networks only 2026-04-06 22:34:31 -07:00
Hanzo AI 1fd6379752 genesis: regenerate all network configs
- mainnet: 100 accounts x 500M LUX, 5 stakers, P-lux1 HRP, real IPs
- testnet: 100 accounts x 500M LUX, 2 stakers, P-test1 HRP, networkID=2
- devnet: 100 accounts x 500M LUX, 3 stakers, P-dev1 HRP, networkID=3
- bootstrappers: real validator IPs
- fix mnemonic env priority: MNEMONIC > LUX_MNEMONIC > LIGHT_MNEMONIC
- remove stale .bak files
2026-04-04 11:22:32 -07:00