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>
`go build ./...` failed outright — the v1.16.x line was DELETED from
github.com/luxfi/geth (newest is v1.20.1) while proxy.golang.org still serves
it, and GOPRIVATE forces a direct fetch for luxfi/*, so resolution fails with
"unknown revision v1.16.98".
Not fixable by tidying here: the requirement is baked into PUBLISHED modules,
which pin the dead tag and in some cases explicitly `exclude` the alternative.
The first versions that moved off it:
luxfi/crypto v1.20.2 (every version through v1.20.0 pins the dead tag)
luxfi/constants v1.6.2
luxfi/warp v1.24.1
Requiring those is what escapes it; geth then resolves to v1.20.1. Companion
luxfi modules moved with them at minor level within v1.x / v0.x — no major jumps.
Verified: go build ./... goes from exit 1 to exit 0. go test ./... is 1 ok / 3 fail. Those tests were NOT reachable before —
the packages could not compile — so this exposes pre-existing failures rather
than causing them.
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>
mainnet's mchain.json asked for 7-of-10 while mainnet genesis declares
five validators, and localnet asked for 3-of-5 with three. Nothing read
those fields before, so the mismatch was inert; now that the policy is
authoritative it would fail closed forever — RunKeygen refuses a policy
needing more parties than the committee has, so no custody key could ever
be generated.
Policies now match the validator set each network actually has: 3-of-5 on
mainnet/testnet/devnet, 2-of-3 on localnet. Both keep 2K > N, so neither
admits two disjoint quorums.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
mchain.json carried "mpcThreshold": 7 alongside "mpcParties": 10. A bare
threshold number is exactly the ambiguity that produced degree-0 (1-of-n)
custody keys elsewhere in this stack: an operator reads it as the number
of signers, every threshold library reads it as the polynomial degree,
and the two differ by one. Nothing in the VM read these fields, so the
genesis blob stated a quorum that had no effect and could not be trusted
if it ever did.
Replaced by a single "policy" field in operator form — "7-of-10" on
mainnet, "3-of-5" on testnet/devnet/localnet, sized so each network's
validator count can actually satisfy it. M-Chain decodes it through
quorum.Policy and derives the protocol degree from it at one place, so
genesis is now the authority on how many custodians must cooperate to
move bridged funds.
Also renames the vm field from the retired "ThresholdVM" to "mpcvm".
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
stateUpgrades[1]@1784415311 (Multicall3 at 0xcA11...CA11, added in b6f6170) is a
latent brick for C-Chain 96369. The C-Chain EVM is github.com/luxfi/evm, whose
config load is strict:
- plugin/evm/vm.go:962 dec.DisallowUnknownFields()
- params/extras/state_upgrade.go checkStateUpgradesCompatible():
len(newUpgrades) > len(activeUpgrades) => "cannot retroactively enable
StateUpgrade[N]" => ConfigCompatError => VM init refused
Timestamp 1784415311 (2026-07-19) is already in the past. The live 5/5 run the
proven-canonical config (c3a1763) which has exactly ONE applied stateUpgrade,
[0]@1766708400 (23-account Path-A). The moment any luxfi/evm node rolls this
config with head past 1784415311, it computes 2 activated stateUpgrades vs the
1 in applied history -> retroactive error -> C-Chain bricks (warpConfig class).
Fix: remove stateUpgrades[1] entirely. Eliminates the retroactive-state-upgrade
brick class. File is now content-identical to proven-canonical c3a1763
(stateUpgrades=[0] only). Repo-config change ONLY — not rolled to live nodes.
Mainnet Multicall3 predeploy must instead use a FUTURE-dated monotonic
stateUpgrade or a deterministic factory (see deploy plan) — never a retroactive
one. Devnet got Multicall3 via CreateX factory with ZERO stateUpgrades.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Canonical upstream mirror of the universe luxd-startup change. Adds a stateUpgrade at
blockTimestamp 1784415311 deploying the canonical Multicall3 runtime (3808 bytes, verified
byte-identical to Ethereum's live 0xcA11). Purely additive; strictly-increasing timestamps.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The LP-018 (family,env)->EVMChainID source-of-truth map carried the stale
7070 for Pars mainnet, but the live chain reports 494949 (api.pars.network
eth_chainId=0x78d65 / net_version=494949), and the map's own Pars testnet/devnet
(494950/494951) are already on the 4949xx base. Align mainnet to 494949 so the
map, its reverse lookup, and the tests agree with reality and with the rest of
the map. brand-genesis-homes.md updated to match. Dated l2-*.json inventory
snapshots left as historical records.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Was 242 lines / 4 near-identical funcs with per-chain include-lists AND
banned-lists — the redundancy bred two self-contradiction bugs (F and M each
listed a required alias in their own banned list; 'mpcvm' twice on F).
Rob Pike: data dominates. One spec table + one loop. Exact alias-set equality
IS the whole invariant — it subsumes must-include, must-not-claim-the-other's,
and no-collision in a single assertion, and makes contradictory lists
impossible by construction. Exact-match also surfaced K-Chain's real alias set
{k,key,keyvm,kms} that the lenient old test never pinned.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The last thresholdvm braid: F-Chain and M-Chain shared constants.ThresholdVMID
and were disambiguated by BlockchainName string-match (mode dispatch). Flip to
distinct VMs per LP-134/LP-7050:
- builder.go:604 F-Chain VMID ThresholdVMID -> FHEVMID
- builder.go:608 M-Chain VMID ThresholdVMID -> MPCVMID
- alias map + Aliases() now dispatch directly on FHEVMID/MPCVMID (no
BlockchainName string-match, no shared-VM path)
- zero ThresholdVMID remains in live genesis wiring
Shard JSON 'vm' fields left as-is to preserve per-chain GenesisData byte-stability
(routing is by builder VMID). Fixed a pre-existing self-contradictory test
(TestLP134_MChainIsMPCOnly required AND banned alias 'mpcvm').
Gate: configs + builder suites green (chainID locks + LP-134 split tests pass).
Prod unaffected — node pins genesis v1.13.16 (T-model); this is un-adopted branch
state pending a coordinated node release.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The genesis/builder submodule go.sum pinned the pre-retag h1 hash
(pFlQm1...) for luxfi/pq v1.0.3, which no longer matches the module the
proxy and the rest of the fleet resolve. Every active repo that depends on
pq v1.0.3 (api, chains, cli, consensus, dex, evm, node, ...) records the
current hash ksw1dmf...; only a few stale/archived trees still carry the
old one. This submodule was one of the stale ones, which broke
`go build`/`go test` in the builder module with a checksum-mismatch
SECURITY ERROR. Reconcile to the fleet-canonical hash (verified against the
already-extracted local module cache). Dependency-hygiene only; no genesis
content, chain ID, or code changes.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Devnet had all precompiles at genesis-0 while mainnet/testnet gate the new-feature
wave at 1766708400. One and one way only: all three networks now share the single
canonical fork-activation schedule. This is also what makes RLP re-import clean
(pre-activation blocks re-execute with new precompiles inactive).
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
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>
--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>
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)
The legacy T-Chain conflated two orthogonal threshold primitives on one
ThresholdVM substrate. LP-134 decomplects them into two distinct
primary-network chains, both served by the shared ThresholdVM library:
F-Chain (F/fhe/fhevm) — threshold FHE, confidential compute / off-EVM
threshold DECRYPT. ThresholdVM in FHE mode.
M-Chain (M/mpc/mpcvm) — threshold MPC, CGGMP21/FROST bridge-custody
SIGNING. ThresholdVM in MPC mode. EVM verify
surface = 0x0800..02 (FROST) + 0x0800..03
(CGGMP21), verify-only, ceremony off-EVM.
Forward-only: tchain.json is retired on all 4 nets (T was never bootstrapped
with live state — a clean 404 forward-split, no state migration). F-Chain
occupies T's vacated shard slot (same VM, keeps Z/G/K byte-stable); M-Chain
is appended. Fresh non-colliding chainIds at the next free indices 9/10
(F: 97269/97268/97270/32327, M: 97369/97368/97370/32437); T's 96869-series
is not recycled.
K-Chain decomplect ("K stays keyvm" per LP-134): the M/mpc/mpcvm aliases and
MPC params were erroneously carried on the KeyVM shard (the "mpc->K backwards"
wiring). Moved them to M-Chain; kchain.json is now pure KMS key management.
Chain-level wiring (one way, data-driven by shard presence):
- pkg/genesis: ConfigOutput/Config gain FChainGenesis (T's slot) +
MChainGenesis; TChainGenesis removed.
- configs.go chainSet/primaryChainShardFiles/slots + config.go
optionalChainShards: tchain.json -> fchain.json, append mchain.json.
- builder: F and M both map to constants.ThresholdVMID (shared substrate)
and are disambiguated in Aliases() by CreateChainTx.BlockchainName.
- chain_mapping role taxonomy: RoleThreshold("T") retired -> RoleFHE("F")
+ RoleMPC("M"); RoleKMS unchanged. (Canonical native FChainID/MChainID
await a luxfi/ids change; the closed P/C/X/Q/A/B/T/Z/G/I/K/D set is
out of scope here. Active path pins EVM chainIds in the shards.)
Tests: chain_shards_test updated to 11 chains + new F/M chainIds; builder
emits 11 chains; new lp134_split_test asserts the decomplect invariants
(F is FHE-only, M is MPC-only, K is KMS-only, no alias collision across
{F,M,K}, tchain.json retired). go build/vet/test green (GOWORK=off).
github.com/luxfi/constants@v1.5.7 and github.com/luxfi/crypto@v1.19.15 had
their git tags re-cut upstream (same go.mod, new module-zip content), so the
pinned h1: content hashes no longer matched what GOPROXY=direct serves and
GOWORK=off builds failed the checksum gate. Refresh the two content hashes to
the current tag state (versions unchanged). Pre-existing environmental drift,
independent of the LP-134 split that follows.
The strictPQTimestamp networkUpgradeOverride drove the EVM strict-PQ
precompile-refusal gate, which is removed (luxfi/evm enable-everything:
the field no longer exists on extras.NetworkUpgrades, so it is silently
ignored). Drop it from every env's upgrade.json so the canonical config
matches the new policy — builder precompile surface enables everything;
Lux PQ security is at consensus, not by refusing EVM verifiers.
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.
20 precompiles (p3qVerify, pulsar/magnetar/corona/hqc, curve25519/x25519/sr25519,
babyjubjub/pedersen/poseidon/pasta/xwing, computeMarket/bridgeRegistrar/stableSwap/
vrf/attestation/anchor/fixedPointMath) were forward-dated to 2026-07-01; realigned to
the canonical Dec-25-2025 activation (1766708400) that testnet already uses. All
networks now activate the full surface as of Dec 25 2025 — zero future-dated upgrades.
The mainnet/testnet C-Chain upgrade.json scheduled 17 already-live
precompiles (aiMiningConfig, blake3Config, cggmp21Verify, deadZeroConfig,
deadConfig, deadFullConfig, routerConfig, fheConfig, frostVerify,
graphConfig, hpkeConfig, mldsaVerify, mlkemConfig, pqcryptoConfig,
ringConfig, slhdsaVerify, zkConfig) at blockTimestamp 1766708400 — a
future (2025-12-26) timestamp. On the running mainnet these precompiles
are active at BLOCK 0 (the deployed luxd UPGRADE_JSON in luxfi/universe
k8s/lux-mainnet/luxd-startup.yaml has always carried them at 0).
Leaving the canonical genesis file at 1766708400 is a relaunch fork /
boot-refusal hazard: a relaunch from the canonical genesis would treat
each as INACTIVE before that timestamp and diverge from the canonical
chain where it is active at block 0 — or checkPrecompileCompatible would
refuse boot ("missing PrecompileUpgrade"). Pin all 17 to blockTimestamp 0
so IsForkTransition(0, nil, genesisTime) = (0 <= genesisTime) activates
them idempotently at block 0 on any relaunch, matching the deployed set.
warpConfig is unchanged in intent: it lives in the genesis chainConfig
(cchain.json config.warpConfig @ genesis time 1730446786,
requirePrimaryNetworkSigners=false). The upgrade schedule keeps its
genuine FUTURE strict-PQ toggle (disable@1766708399, re-enable@1766708400
with quorumNumerator=67, requirePrimaryNetworkSigners=true). The array is
reordered so all blockTimestamp:0 entries lead, preserving the
non-decreasing monotonicity rule in verifyPrecompileUpgrades. Every
non-zero timestamp is strictly greater than the genesis time, so a
relaunch re-fires no already-applied activation.
UPGRADE_NOTES.md documents the live-at-0 / strict-PQ-toggle / BLS /
Quasar-Edition tiers and the relaunch-safety contract.
node v1.30.27/28 dropped the 0x9010 DEX precompile; its config key
`dexConfig` is no longer registered, so any C-Chain upgrade/genesis
config carrying it is rejected at boot with "unknown precompile config:
dexConfig" -> C-Chain VM init fails. Remove the dead dexConfig entry from
every per-env upgrade.json and the devnet cchain.json. 0x9999 (live DEX
settlement) is config-free, so no key is added; routerConfig and all
other registered keys are preserved. Verified each file remains valid
JSON with dexConfig absent.
Files:
- configs/{mainnet,testnet,devnet,local,localnet}/upgrade.json
- configs/devnet/cchain.json
Replaces non-canonical scale-set / org-prefixed labels with the
existing labels every arcd host registers with. Matches evo for
amd64 and spark for arm64. No new labels added.