mpcvm: docs said this was not a chain; it is M-Chain

README.md led with "ThresholdVM is a Go library, not a chain" and LLM.md
instructed agents "do not turn this back into a chain, no new VM ID",
both describing an import graph (chains/mchain, chains/fchain) that does
not exist. While that stood, the VM declared a private thresholdvm vmID
matching nothing else in the stack and the node installed the plugin
under that dead name — M-Chain could not have started.

Rewritten to describe what is actually here: the M-Chain VM, its
canonical vmID and every declaration that must agree with it, the K-vs-t
rule and why the quorum is spelled "3-of-5", the consensus/node-private
state split, what makes a block verifiable, and what is still unproven
(no cross-process DKG yet, no resharing).

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
zeekay
2026-07-25 15:09:22 -07:00
co-authored by Hanzo Dev
parent 4571c38a4a
commit 3fa525d1bd
2 changed files with 180 additions and 158 deletions
+76 -38
View File
@@ -2,49 +2,87 @@
## What this is
ThresholdVM is a **library substrate**, not a Lux chain. It is imported by:
**This package IS M-Chain.** It is the VM for the MPC threshold-custody chain:
a plugin-loaded `chain.ChainVM` with genesis, validators, block production and
replicated consensus state. It holds the keys that custody bridged assets on
external chains.
- `chains/mchain/` — MPC ceremonies (CGGMP21, FROST, Corona-general)
- `chains/fchain/` — FHE compute (TFHE bootstrap, encrypted EVM)
An earlier revision of this file said the opposite — "a library substrate, not
a Lux chain", imported by `chains/mchain/` and `chains/fchain/`. Those packages
do not exist and never did. The instruction that followed from it ("do not turn
this back into a chain, no new VM ID") was wrong and, while it stood, the VM
declared a private `thresholdvm` vmID that matched nothing else in the stack.
The node installed the plugin under that dead name's CB58 while genesis
declared `constants.MPCVMID`, so M-Chain could not have started.
The substrate hosts the ceremony state machine, share envelope,
QuasarCertLane registration, and certificate-subject binding logic
shared by both chains.
## Identity — the one-way door
vmID is `constants.MPCVMID` (`mpcvm`), CB58
`qCURact1n41FcoNBch8iMVBwc9AWie48D118ZNJ5tBdWrvryS`.
It is immutable once a chain is created with it, and it must agree in **all** of:
| where | what |
|---|---|
| `luxfi/constants` | `vm_ids.go` `MPCVMID` |
| `node/genesis/builder` | `registry.go` M-Chain row |
| `node/node` | `vms.go` `OptionalVMs` (plugin-only; never link in-process) |
| `node/Dockerfile` | plugin build `-o`, the build-verify list, the runtime `COPY` |
| `node/scripts` | `publish_plugin_set.sh` |
| `chains/mpcvm` | `factory.go`, `vmid_test.go` |
The plugin binary's **filename** is the CB58 — that is how the registry
resolves a `CreateChainTx`'s vmID. `TestVMID_IsCanonicalAndStable` pins it.
## What an agent must NOT do here
- Do **not** turn this back into a chain. No `factory.go` exposed to
`chains.Manager`. No new VM ID. M-Chain and F-Chain are the chains.
- Do **not** import M-Chain or F-Chain code from this package. The
dependency graph is `mchain → mpcvm` and `fchain → mpcvm`;
reverse edges are forbidden.
- Do **not** add a protocol implementation here. Protocol packages
expose **interfaces only**; impls live in the chain that runs the
protocol (CGGMP21 in M-Chain, TFHE keygen straddles M-Chain →
F-Chain via the handoff envelope).
- Do **not** introduce `t-chain` / `tchain` types here, and do **not**
reintroduce a "T-Chain" chain. Per LP-134 / LP-7050 T-Chain is removed
with zero remainder (it was never launched with live state — a clean
forward split, no migration window). The `vm.go`, `block.go`,
`factory.go`, `fhe/`, `cmd/` files are the shared **library** substrate
(the ceremony VM + FHE code) that M-Chain and F-Chain consume — not a
chain, no genesis, no validators.
- Do **not** reintroduce a bare `Threshold`/`TotalParties` pair, or any single
integer named "threshold". A quorum is a `quorum.Policy` (K-of-N) and the
polynomial degree is obtained only via `Policy.Degree()`. Those two numbers
differ by one, and conflating them silently provisions a wrong-degree key
that cannot be fixed without a resharing ceremony.
- Do **not** add a second path to a key or a signature. `RunKeygen` and
`RunSign` in `custody.go` are the only ones; the in-memory session machinery
that used to shadow them (`keygenSessions`, `signingSessions`, `activeKeyID`,
a `keys` map flushed at shutdown) was deleted because it was an unreplicated
copy of state that could disagree with the chain.
- Do **not** put a secret under the `c/` prefix. `c/` is replicated and enters
the state root; key shares live under `n/`, node-private.
- Do **not** make `Block.Verify` trust the proposer. Every operation carries a
verifiable artifact and the block carries its post-state root.
- Do **not** reintroduce `t-chain`/`tchain` types or a "T-Chain". Per LP-134 /
LP-7050 it is removed with zero remainder: MPC→M-Chain, FHE→F-Chain
(`fhevm`), teleport→B-Chain (`bridgevm`).
- Do **not** derive an external custody address with anything but Keccak-256.
It was SHA-256 once; the published address then belongs to no one, and funds
sent to it are unspendable.
## Invariants worth knowing before editing
- Ceremony ids are **derived** from the task, never announced. That is what
makes the protocol leaderless — change the derivation and nodes stop
converging on the same ceremony.
- The committee is the validator set (`Committee`), so the policy's `N` cannot
exceed the network's validator count or keygen fails closed forever.
- `2K > N` (`types.HasUniqueQuorum`) is a custody policy, not a protocol
requirement — it prevents two disjoint quorums authorising contradictory
releases.
- `Initialize` resumes the persisted tip. It must never recompute genesis over
live state.
## Status
- Substrate ships with Quasar 3.0 activation on **2025-12-25**.
- LP-5013 (T-Chain MPC Custody) and the whole T-Chain concept are **removed** by LP-134; see LP-7050 for the full supersession map (MPC→M-Chain, FHE→F-Chain, teleport→B-Chain/bridgevm).
- Cert-lane enums `MChainCGGMP21=5`, `MChainFROST=6`,
`MChainCoronaGen=7`, `FChainTFHE=8`, `FChainBootstrap=9`. Never
reorder; appends only.
## Where to read next
| File | Why |
|---|---|
| `DESIGN.md` | architecture, state machine, lane integration |
| `docs/ARCHITECTURE.md` | how M-Chain / F-Chain plug in |
| `docs/M_CHAIN_INTEGRATION.md` | M-Chain adapter contract |
| `docs/F_CHAIN_INTEGRATION.md` | F-Chain adapter contract |
| `types/ceremony.go` | the state machine in code |
| `cert/subject.go` | how `certificate_subject` binds both roots |
- M-Chain is in the genesis chain set of every network
(`genesis/configs/*/mchain.json`), policy `3-of-5` on
mainnet/testnet/devnet and `2-of-3` on localnet.
- Proven: boots on luxd as a genesis chain and serves `/v1/bc/m-chain/rpc`;
`TestBridgeCustody_ThreeOfFive` runs a real 5-validator CGGMP21 DKG at
degree 2 with a genuine 3-of-5 signature verified and recorded on-chain.
- **Not** yet proven: a DKG across five separate luxd OS processes over real
sockets, and resharing/refresh (`ReshareKey`/`RefreshKey` are not
implemented — a validator joining after a key exists is not yet shared in).
- Cert-lane enums `MChainCGGMP21=5`, `MChainFROST=6`, `MChainCoronaGen=7`,
`FChainTFHE=8`, `FChainBootstrap=9`. Never reorder; appends only.
- `types/`, `cert/`, `runtime/` hold the shared ceremony state machine that
F-Chain will also consume. `types` is wired (the custody floor); `cert/` and
`runtime/` are **not** yet consumed by the VM.
+104 -120
View File
@@ -1,128 +1,112 @@
# ThresholdVM
# mpcvm — M-Chain
**ThresholdVM is a Go library, not a chain.**
**M-Chain is the MPC threshold-custody chain.** `chains/mpcvm` is its VM: a
plugin-loaded `chain.ChainVM` with genesis, validators, block production and
replicated state.
Per LP-134 (Lux Chain Topology) and LP-7050 (Supersession Map), T-Chain is
**fully removed** with zero remainder. Its concerns decompose onto existing
chains, each of which consumes this library substrate:
It exists to hold the keys that custody bridged assets on external chains, and
to do that on-chain instead of in an off-chain signer cluster.
- **M-Chain** (`mpcvm`, LP-7100) — bridge custody for external wallets,
threshold signing ceremonies (CGGMP21 / FROST / Pulsar-general). Owns
*all* threshold signing.
- **F-Chain** (`fhevm`, LP-8200) — TFHE keygen, encrypted EVM, threshold
decrypt, confidential compute. Owns *all* encrypted computation.
- **vmID** `mpcvm` = `constants.MPCVMID`, CB58
`qCURact1n41FcoNBch8iMVBwc9AWie48D118ZNJ5tBdWrvryS`. The plugin binary's
filename **must** be that CB58 — that is how the node resolves a
`CreateChainTx`'s vmID to an implementation. A vmID is an immutable one-way
door once a chain is created with it; `TestVMID_IsCanonicalAndStable` pins it
and lists every declaration that must move together.
- **Genesis chain.** M-Chain is in the P-Chain chain set at height 0 on every
network (`genesis/configs/*/mchain.json`), tracked by validators from boot —
not created later by a `CreateChainTx` someone has to remember to submit.
- Per LP-134 / LP-7050 there is no T-Chain and no `teleportvm`: teleport IS
`bridgevm` (B-Chain, LP-6000), and the FHE half of the retired ThresholdVM is
`fhevm` (F-Chain, LP-8200). Any identifier still naming "T-Chain" or
"ThresholdVM-as-a-chain" is stale.
`chains/mpcvm/` is the shared **library** these two chains import in
process — it is not itself a chain and has no genesis, no validators, no
block production. There is **no** `teleportvm` and **no** "T-Chain": teleport
IS `bridgevm` (B-Chain, LP-6000). Any live identifier still naming "T-Chain",
"ThresholdVM-as-a-chain", or "teleportvm" is stale.
## The quorum, and why it is spelled "3-of-5"
```
+--------------------------------+
| ThresholdVM |
| (ceremony state machine, |
| share envelope, lane API, |
| subject binding) |
+----------------+---------------+
|
+-------------------+-------------------+
| |
+--------v--------+ +--------v--------+
| M-Chain | | F-Chain |
| (MPC: CGGMP21, | | (FHE: TFHE, |
| FROST, | | bootstrap-key |
| Corona-gen) | --bootstrap key--> | gen, encrypted |
| | | EVM) |
+-----------------+ +-----------------+
ceremony_root: ceremony_root:
mchain_ceremony_root fchain_fhe_root
block time ~500ms block time ~2s
sign-oriented compute-oriented
```
A threshold policy has two numbers that differ by one, and confusing them is
the defining failure mode of this domain:
## What this directory contains
| | meaning | where it appears |
|---|---|---|
| **K** | signers required | operator language, runbooks, genesis `policy` |
| **t** | polynomial degree = K1 | `cmp.Keygen`, `frost.Keygen`, stored configs |
| Path | Role |
Passing an operator's `3` where a library wants `t` builds a **4-of-5** key.
Nothing errors; three custodians simply cannot sign, and for a key that already
holds funds the only fix is a resharing ceremony with parties that may no
longer exist.
So M-Chain never carries a bare threshold number. Genesis states
`"policy": "3-of-5"`, it decodes to a `quorum.Policy`, and the degree is
obtained **only** by calling `Policy.Degree()`. `luxfi/threshold/pkg/quorum` is
the one place K and t are allowed to meet.
M-Chain additionally requires `2K > N` (`types.HasUniqueQuorum`). CGGMP21 is
unforgeable against up to N1 corruptions, so 2-of-5 is cryptographically
sound; but with two disjoint quorums, two halves of the committee could
authorise contradictory releases of the same funds.
The policy must also fit the network's validator set — the committee IS the
validator set, so `N` cannot exceed the validators that exist to hold shares.
`TestMChainPolicyIsSatisfiableByTheGenesisValidatorSet` in the node repo
enforces it.
## What is on-chain and what is not
| | replicated | in the state root | contents |
|---|---|---|---|
| `c/` consensus | yes | yes | key registry, ceremony log, blocks, height index |
| `n/` node-private | **no** | **no** | this validator's secret key shares |
The registry holds only public values — policy, participants, group public key,
custody address. A share never enters consensus state. State is written the
moment each fact becomes true, so a crash cannot lose the record of who holds
the funds, and `Initialize` resumes the accepted tip rather than recomputing
genesis.
## Blocks are verified, not trusted
- A **sign** operation carries the signature; `Verify` re-checks it against the
group public key already in the registry. A proposer cannot fabricate one
without forging ECDSA, and a validator that sat out the ceremony still
verifies the result.
- A **keygen** operation carries a proof of possession by the new group key
over its own registration. That rules out registering a key you do not
control. The declared *degree* is bound separately, by participants
cross-checking the record against their own share — one honest participant is
enough to reject a mis-declared key.
- Every block carries its post-state root. `Verify` recomputes it, so two
validators that would diverge cannot both accept.
## Leaderless and permissionless
Ceremony ids are **derived** from the task — `H(tag ‖ keyID ‖ digest ‖ sorted
signers)` — so every validator converges on the same ceremony with no announce
round and no coordinator. The signing quorum is likewise a pure function of the
task, so all nodes agree on which K sign without an election.
The committee is the chain's own validator set. Joining the signing ring is
joining the validator set: no allowlist, no operator registry, no admin-gated
key ceremony.
## Proof
`TestBridgeCustody_ThreeOfFive` runs five VMs over the real gossip transport: a
real CGGMP21 DKG at degree 2, exactly three of five sign, two decline, the
signature verifies under the group key, and a block carrying it is verified and
accepted by all five — including the two that never touched the ceremony — with
every node ending on the same state root.
## Layout
| path | role |
|---|---|
| `types/` | Ceremony, Participant, Share, Proof — pure data types, no chain logic |
| `protocol/` | Protocol-specific verifier interfaces (impl ships with the chain that uses it) |
| `cert/` | QuasarCertLane integration: lane registration + certificate-subject binding |
| `runtime/` | `MChainAdapter` and `FChainAdapter` interfaces the two chains implement |
| `docs/` | Architecture + per-chain integration notes |
## What this directory is **not**
- Not a Lux VM. There is no `factory.go` exposed to `chains.Manager`.
- Not a chain ID. M-Chain and F-Chain are the chains; they have their
own validator sets, ceremony roots, and block cadence.
- Not a registry of secrets, keys, or signed material. State lives on
M-Chain or F-Chain, never here.
## Why split
LP-134 deprecates LP-5013 ("T-Chain MPC Custody"). T-Chain previously
mixed MPC ceremonies and FHE compute under one validator set, one
ceremony root, one block cadence. Sign-oriented (sub-second, latency
critical) and compute-oriented (multi-second, throughput critical)
workloads have incompatible scaling profiles. Splitting them lets each
scale on its own validator set.
The substrate stays one piece because the **ceremony state machine** is
identical across both: registered participants → round 1 commitments →
round 2 shares → finalized cert. Only the per-protocol payload
(carried by `(offset, len)` indirection in the share envelope) differs.
## Permissionless participation
Both M-Chain and F-Chain inherit P-Chain's permissionless validator
model. Anyone with stake on P-Chain can delegate to the M-Chain or
F-Chain validator subset (or both). No allowlist. No central authority.
Bitcoin-like decentralization carries through — the threshold scheme
itself enforces honest-majority safety, the chain enforces liveness.
## Cross-chain handoff
When M-Chain finishes a ceremony whose output feeds F-Chain (e.g., a
TFHE bootstrap key generated by FROST DKG on M-Chain), the output
crosses via a CertLane artifact:
1. M-Chain finalizes ceremony, emits cert with lane `MChainFROST` and
`mchain_ceremony_root` updated.
2. F-Chain consumes the artifact via its `FChainBootstrap` lane,
binds it into `fchain_fhe_root`.
3. Quasar 3.0's `certificate_subject` binds **both** roots in the same
round, making cross-chain replay structurally impossible.
The handoff envelope is defined in `cert/lane.go`; the typed adapter
in `runtime/`.
## Migration from T-Chain
LP-5013 is deprecated. The legacy chain at `chains/mpcvm/` (this
directory's `vm.go`, `block.go`, `factory.go`, `fhe/`, `cmd/`) hosts
the migration shims that route legacy `TChain*` cert lanes to the
appropriate `MChain*` / `FChain*` verifiers during the one-epoch grace
window. After the grace window closes, the shims are removed and only
the substrate (this README's directory tree) remains.
## Activation
ThresholdVM substrate activates with **Quasar 3.0 on 2025-12-25**,
alongside the M-Chain and F-Chain genesis events. Versions before that
date target the legacy T-Chain layout.
## References
| LP | Topic |
|---|---|
| LP-019 | Threshold MPC for Bridge Signing (M-Chain protocols) |
| LP-013 | FHE on GPU (F-Chain compute) |
| LP-076 | Universal Threshold Cryptography Framework |
| LP-134 | Lux Chain Topology — defines the M/F split |
| LP-132 | QuasarGPU Execution Adapter — lane verifiers run here |
| LP-020 | Quasar Consensus 3.0 — certificate_subject binding |
## License
Copyright (C) 2019-2025 Lux Industries Inc. MIT licensed.
| `vm.go` | `chain.ChainVM`: Initialize/resume, BuildBlock, custody API |
| `custody.go` | ceremony lifecycle — DKG, signing, quorum selection, staging |
| `state.go` | persisted state: registry, ceremony log, roots, share store |
| `block.go` | the verified state transition |
| `wire.go` | native-ZAP struct-is-wire encodings |
| `transport.go` | gossip router (production) + in-process mesh (tests) |
| `executor.go` | drives `luxfi/threshold` protocol handlers |
| `types/` | shared ceremony state machine and the `2K > N` custody floor |
| `fhe/` | TFHE helpers retained for the F-Chain handoff |