Lux Proposals
← All proposals
LP-0019Final

LP-019: Genesis Canonical Shape

Abstract

This LP fixes the shape of ~/work/lux/genesis/configs/. Lux primary

network has three coordination chains (P, X, C) at v1, each with its

own genesis blob keyed by environment (mainnet / testnet / devnet).

Each sovereign L1 (Hanzo, Zoo, Pars, Spc) has a single genesis.json

per environment carrying its EVM chain genesis. The v1 lock fixes

field names (evmAddr + utxoAddr), the precompile activation set

(canonical 18-precompile manifest), and the directory layout. A

genesis file outside this shape is a v1 regression caught by the

genesis verification suite.

Motivation

Pre-v1 the genesis directory accreted ad-hoc fields, mixed

conventions for address representation, and carried precompile

configs that no longer mapped to a live precompile binary. v1

freezes:

A genesis file that drifts from this shape fails the genesis

verification suite at

~/work/lux/genesis/configs/getgenesis_test.go.

Specification

Directory layout


~/work/lux/genesis/configs/
  mainnet/
    pchain.json          P-Chain genesis (Lux primary, networkID=1)
    xchain.json          X-Chain genesis (UTXO, network-wide settlement)
    cchain.json          C-Chain genesis (Lux primary EVM ONLY)
    bootstrappers.json   primary network bootstrappers
  testnet/
    pchain.json          (networkID=2)
    xchain.json
    cchain.json
    bootstrappers.json
  devnet/
    pchain.json          (networkID=3)
    xchain.json
    cchain.json
    bootstrappers.json
  hanzo-mainnet/genesis.json    Hanzo L1 EVM chain genesis (chainID=36963)
  hanzo-testnet/genesis.json    Hanzo testnet
  hanzo-devnet/genesis.json     Hanzo devnet
  zoo-mainnet/genesis.json      Zoo L1 EVM chain genesis (chainID=200200)
  zoo-testnet/genesis.json
  zoo-devnet/genesis.json
  pars-mainnet/genesis.json     Pars L1 EVM chain genesis (chainID=7070)
  pars-testnet/genesis.json
  pars-devnet/genesis.json
  spc-mainnet/genesis.json      Spc L1 EVM chain genesis (chainID=36911)
  spc-testnet/genesis.json
  spc-devnet/genesis.json

NO non-Lux L1 has a pchain.json. The P-Chain is Lux primary's

validator registry; sovereign L1s do not need their own. The

sovereign L1's validator set is bound at spawn time via the

atomic primitive (LP-018) and lives on Lux primary's P-Chain

registration record.

Lux primary P-Chain genesis

~/work/lux/genesis/configs/<env>/pchain.json carries the Lux

primary network's validator set and allocations. Required

top-level fields:


{
  "networkID": 1,
  "allocations": [
    {
      "ethAddr": "0x...",            // (deprecated; superseded by evmAddr in v1)
      "utxoAddr": "P-lux1...",
      "evmAddr":  "0x9011e888...",
      "initialAmount": ...,
      "unlockSchedule": [ ... ]
    },
    ...
  ],
  "initialStakers": [ ... ],
  ...
}

The v1 canonical address-field invariant: every allocation entry

carries both utxoAddr (bech32 P-Chain address) and evmAddr (20-byte

EVM address, hex). The pre-v1 ethAddr alias is retained for

on-disk compatibility but new tooling reads evmAddr exclusively.

networkID values:

| Environment | networkID |
|---|---|
| mainnet | 1 |
| testnet | 2 |
| devnet | 3 |

Setting networkID to any value in 8675xxx is a v1 violation —

those are EVM chain IDs, not primary network IDs (LP-016).

Lux primary X-Chain genesis

~/work/lux/genesis/configs/<env>/xchain.json carries the X-Chain

UTXO genesis. X-Chain is reserved for network-wide settlement of

primary-network assets. Sovereign L1s do NOT have their own

X-Chain.

Lux primary C-Chain genesis

~/work/lux/genesis/configs/<env>/cchain.json carries the C-Chain

EVM genesis. C-Chain runs Lux primary's EVM, with EVM chain ID

distinct from any sovereign L1 EVM chain ID. The

LUX_DISABLE_CCHAIN=1 env knob lives in

~/work/lux/node/config/configs.go; downstream L1 builds set it to

harden against accidental C-Chain spawning on a non-Lux primary.

Sovereign L1 genesis

~/work/lux/genesis/configs/<tenant>-<env>/genesis.json is the

ONLY genesis file each sovereign L1 carries. Its shape is the

standard EVM genesis with Lux-specific extensions:


{
  "alloc": {
    "0xPRECOMPILE_OR_USER_ADDR": {
      "balance": "0x...",
      "code": "0x...",
      "nonce": "0x..."
    },
    ...
  },
  "baseFeePerGas": "0x...",
  "coinbase": "0x0000000000000000000000000000000000000000",
  "config": {
    "chainId": <int>,            // L1's EVM chain ID, distinct per tenant
    "feeConfig": { ... },
    "warpConfig": { ... },
    "precompileUpgrades": [ ... ]    // canonical 18-precompile activation
  },
  ...
}

The alloc map funds genesis accounts to the wei. Per-L1 native

funding lives here; cross-L1 funding (e.g. tenant treasury seeded

from Lux primary) is a post-spawn bridge tx, not a genesis field.

Canonical chain-ID assignments

| Tenant L1 | EVM chain ID |
|---|---|
| Hanzo | 36963 (mainnet), 36964 (testnet), 36912 (devnet) |
| Zoo | 200200 (mainnet), 200201 (testnet), 200202 (devnet) |
| Pars | 7070 (mainnet), 7071 (testnet), 7072 (devnet) |
| Spc | 36911 (mainnet), and parallel test/dev values |

Cross-grep against

~/work/lux/genesis/configs/<tenant>-<env>/genesis.json:"chainId":

verifies these.

Canonical 18-precompile activation

The v1 lock fixes the precompile manifest a fresh sovereign L1's

precompileUpgrades array must enable. The set is keyed by config

name:

1. aiMiningConfig

2. anchorConfig

3. attestationConfig

4. babyjubjubConfig

5. blake3Config

6. bls12381G1AddConfig, bls12381G1MulConfig,

bls12381G1MSMConfig, bls12381G2AddConfig,

bls12381G2MulConfig, bls12381G2MSMConfig,

bls12381PairingConfig (counted as one BLS12-381 EIP-2537

precompile-set entry, exposed as 7 sub-configs)

7. bridgeRegistrarConfig

8. coronaThreshold (Corona PQ precompile)

9. curve25519Config

10. dexConfig (only on DEX-bearing L1s)

11. fheConfig (only on FHE-bearing L1s)

12. frostVerify

13. hpkeConfig

14. mlkemConfig (FIPS 203 ML-KEM)

15. pedersenConfig

16. poseidonConfig

17. vrfConfig

18. zkConfig

Removed in v1:

DEX-VM-only and FHE-VM-only precompile entries are conditional:

a non-DEX L1 SHOULD NOT enable dexConfig; a non-FHE L1 SHOULD

NOT enable fheConfig.

Field-naming invariant

evmAddr (lowercase EVM-address hex) and utxoAddr (bech32 P-Chain

address) are the canonical field names across every Lux primary

allocation entry. The v1 test gate at

~/work/lux/genesis/configs/getgenesis_test.go:29-30 asserts

utxoAddr presence on every allocation; evmAddr presence is

asserted in the same suite.

Downstream tenant case

White-label tenants live outside ~/work/lux/genesis/ in their own

trees and may opt out of C-Chain (LP-016) with their own EVM chain

ID space. The LUX_DISABLE_CCHAIN=1 env knob in

~/work/lux/node/config/configs.go is the load-bearing guard that

keeps C-Chain off for any node that does not need it.

bootstrap-chain CLI surface

The bootstrap-chain CLI at

~/work/lux/genesis/cmd/bootstrap-chain/main.go consumes these

configs to spin up a fresh chain. Key code paths:

The genesis CLI at ~/work/lux/genesis/cmd/genesis/main.go

emits, edits, and verifies genesis blobs. Its address-emission

path at line 249 (addrHex := fmt.Sprintf("0x%s", a.EVMAddr.Hex()))

uses the canonical field name.

The checkkeys CLI at

~/work/lux/genesis/cmd/checkkeys/main.go:39-47 cross-checks

derived EVM addresses against the genesis allocation map, using

the canonical EVMAddr field name throughout.

Rationale

Why one genesis.json per sovereign L1

A tenant L1 carries one or more chains, but the v1 spawn primitive

(LP-018) registers all chains in one atomic tx. The

genesis.json is the EVM chain's genesis; per-L1 non-EVM chains

(DEX, FHE) embed their genesis as GenesisData inside the spawn

tx's Chains[] array, not as a separate config file.

This keeps the on-disk surface minimal: one genesis file per L1

EVM chain, full stop. Non-EVM chain genesis blobs are inputs to

the spawn CLI, not standing config artifacts.

Why no per-tenant pchain.json

P-Chain is Lux primary's validator-set registry. A sovereign L1's

validator set is encoded in the spawn tx (LP-018), not in a

separate genesis file. The pre-v1 pattern of carrying a

per-tenant pchain.json was a workaround for the four-step spawn

flow; v1's atomic spawn removes the need.

Why evmAddr + utxoAddr over address / eth_addr

Both representations are present in every allocation because Lux

primary's UTXO + EVM bridge needs both views. Naming them as

evmAddr + utxoAddr makes the intent explicit and decomplects

them from chain-specific aliases (paddr, xaddr, cAddress,

etc.) that drifted across the pre-v1 tooling.

Why drop eciesConfig

ECIES (secp256k1 + AES-GCM with KDF over the shared secret) is

not PQ-secure. v1 mandates hpkeConfig (RFC 9180 HPKE, ML-KEM-768

PQ-hybrid) for fresh encryption usage. The ECIES precompile binary

remains for historical bytecode that calls it; new genesis emission

omits it.

Backwards Compatibility

None. v1 is the lock-in.

Existing on-disk tenant genesis files that carry the pre-v1 fields

(eciesConfig, ethAddr alias, etc.) remain decodable. New

emission paths (CLI, scripts, infra-as-code) write the v1 shape

exclusively.

Test Cases

A v1 compliance test added under

~/work/lux/genesis/configs/v1_canonical_shape_test.go MUST

assert:

1. Every <env>/pchain.json parses with networkID ∈ {1, 2, 3}.

2. Every <tenant>-<env>/genesis.json parses with chainId ∈

the canonical assignment table.

3. No fresh genesis.json enables eciesConfig or

coronaThreshold.

Reference Implementation

Security Considerations

The canonical 18-precompile activation pin closes a v0 footgun: a

genesis blob that silently enabled an unaudited precompile gave

that precompile EVM-execution semantics from block 0. v1's pinned

manifest means a reviewer checking the genesis sees exactly which

precompiles are active and can cross-check each against its

binary at ~/work/lux/node/precompile/.

The LUX_DISABLE_CCHAIN=1 env knob is the load-bearing guard

against the historical "C-Chain leak" pattern: a downstream

tenant accidentally inheriting C-Chain because the daemon

was the upstream luxd. The env knob is checked in

~/work/lux/node/config/configs.go and the resulting build refuses

to spawn C-Chain.

The evmAddr + utxoAddr field-naming invariant prevents a class

of address-confusion bug where tooling reads the wrong address

representation and credits funds to the wrong account-model

account. Both representations refer to the same allocation; the

naming pins which is which.

Copyright

Copyright and related rights waived via CC0.