genesis: split T-Chain into F-Chain (FHE) + M-Chain (MPC) per LP-134

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).
This commit is contained in:
zeekay
2026-06-28 07:40:05 -07:00
parent 3aef5af704
commit b81715a57a
25 changed files with 569 additions and 172 deletions
+42 -20
View File
@@ -56,21 +56,24 @@ var (
AChainAliases = []string{"A", "ai", "aivm"}
// BChainAliases are the default aliases for the B-Chain (Bridge)
BChainAliases = []string{"B", "bridge", "bridgevm"}
// TChainAliases are the default aliases for the T-Chain (Threshold-FHE).
// "F" / "fhe" alias the threshold-FHE chain since this is the
// encrypted-compute chain in conceptual letter-chain taxonomy
// (LP-134 / "F-Chain" in user-facing docs). The on-disk shard
// stays tchain.json — letter is decomplected from product name.
TChainAliases = []string{"T", "threshold", "thresholdvm", "F", "fhe", "fhevm"}
// FChainAliases are the default aliases for the F-Chain (threshold-FHE).
// LP-134: F-Chain runs the ThresholdVM substrate in FHE mode (confidential
// compute / off-EVM threshold DECRYPT). It supersedes the retired T-Chain;
// the legacy "T"/"threshold" labels now belong only to teleportvm
// (LP-6332). On-disk shard: fchain.json.
FChainAliases = []string{"F", "fhe", "fhevm"}
// ZChainAliases are the default aliases for the Z-Chain (ZK)
ZChainAliases = []string{"Z", "zk", "zkvm"}
// GChainAliases are the default aliases for the G-Chain (Graph)
GChainAliases = []string{"G", "graph", "graphvm"}
// KChainAliases are the default aliases for the K-Chain (KMS / MPC).
// "M" / "mpc" alias the KMS chain since this is the MPC-topology
// chain in conceptual letter-chain taxonomy (LP-134 / "M-Chain"
// in user-facing docs). The on-disk shard stays kchain.json.
KChainAliases = []string{"K", "kms", "kmsvm", "M", "mpc", "mpcvm"}
// KChainAliases are the default aliases for the K-Chain (KMS).
// LP-134: "K stays keyvm" — the threshold-MPC labels M/mpc/mpcvm moved off
// K onto the M-Chain (MChainAliases). K-Chain is pure KMS key management.
KChainAliases = []string{"K", "key", "keyvm", "kms", "kmsvm"}
// MChainAliases are the default aliases for the M-Chain (threshold-MPC).
// LP-134: M-Chain runs the ThresholdVM substrate in MPC mode (CGGMP21 /
// FROST threshold SIGNING for bridge custody). On-disk shard: mchain.json.
MChainAliases = []string{"M", "mpc", "mpcvm"}
// VMAliases are the default aliases for VMs
VMAliases = map[ids.ID][]string{
@@ -576,10 +579,15 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
// chains exist is a function of which shards the operator shipped,
// not a runtime knob.
//
// Order is fixed (X→C→D→Q→A→B→T→Z→G→K) and preserved across
// Order is fixed (X→C→D→Q→A→B→F→Z→G→K→M) and preserved across
// presence/absence so byte-identical genesis holds when the same
// shard set is supplied. Append-only — reordering shifts the
// P-Chain genesis byte layout.
//
// LP-134: F-Chain and M-Chain both run the ThresholdVM substrate (F in
// FHE mode, M in MPC mode) — they share constants.ThresholdVMID and are
// disambiguated by BlockchainName. F-Chain occupies the retired T-Chain's
// slot (same VM); M-Chain is appended, keeping Z/G/K byte-stable.
chainEntries := []struct {
GenesisData []byte
VMID ids.ID
@@ -593,10 +601,11 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
{GenesisData: []byte(config.QChainGenesis), VMID: constants.QuantumVMID, Name: "Q-Chain"},
{GenesisData: []byte(config.AChainGenesis), VMID: constants.AIVMID, Name: "A-Chain"},
{GenesisData: []byte(config.BChainGenesis), VMID: constants.BridgeVMID, Name: "B-Chain"},
{GenesisData: []byte(config.TChainGenesis), VMID: constants.ThresholdVMID, Name: "T-Chain"},
{GenesisData: []byte(config.FChainGenesis), VMID: constants.ThresholdVMID, Name: "F-Chain"},
{GenesisData: []byte(config.ZChainGenesis), VMID: constants.ZKVMID, Name: "Z-Chain"},
{GenesisData: []byte(config.GChainGenesis), VMID: constants.GraphVMID, Name: "G-Chain"},
{GenesisData: []byte(config.KChainGenesis), VMID: constants.KeyVMID, Name: "K-Chain"},
{GenesisData: []byte(config.MChainGenesis), VMID: constants.ThresholdVMID, Name: "M-Chain"},
}
chains := []genesis.Chain{}
for _, e := range chainEntries {
@@ -787,12 +796,25 @@ func Aliases(genesisBytes []byte) (map[string][]string, map[ids.ID][]string, err
}
chainAliases[chainID] = BChainAliases
case constants.ThresholdVMID:
apiAliases[endpoint] = []string{
"T", "threshold", "thresholdvm",
path.Join(constants.ChainAliasPrefix, "T"),
path.Join(constants.ChainAliasPrefix, "threshold"),
// LP-134: F-Chain (FHE mode) and M-Chain (MPC mode) both run the
// ThresholdVM substrate, so they share constants.ThresholdVMID.
// They are distinct chains — disambiguate by the CreateChainTx's
// BlockchainName (set from the chain entry Name in FromConfig).
if uChain.BlockchainName == "M-Chain" {
apiAliases[endpoint] = []string{
"M", "mpc", "mpcvm",
path.Join(constants.ChainAliasPrefix, "M"),
path.Join(constants.ChainAliasPrefix, "mpc"),
}
chainAliases[chainID] = MChainAliases
} else {
apiAliases[endpoint] = []string{
"F", "fhe", "fhevm",
path.Join(constants.ChainAliasPrefix, "F"),
path.Join(constants.ChainAliasPrefix, "fhe"),
}
chainAliases[chainID] = FChainAliases
}
chainAliases[chainID] = TChainAliases
case constants.ZKVMID:
apiAliases[endpoint] = []string{
"Z", "zk", "zkvm",
@@ -809,9 +831,9 @@ func Aliases(genesisBytes []byte) (map[string][]string, map[ids.ID][]string, err
chainAliases[chainID] = GChainAliases
case constants.KeyVMID:
apiAliases[endpoint] = []string{
"K", "kms", "kmsvm",
"K", "key", "keyvm", "kms",
path.Join(constants.ChainAliasPrefix, "K"),
path.Join(constants.ChainAliasPrefix, "kms"),
path.Join(constants.ChainAliasPrefix, "key"),
}
chainAliases[chainID] = KChainAliases
}
+3 -2
View File
@@ -208,7 +208,7 @@ func TestFromConfig_ChainSetIsShardDriven(t *testing.T) {
full, err := pgenesis.Parse(pgc, bytes)
require.NoError(t, err)
require.Len(t, full.Chains, 10, "mainnet emits all 10 primary chains")
require.Len(t, full.Chains, 11, "mainnet emits all 11 primary chains (LP-134: X,C,D,Q,A,B,F,Z,G,K,M)")
// Strip every chain shard. Allocations + validators stay intact —
// the P-Chain itself is the foundation, not a CreateChainTx entry.
@@ -218,10 +218,11 @@ func TestFromConfig_ChainSetIsShardDriven(t *testing.T) {
cfg.QChainGenesis = ""
cfg.AChainGenesis = ""
cfg.BChainGenesis = ""
cfg.TChainGenesis = ""
cfg.FChainGenesis = ""
cfg.ZChainGenesis = ""
cfg.GChainGenesis = ""
cfg.KChainGenesis = ""
cfg.MChainGenesis = ""
bytes, utxoAssetID, err = FromConfig(cfg)
require.NoError(t, err, "P-only build must succeed")
+1 -1
View File
@@ -39,7 +39,7 @@ func TestWire_PVMGenesisRoundtrip(t *testing.T) {
gen, err := pgenesis.Parse(pgc, bytes1)
require.NoError(t, err)
require.NotNil(t, gen)
require.Equal(t, 10, len(gen.Chains), "mainnet ships all 10 primary chains")
require.Equal(t, 11, len(gen.Chains), "mainnet ships all 11 primary chains (LP-134: T split into F+M)")
// Re-marshal.
bytes2, err := gen.Bytes(pgc)
+20 -11
View File
@@ -143,12 +143,14 @@ func networkIDFromName(t *testing.T, name string) uint32 {
}
// TestGetGenesis_AllPrimaryChainsBakedIn locks the contract that every
// primary network (mainnet, testnet, devnet, localnet) ships ALL 10
// primary network (mainnet, testnet, devnet, localnet) ships ALL 11
// primary-network chain shards baked into its genesis. This means the
// chains spawn at network startup with NO post-launch CreateBlockchainTx —
// chain set is fully data-driven by which shards are committed.
//
// The 10 primary-network chains, in canonical order:
// The 11 primary-network chains, in canonical order (LP-134: the legacy
// T-Chain/ThresholdVM is retired and split into F-Chain + M-Chain, both
// served by the same ThresholdVM substrate in FHE / MPC mode respectively):
//
// X XVM exchange / UTXO asset
// C EVM contracts
@@ -156,10 +158,11 @@ func networkIDFromName(t *testing.T, name string) uint32 {
// Q QuantumVM post-quantum consensus (Quasar / ML-DSA / ML-KEM)
// A AIVM AI verification / attestation
// B BridgeVM cross-chain bridge (MPC signing)
// T ThresholdVM threshold FHE encrypted compute (LP-?? F-chain semantic)
// F ThresholdVM threshold-FHE confidential compute (LP-134; FHE mode, T's slot)
// Z ZKVM zero-knowledge proofs / private state
// G GraphVM graph database
// K KeyVM KMS — MPC topology (LP-?? M-chain semantic)
// K KeyVM KMS — key management
// M ThresholdVM threshold-MPC bridge-custody signing (LP-134; MPC mode)
//
// P-Chain is the primary network itself and carries the validator set + chain
// registry; it has no shard slot because it isn't a CreateChainTx entry.
@@ -177,10 +180,11 @@ func TestGetGenesis_AllPrimaryChainsBakedIn(t *testing.T) {
"qChainGenesis",
"aChainGenesis",
"bChainGenesis",
"tChainGenesis",
"fChainGenesis",
"zChainGenesis",
"gChainGenesis",
"kChainGenesis",
"mChainGenesis",
}
for _, name := range []string{"mainnet", "testnet", "devnet", "localnet"} {
t.Run(name, func(t *testing.T) {
@@ -214,11 +218,16 @@ func TestGetGenesis_AllPrimaryChainsBakedIn(t *testing.T) {
// for the D/Q/A/... shards below; C is the one cell pinned by the
// canonical map rather than derived from the arithmetic.
//
// LP-134: the legacy T-Chain (idx 5, 96869-series) is retired. F-Chain and
// M-Chain take the next free indices 9 and 10 — fresh IDs, T's are not
// recycled. (localnet base = 31337 + 110*idx.)
//
// This produces:
//
// C(96369/96368/96367), D(96469/96468/96470), Q(96569/96568/96570),
// A(96669/96668/96670), B(96769/96768/96770), T(96869/96868/96870),
// Z(96969/96968/96970), G(97069/97068/97070), K(97169/97168/97170)
// A(96669/96668/96670), B(96769/96768/96770), F(97269/97268/97270),
// Z(96969/96968/96970), G(97069/97068/97070), K(97169/97168/97170),
// M(97369/97368/97370)
//
// Each ID is unique across {network × letter} so a misrouted tx cannot
// be replayed against the wrong chain.
@@ -226,19 +235,19 @@ func TestPrimaryChainShards_PerChainCanonicalChainID(t *testing.T) {
want := map[string]map[string]int{
"mainnet": {
"d": 96469, "q": 96569, "a": 96669, "b": 96769,
"t": 96869, "z": 96969, "g": 97069, "k": 97169,
"f": 97269, "z": 96969, "g": 97069, "k": 97169, "m": 97369,
},
"testnet": {
"d": 96468, "q": 96568, "a": 96668, "b": 96768,
"t": 96868, "z": 96968, "g": 97068, "k": 97168,
"f": 97268, "z": 96968, "g": 97068, "k": 97168, "m": 97368,
},
"devnet": {
"d": 96470, "q": 96570, "a": 96670, "b": 96770,
"t": 96870, "z": 96970, "g": 97070, "k": 97170,
"f": 97270, "z": 96970, "g": 97070, "k": 97170, "m": 97370,
},
"localnet": {
"d": 31447, "q": 31557, "a": 31667, "b": 31777,
"t": 31887, "z": 31997, "g": 32107, "k": 32217,
"f": 32327, "z": 31997, "g": 32107, "k": 32217, "m": 32437,
},
}
for net, byLetter := range want {
+17 -7
View File
@@ -201,7 +201,7 @@ func loadEmbeddedGenesisWithDynamic(networkName string, dynamicPChain *genesis.P
// Runtime gate is luxd's --track-chains, not bake-time env knobs.
//
// To run a P-only network (a regulated securities L1, etc.), ship a config tree
// that omits every {x,c,d,q,a,b,t,z,g,k}chain.json. No knob, no
// that omits every {x,c,d,q,a,b,f,z,g,k,m}chain.json. No knob, no
// hack — chain set is purely data-driven by which shards are
// present.
chainShards, err := loadAllChainShards(networkName)
@@ -237,10 +237,11 @@ func loadEmbeddedGenesisWithDynamic(networkName string, dynamicPChain *genesis.P
QChainGenesis: chainShards.Q,
AChainGenesis: chainShards.A,
BChainGenesis: chainShards.B,
TChainGenesis: chainShards.T,
FChainGenesis: chainShards.F,
ZChainGenesis: chainShards.Z,
GChainGenesis: chainShards.G,
KChainGenesis: chainShards.K,
MChainGenesis: chainShards.M,
SecurityProfile: securityProfile,
CertPolicy: certPolicy,
Message: network.Message,
@@ -255,13 +256,19 @@ func loadEmbeddedGenesisWithDynamic(networkName string, dynamicPChain *genesis.P
// in the builder's chains slice — the operator's filesystem is the
// declarative source of truth for which primary-network chains exist.
type chainSet struct {
X, C, D, Q, A, B, T, Z, G, K string
X, C, D, Q, A, B, F, Z, G, K, M string
}
// primaryChainShardFiles lists every primary-network chain shard
// filename in canonical order. Order matters: builder.FromConfig
// preserves it when assembling the chains slice, so changing this
// list shifts the P-Chain genesis byte layout. Append-only.
//
// LP-134: the legacy tchain.json (ThresholdVM) is retired; the threshold
// substrate is split into fchain.json (F-Chain, threshold-FHE) — which
// takes T's vacated slot since it runs the same ThresholdVM — and
// mchain.json (M-Chain, threshold-MPC bridge custody), appended at the
// end. Z/G/K positions are unchanged so their genesis layout is stable.
var primaryChainShardFiles = [...]string{
"xchain.json",
"cchain.json",
@@ -269,10 +276,11 @@ var primaryChainShardFiles = [...]string{
"qchain.json",
"achain.json",
"bchain.json",
"tchain.json",
"fchain.json",
"zchain.json",
"gchain.json",
"kchain.json",
"mchain.json",
}
// chainSetSlots returns the address-of-field slots for s in the
@@ -280,7 +288,7 @@ var primaryChainShardFiles = [...]string{
// FS loaders to bind shard files to ConfigOutput fields without
// repeating the per-chain switch.
func (s *chainSet) slots() []*string {
return []*string{&s.X, &s.C, &s.D, &s.Q, &s.A, &s.B, &s.T, &s.Z, &s.G, &s.K}
return []*string{&s.X, &s.C, &s.D, &s.Q, &s.A, &s.B, &s.F, &s.Z, &s.G, &s.K, &s.M}
}
// loadAllChainShards reads every primary-network chain shard from the
@@ -535,10 +543,11 @@ func buildCanonicalGenesisFromSplitFiles(networkName string) ([]byte, error) {
QChainGenesis: chainShards.Q,
AChainGenesis: chainShards.A,
BChainGenesis: chainShards.B,
TChainGenesis: chainShards.T,
FChainGenesis: chainShards.F,
ZChainGenesis: chainShards.Z,
GChainGenesis: chainShards.G,
KChainGenesis: chainShards.K,
MChainGenesis: chainShards.M,
Message: network.Message,
}
@@ -642,10 +651,11 @@ func buildGenesisFromDir(dir string) ([]byte, error) {
QChainGenesis: chainShards.Q,
AChainGenesis: chainShards.A,
BChainGenesis: chainShards.B,
TChainGenesis: chainShards.T,
FChainGenesis: chainShards.F,
ZChainGenesis: chainShards.Z,
GChainGenesis: chainShards.G,
KChainGenesis: chainShards.K,
MChainGenesis: chainShards.M,
SecurityProfile: securityProfile,
Message: network.Message,
}
+21
View File
@@ -0,0 +1,21 @@
{
"aliases": [
"F",
"fhe",
"fhevm"
],
"description": "Threshold FHE confidential compute — off-EVM threshold DECRYPT (TFHE, ring N=1024 q=12289)",
"dkgParties": 10,
"dkgThreshold": 7,
"fhePrecompilePrefix": "0x07",
"message": "Lux F-Chain Genesis",
"mode": "fhe",
"name": "F-Chain",
"networkId": 3,
"ringDegreeN": 1024,
"ringModulusQ": 12289,
"timestamp": 1730531602,
"version": 1,
"vm": "ThresholdVM",
"chainId": 97270
}
+6 -7
View File
@@ -1,17 +1,16 @@
{
"aliases": [
"M",
"mpc",
"mpcvm"
"K",
"key",
"keyvm",
"kms"
],
"description": "KMS \u2014 MPC key management + threshold sigs",
"description": "KMS key management (ML-KEM / ML-DSA / BLS)",
"message": "Lux K-Chain Genesis",
"mpcParties": 10,
"mpcThreshold": 7,
"name": "K-Chain",
"networkId": 3,
"timestamp": 1730531602,
"version": 1,
"vm": "KeyVM",
"chainId": 97170
}
}
+22
View File
@@ -0,0 +1,22 @@
{
"aliases": [
"M",
"mpc",
"mpcvm"
],
"description": "Threshold MPC signing — off-EVM CGGMP21/FROST ceremony for bridge custody of external wallets",
"message": "Lux M-Chain Genesis",
"mode": "mpc",
"mpcParties": 10,
"mpcThreshold": 7,
"mpcVerifyPrecompiles": [
"0x0800000000000000000000000000000000000002",
"0x0800000000000000000000000000000000000003"
],
"name": "M-Chain",
"networkId": 3,
"timestamp": 1730531602,
"version": 1,
"vm": "ThresholdVM",
"chainId": 97370
}
-19
View File
@@ -1,19 +0,0 @@
{
"aliases": [
"F",
"fhe",
"fhevm"
],
"description": "Threshold FHE encrypted compute (ring N=1024 q=12289)",
"dkgParties": 10,
"dkgThreshold": 7,
"message": "Lux T-Chain Genesis",
"name": "T-Chain",
"networkId": 3,
"ringDegreeN": 1024,
"ringModulusQ": 12289,
"timestamp": 1730531602,
"version": 1,
"vm": "ThresholdVM",
"chainId": 96870
}
+21
View File
@@ -0,0 +1,21 @@
{
"aliases": [
"F",
"fhe",
"fhevm"
],
"description": "Threshold FHE confidential compute — off-EVM threshold DECRYPT (TFHE, ring N=1024 q=12289)",
"dkgParties": 10,
"dkgThreshold": 7,
"fhePrecompilePrefix": "0x07",
"message": "Lux F-Chain Genesis",
"mode": "fhe",
"name": "F-Chain",
"networkId": 1337,
"ringDegreeN": 1024,
"ringModulusQ": 12289,
"timestamp": 1735689600,
"version": 1,
"vm": "ThresholdVM",
"chainId": 32327
}
+6 -7
View File
@@ -1,17 +1,16 @@
{
"aliases": [
"M",
"mpc",
"mpcvm"
"K",
"key",
"keyvm",
"kms"
],
"description": "KMS \u2014 MPC key management + threshold sigs",
"description": "KMS key management (ML-KEM / ML-DSA / BLS)",
"message": "Lux K-Chain Genesis",
"mpcParties": 10,
"mpcThreshold": 7,
"name": "K-Chain",
"networkId": 1337,
"timestamp": 1735689600,
"version": 1,
"vm": "KeyVM",
"chainId": 32217
}
}
+22
View File
@@ -0,0 +1,22 @@
{
"aliases": [
"M",
"mpc",
"mpcvm"
],
"description": "Threshold MPC signing — off-EVM CGGMP21/FROST ceremony for bridge custody of external wallets",
"message": "Lux M-Chain Genesis",
"mode": "mpc",
"mpcParties": 10,
"mpcThreshold": 7,
"mpcVerifyPrecompiles": [
"0x0800000000000000000000000000000000000002",
"0x0800000000000000000000000000000000000003"
],
"name": "M-Chain",
"networkId": 1337,
"timestamp": 1735689600,
"version": 1,
"vm": "ThresholdVM",
"chainId": 32437
}
-19
View File
@@ -1,19 +0,0 @@
{
"aliases": [
"F",
"fhe",
"fhevm"
],
"description": "Threshold FHE encrypted compute (ring N=1024 q=12289)",
"dkgParties": 10,
"dkgThreshold": 7,
"message": "Lux T-Chain Genesis",
"name": "T-Chain",
"networkId": 1337,
"ringDegreeN": 1024,
"ringModulusQ": 12289,
"timestamp": 1735689600,
"version": 1,
"vm": "ThresholdVM",
"chainId": 31887
}
+238
View File
@@ -0,0 +1,238 @@
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package configs
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
// LP-134 chain-level decomplect invariants.
//
// The legacy T-Chain (ThresholdVM serving BOTH threshold-FHE and
// threshold-MPC) is split into two orthogonal chains — F-Chain (FHE only,
// confidential DECRYPT) and M-Chain (MPC only, CGGMP21/FROST bridge-custody
// SIGNING) — plus a K-Chain (KeyVM/KMS) cleaned of the MPC conflation
// ("K stays keyvm"). Both F and M run the same ThresholdVM substrate but are
// distinct chains with disjoint purposes, params, precompile families, and
// aliases. These tests fail closed if any concern leaks across the split:
// - no MPC field/alias/precompile-family on F-Chain,
// - no FHE field/alias/precompile-family on M-Chain,
// - no alias claimed by two of {F, M, K},
// - the retired tchain.json is gone (forward-only).
var lp134Nets = []string{"mainnet", "testnet", "devnet", "localnet"}
func lp134ShardPath(net, letter string) string {
return filepath.Join("..", "configs", net, letter+"chain.json")
}
func lp134ReadShard(t *testing.T, net, letter string) map[string]any {
t.Helper()
data, err := os.ReadFile(lp134ShardPath(net, letter))
if err != nil {
t.Fatalf("%s/%schain.json: %v", net, letter, err)
}
var m map[string]any
if err := json.Unmarshal(data, &m); err != nil {
t.Fatalf("%s/%schain.json parse: %v", net, letter, err)
}
return m
}
func lp134ShardText(t *testing.T, net, letter string) string {
t.Helper()
data, err := os.ReadFile(lp134ShardPath(net, letter))
if err != nil {
t.Fatalf("%s/%schain.json: %v", net, letter, err)
}
return string(data)
}
func lp134AliasSet(t *testing.T, m map[string]any) map[string]bool {
t.Helper()
raw, ok := m["aliases"].([]any)
if !ok {
t.Fatalf("aliases missing or wrong type: %T", m["aliases"])
}
out := make(map[string]bool, len(raw))
for _, a := range raw {
s, _ := a.(string)
out[strings.ToLower(s)] = true
}
return out
}
// TestLP134_TChainRetired: the legacy tchain.json is gone on every net
// (forward-only split — F-Chain + M-Chain supersede it).
func TestLP134_TChainRetired(t *testing.T) {
for _, net := range lp134Nets {
if _, err := os.Stat(lp134ShardPath(net, "t")); !os.IsNotExist(err) {
t.Fatalf("%s/tchain.json must be retired (LP-134 forward-only split); stat err=%v", net, err)
}
}
}
// TestLP134_FChainIsFHEOnly: F-Chain carries FHE concepts only — ThresholdVM
// in FHE mode, FHE ring params, the 0x07 FHE precompile family, and NO MPC
// field, alias, or 0x08 (threshold-signature) precompile-family reference.
func TestLP134_FChainIsFHEOnly(t *testing.T) {
for _, net := range lp134Nets {
t.Run(net, func(t *testing.T) {
m := lp134ReadShard(t, net, "f")
if got, _ := m["vm"].(string); got != "ThresholdVM" {
t.Fatalf("F-Chain vm = %q, want ThresholdVM", got)
}
if got, _ := m["mode"].(string); got != "fhe" {
t.Fatalf("F-Chain mode = %q, want fhe", got)
}
if m["ringDegreeN"] == nil || m["ringModulusQ"] == nil {
t.Fatalf("F-Chain must carry the FHE ring params (ringDegreeN/ringModulusQ)")
}
if got, _ := m["fhePrecompilePrefix"].(string); got != "0x07" {
t.Fatalf("F-Chain fhePrecompilePrefix = %q, want 0x07", got)
}
// Decomplect: no MPC concepts on F-Chain.
for _, banned := range []string{"mpcParties", "mpcThreshold", "mpcVerifyPrecompiles"} {
if m[banned] != nil {
t.Fatalf("F-Chain must not carry MPC field %q (LP-134 decomplect)", banned)
}
}
if strings.Contains(lp134ShardText(t, net, "f"), "0x08") {
t.Fatalf("F-Chain must not reference the 0x08 threshold-signature precompile family")
}
al := lp134AliasSet(t, m)
for _, want := range []string{"f", "fhe", "fhevm"} {
if !al[want] {
t.Fatalf("F-Chain aliases must include %q", want)
}
}
for _, banned := range []string{"m", "mpc", "mpcvm", "t", "threshold", "thresholdvm"} {
if al[banned] {
t.Fatalf("F-Chain must not claim alias %q", banned)
}
}
})
}
}
// TestLP134_MChainIsMPCOnly: M-Chain carries MPC concepts only — ThresholdVM
// in MPC mode, MPC params, the CGGMP21 (0x0800..03) + FROST (0x0800..02)
// verify precompiles, and NO FHE field, alias, or 0x07 precompile-family ref.
func TestLP134_MChainIsMPCOnly(t *testing.T) {
const (
frostAddr = "0x0800000000000000000000000000000000000002"
cggmp21Addr = "0x0800000000000000000000000000000000000003"
)
for _, net := range lp134Nets {
t.Run(net, func(t *testing.T) {
m := lp134ReadShard(t, net, "m")
if got, _ := m["vm"].(string); got != "ThresholdVM" {
t.Fatalf("M-Chain vm = %q, want ThresholdVM", got)
}
if got, _ := m["mode"].(string); got != "mpc" {
t.Fatalf("M-Chain mode = %q, want mpc", got)
}
if m["mpcParties"] == nil || m["mpcThreshold"] == nil {
t.Fatalf("M-Chain must carry the MPC params (mpcParties/mpcThreshold)")
}
// EVM verify surface: exactly the two threshold-signature precompiles.
pcs, ok := m["mpcVerifyPrecompiles"].([]any)
if !ok || len(pcs) != 2 {
t.Fatalf("M-Chain must declare its 2 MPC verify precompiles, got %v", m["mpcVerifyPrecompiles"])
}
seen := map[string]bool{frostAddr: false, cggmp21Addr: false}
for _, p := range pcs {
ps, _ := p.(string)
ps = strings.ToLower(ps)
if _, known := seen[ps]; !known {
t.Fatalf("M-Chain unexpected precompile %q (want only FROST/CGGMP21)", ps)
}
seen[ps] = true
}
for addr, ok := range seen {
if !ok {
t.Fatalf("M-Chain missing MPC verify precompile %s", addr)
}
}
// Decomplect: no FHE concepts on M-Chain.
for _, banned := range []string{"ringDegreeN", "ringModulusQ", "dkgParties", "dkgThreshold", "fhePrecompilePrefix"} {
if m[banned] != nil {
t.Fatalf("M-Chain must not carry FHE field %q (LP-134 decomplect)", banned)
}
}
if strings.Contains(lp134ShardText(t, net, "m"), "0x07") {
t.Fatalf("M-Chain must not reference the 0x07 FHE precompile family")
}
al := lp134AliasSet(t, m)
for _, want := range []string{"m", "mpc", "mpcvm"} {
if !al[want] {
t.Fatalf("M-Chain aliases must include %q", want)
}
}
for _, banned := range []string{"f", "fhe", "fhevm", "t", "threshold", "thresholdvm"} {
if al[banned] {
t.Fatalf("M-Chain must not claim alias %q", banned)
}
}
})
}
}
// TestLP134_KChainIsKMSOnly: K-Chain is pure KeyVM/KMS — the M/mpc aliases and
// MPC params moved to M-Chain ("K stays keyvm" per LP-134).
func TestLP134_KChainIsKMSOnly(t *testing.T) {
for _, net := range lp134Nets {
t.Run(net, func(t *testing.T) {
m := lp134ReadShard(t, net, "k")
if got, _ := m["vm"].(string); got != "KeyVM" {
t.Fatalf("K-Chain vm = %q, want KeyVM", got)
}
for _, banned := range []string{"mpcParties", "mpcThreshold", "mpcVerifyPrecompiles", "mode", "ringDegreeN"} {
if m[banned] != nil {
t.Fatalf("K-Chain must not carry %q (LP-134: MPC/FHE concerns live on M/F)", banned)
}
}
al := lp134AliasSet(t, m)
if !al["k"] {
t.Fatalf("K-Chain must claim alias K")
}
for _, banned := range []string{"m", "mpc", "mpcvm", "f", "fhe", "fhevm"} {
if al[banned] {
t.Fatalf("K-Chain must not claim alias %q (LP-134 decomplect)", banned)
}
}
})
}
}
// TestLP134_NoAliasCollision: the threshold-family chains {F, M, K} claim
// mutually-disjoint alias sets — the core decomplect invariant (a single alias
// resolving to two chains is the exact bug LP-134 removes).
func TestLP134_NoAliasCollision(t *testing.T) {
for _, net := range lp134Nets {
t.Run(net, func(t *testing.T) {
f := lp134AliasSet(t, lp134ReadShard(t, net, "f"))
mm := lp134AliasSet(t, lp134ReadShard(t, net, "m"))
k := lp134AliasSet(t, lp134ReadShard(t, net, "k"))
for _, pair := range []struct {
a, b map[string]bool
an, bn string
}{
{f, mm, "F", "M"},
{f, k, "F", "K"},
{mm, k, "M", "K"},
} {
for alias := range pair.a {
if pair.b[alias] {
t.Fatalf("%s-Chain alias %q collides with %s-Chain (LP-134: aliases must be disjoint)", pair.an, alias, pair.bn)
}
}
}
})
}
}
+21
View File
@@ -0,0 +1,21 @@
{
"aliases": [
"F",
"fhe",
"fhevm"
],
"description": "Threshold FHE confidential compute — off-EVM threshold DECRYPT (TFHE, ring N=1024 q=12289)",
"dkgParties": 10,
"dkgThreshold": 7,
"fhePrecompilePrefix": "0x07",
"message": "Lux F-Chain Genesis",
"mode": "fhe",
"name": "F-Chain",
"networkId": 1,
"ringDegreeN": 1024,
"ringModulusQ": 12289,
"timestamp": 1730446786,
"version": 1,
"vm": "ThresholdVM",
"chainId": 97269
}
+6 -7
View File
@@ -1,17 +1,16 @@
{
"aliases": [
"M",
"mpc",
"mpcvm"
"K",
"key",
"keyvm",
"kms"
],
"description": "KMS \u2014 MPC key management + threshold sigs",
"description": "KMS key management (ML-KEM / ML-DSA / BLS)",
"message": "Lux K-Chain Genesis",
"mpcParties": 10,
"mpcThreshold": 7,
"name": "K-Chain",
"networkId": 1,
"timestamp": 1730446786,
"version": 1,
"vm": "KeyVM",
"chainId": 97169
}
}
+22
View File
@@ -0,0 +1,22 @@
{
"aliases": [
"M",
"mpc",
"mpcvm"
],
"description": "Threshold MPC signing — off-EVM CGGMP21/FROST ceremony for bridge custody of external wallets",
"message": "Lux M-Chain Genesis",
"mode": "mpc",
"mpcParties": 10,
"mpcThreshold": 7,
"mpcVerifyPrecompiles": [
"0x0800000000000000000000000000000000000002",
"0x0800000000000000000000000000000000000003"
],
"name": "M-Chain",
"networkId": 1,
"timestamp": 1730446786,
"version": 1,
"vm": "ThresholdVM",
"chainId": 97369
}
-19
View File
@@ -1,19 +0,0 @@
{
"aliases": [
"F",
"fhe",
"fhevm"
],
"description": "Threshold FHE encrypted compute (ring N=1024 q=12289)",
"dkgParties": 10,
"dkgThreshold": 7,
"message": "Lux T-Chain Genesis",
"name": "T-Chain",
"networkId": 1,
"ringDegreeN": 1024,
"ringModulusQ": 12289,
"timestamp": 1730446786,
"version": 1,
"vm": "ThresholdVM",
"chainId": 96869
}
+21
View File
@@ -0,0 +1,21 @@
{
"aliases": [
"F",
"fhe",
"fhevm"
],
"description": "Threshold FHE confidential compute — off-EVM threshold DECRYPT (TFHE, ring N=1024 q=12289)",
"dkgParties": 10,
"dkgThreshold": 7,
"fhePrecompilePrefix": "0x07",
"message": "Lux F-Chain Genesis",
"mode": "fhe",
"name": "F-Chain",
"networkId": 2,
"ringDegreeN": 1024,
"ringModulusQ": 12289,
"timestamp": 1730531602,
"version": 1,
"vm": "ThresholdVM",
"chainId": 97268
}
+6 -7
View File
@@ -1,17 +1,16 @@
{
"aliases": [
"M",
"mpc",
"mpcvm"
"K",
"key",
"keyvm",
"kms"
],
"description": "KMS \u2014 MPC key management + threshold sigs",
"description": "KMS key management (ML-KEM / ML-DSA / BLS)",
"message": "Lux K-Chain Genesis",
"mpcParties": 10,
"mpcThreshold": 7,
"name": "K-Chain",
"networkId": 2,
"timestamp": 1730531602,
"version": 1,
"vm": "KeyVM",
"chainId": 97168
}
}
+22
View File
@@ -0,0 +1,22 @@
{
"aliases": [
"M",
"mpc",
"mpcvm"
],
"description": "Threshold MPC signing — off-EVM CGGMP21/FROST ceremony for bridge custody of external wallets",
"message": "Lux M-Chain Genesis",
"mode": "mpc",
"mpcParties": 10,
"mpcThreshold": 7,
"mpcVerifyPrecompiles": [
"0x0800000000000000000000000000000000000002",
"0x0800000000000000000000000000000000000003"
],
"name": "M-Chain",
"networkId": 2,
"timestamp": 1730531602,
"version": 1,
"vm": "ThresholdVM",
"chainId": 97368
}
-19
View File
@@ -1,19 +0,0 @@
{
"aliases": [
"F",
"fhe",
"fhevm"
],
"description": "Threshold FHE encrypted compute (ring N=1024 q=12289)",
"dkgParties": 10,
"dkgThreshold": 7,
"message": "Lux T-Chain Genesis",
"name": "T-Chain",
"networkId": 2,
"ringDegreeN": 1024,
"ringModulusQ": 12289,
"timestamp": 1730531602,
"version": 1,
"vm": "ThresholdVM",
"chainId": 96868
}
+37 -17
View File
@@ -14,17 +14,21 @@ import (
type ChainRole string
const (
RoleProtocol ChainRole = "P" // Protocol chain — validator set, chain registry, ordering
RoleExchange ChainRole = "X" // Exchange/asset transfer chain
RoleContract ChainRole = "C" // Contract/EVM chain
RoleQuantum ChainRole = "Q" // Quantum-resistant chain
RoleAttest ChainRole = "A" // Attestation chain
RoleBridge ChainRole = "B" // Bridge chain
RoleThreshold ChainRole = "T" // Threshold/MPC chain
RoleZK ChainRole = "Z" // Zero-knowledge chain
RoleGraph ChainRole = "G" // Graph/data chain (future)
RoleIdentity ChainRole = "I" // Identity chain (future)
RoleKMS ChainRole = "K" // KMS chain (future)
RoleProtocol ChainRole = "P" // Protocol chain — validator set, chain registry, ordering
RoleExchange ChainRole = "X" // Exchange/asset transfer chain
RoleContract ChainRole = "C" // Contract/EVM chain
RoleQuantum ChainRole = "Q" // Quantum-resistant chain
RoleAttest ChainRole = "A" // Attestation chain
RoleBridge ChainRole = "B" // Bridge chain
// LP-134: the retired RoleThreshold ("T") is split into RoleFHE ("F") and
// RoleMPC ("M"). The ThresholdVM substrate serves both — F-Chain in FHE
// mode (confidential compute) and M-Chain in MPC mode (bridge custody).
RoleFHE ChainRole = "F" // Threshold-FHE chain (confidential compute)
RoleMPC ChainRole = "M" // Threshold-MPC chain (bridge custody signing)
RoleZK ChainRole = "Z" // Zero-knowledge chain
RoleGraph ChainRole = "G" // Graph/data chain (future)
RoleIdentity ChainRole = "I" // Identity chain (future)
RoleKMS ChainRole = "K" // KMS chain (future)
)
// ChainConfig represents a single chain's configuration
@@ -228,12 +232,28 @@ func DefaultMainnetMapping() *ChainMapping {
Name: "Bridge Chain",
Aliases: []string{"B", "bridge"},
},
RoleThreshold: {
ChainID: ids.TChainID,
VMID: ids.Empty, // Threshold VM ID
Role: RoleThreshold,
Name: "Threshold Chain",
Aliases: []string{"T", "threshold", "mpc"},
RoleFHE: {
// LP-134: F-Chain runs ThresholdVM in FHE mode. The canonical
// native chain ID awaits luxfi/ids.FChainID — the ids native-
// chain set (P/C/X/Q/A/B/T/Z/G/I/K/D) is closed, so adding F/M
// is a separate luxfi/ids change. The active genesis path pins
// the EVM chainId in fchain.json (97269/97268/97270/32327).
ChainID: ids.Empty,
VMID: ids.Empty, // ThresholdVM (FHE mode)
Role: RoleFHE,
Name: "F-Chain",
Aliases: []string{"F", "fhe", "fhevm"},
},
RoleMPC: {
// LP-134: M-Chain runs ThresholdVM in MPC mode (CGGMP21 / FROST
// bridge custody). Canonical native chain ID awaits
// luxfi/ids.MChainID; the active genesis path pins the EVM
// chainId in mchain.json (97369/97368/97370/32437).
ChainID: ids.Empty,
VMID: ids.Empty, // ThresholdVM (MPC mode)
Role: RoleMPC,
Name: "M-Chain",
Aliases: []string{"M", "mpc", "mpcvm"},
},
RoleZK: {
ChainID: ids.ZChainID,
+6 -3
View File
@@ -104,10 +104,11 @@ func GetConfigFile(filepath string) (*Config, error) {
QChainGenesis: output.QChainGenesis,
AChainGenesis: output.AChainGenesis,
BChainGenesis: output.BChainGenesis,
TChainGenesis: output.TChainGenesis,
FChainGenesis: output.FChainGenesis,
ZChainGenesis: output.ZChainGenesis,
GChainGenesis: output.GChainGenesis,
KChainGenesis: output.KChainGenesis,
MChainGenesis: output.MChainGenesis,
SecurityProfile: output.SecurityProfile,
CertPolicy: output.CertPolicy,
Message: output.Message,
@@ -247,10 +248,11 @@ var optionalChainShards = []struct {
{"qchain.json", func(c *Config) *string { return &c.QChainGenesis }},
{"achain.json", func(c *Config) *string { return &c.AChainGenesis }},
{"bchain.json", func(c *Config) *string { return &c.BChainGenesis }},
{"tchain.json", func(c *Config) *string { return &c.TChainGenesis }},
{"fchain.json", func(c *Config) *string { return &c.FChainGenesis }},
{"zchain.json", func(c *Config) *string { return &c.ZChainGenesis }},
{"gchain.json", func(c *Config) *string { return &c.GChainGenesis }},
{"kchain.json", func(c *Config) *string { return &c.KChainGenesis }},
{"mchain.json", func(c *Config) *string { return &c.MChainGenesis }},
}
// readOptionalShard reads filename from dir, returning the file contents on
@@ -390,10 +392,11 @@ func ParseConfigOutput(output *ConfigOutput, networkID uint32) (*Config, error)
QChainGenesis: output.QChainGenesis,
AChainGenesis: output.AChainGenesis,
BChainGenesis: output.BChainGenesis,
TChainGenesis: output.TChainGenesis,
FChainGenesis: output.FChainGenesis,
ZChainGenesis: output.ZChainGenesis,
GChainGenesis: output.GChainGenesis,
KChainGenesis: output.KChainGenesis,
MChainGenesis: output.MChainGenesis,
Chains: output.Chains,
SecurityProfile: output.SecurityProfile,
CertPolicy: output.CertPolicy,
+9 -7
View File
@@ -26,14 +26,15 @@ type Config struct {
QChainGenesis string `json:"qChainGenesis,omitempty"` // Q-Chain: Quantum VM genesis
AChainGenesis string `json:"aChainGenesis,omitempty"` // A-Chain: Attestation/AI VM genesis
BChainGenesis string `json:"bChainGenesis,omitempty"` // B-Chain: Bridge VM genesis
TChainGenesis string `json:"tChainGenesis,omitempty"` // T-Chain: Threshold VM genesis
FChainGenesis string `json:"fChainGenesis,omitempty"` // F-Chain: Threshold-FHE VM genesis (LP-134; supersedes T-Chain)
ZChainGenesis string `json:"zChainGenesis,omitempty"` // Z-Chain: ZK VM genesis
GChainGenesis string `json:"gChainGenesis,omitempty"` // G-Chain: Graph VM genesis
KChainGenesis string `json:"kChainGenesis,omitempty"` // K-Chain: KMS VM genesis
MChainGenesis string `json:"mChainGenesis,omitempty"` // M-Chain: Threshold-MPC VM genesis (LP-134; supersedes T-Chain)
Message string `json:"message"`
// Chains defines additional chains to include in genesis beyond the
// built-in alphabet chains (C, D, Q, B, T, Z, G, K). Each entry becomes
// built-in alphabet chains (C, D, Q, A, B, F, Z, G, K, M). Each entry becomes
// a CreateChainTx in the P-Chain genesis. This allows chains to be
// embedded directly for automatic bootstrap.
Chains []ChainEntry `json:"chains,omitempty"`
@@ -73,7 +74,8 @@ type Config struct {
//
// EVMAddr is the 20-byte H160 EVM address (C-Chain and other EVM chains).
// UTXOAddr is the 20-byte ShortID used by both P-Chain and X-Chain UTXOs
// (same bytes; bech32 prefix differentiates the chain).
//
// (same bytes; bech32 prefix differentiates the chain).
type Allocation struct {
EVMAddr ids.ShortID `json:"evmAddr"`
UTXOAddr ids.ShortID `json:"utxoAddr"`
@@ -201,7 +203,6 @@ type StakerJSON struct {
EndTime uint64 `json:"endTime,omitempty"`
}
// CChainConfig is the C-Chain genesis configuration
type CChainConfig struct {
Config CChainParams `json:"config"`
@@ -308,10 +309,11 @@ type ConfigOutput struct {
QChainGenesis string `json:"qChainGenesis,omitempty"` // Q-Chain: Quantum VM genesis
AChainGenesis string `json:"aChainGenesis,omitempty"` // A-Chain: Attestation/AI VM genesis
BChainGenesis string `json:"bChainGenesis,omitempty"` // B-Chain: Bridge VM genesis
TChainGenesis string `json:"tChainGenesis,omitempty"` // T-Chain: Threshold VM genesis
FChainGenesis string `json:"fChainGenesis,omitempty"` // F-Chain: Threshold-FHE VM genesis (LP-134; supersedes T-Chain)
ZChainGenesis string `json:"zChainGenesis,omitempty"` // Z-Chain: ZK VM genesis
GChainGenesis string `json:"gChainGenesis,omitempty"` // G-Chain: Graph VM genesis
KChainGenesis string `json:"kChainGenesis,omitempty"` // K-Chain: KMS VM genesis
MChainGenesis string `json:"mChainGenesis,omitempty"` // M-Chain: Threshold-MPC VM genesis (LP-134; supersedes T-Chain)
Chains []ChainEntry `json:"chains,omitempty"` // Additional genesis chains
SecurityProfile *SecurityProfile `json:"securityProfile,omitempty"`
CertPolicy *CertPolicy `json:"certPolicy,omitempty"`
@@ -370,10 +372,11 @@ func (c *Config) ToJSON(hrp string) *ConfigOutput {
QChainGenesis: c.QChainGenesis,
AChainGenesis: c.AChainGenesis,
BChainGenesis: c.BChainGenesis,
TChainGenesis: c.TChainGenesis,
FChainGenesis: c.FChainGenesis,
ZChainGenesis: c.ZChainGenesis,
GChainGenesis: c.GChainGenesis,
KChainGenesis: c.KChainGenesis,
MChainGenesis: c.MChainGenesis,
SecurityProfile: c.SecurityProfile,
CertPolicy: c.CertPolicy,
Message: c.Message,
@@ -385,4 +388,3 @@ func (c *Config) MarshalJSON() ([]byte, error) {
hrp := constants.GetHRP(c.NetworkID)
return json.Marshal(c.ToJSON(hrp))
}