mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
feat(thresholdvm): stage design docs and protocol scaffolding
Stages the ThresholdVM engineering record under chains/thresholdvm/: Docs (engineering record, 502 LOC): - DESIGN.md, LLM.md, README.md - docs/ARCHITECTURE.md, docs/F_CHAIN_INTEGRATION.md, docs/M_CHAIN_INTEGRATION.md Source scaffolding (1289 LOC): - cert/ — QuasarCertLane registration + subject binding (M/F shared) - types/ — ceremony, participant, proof, share substrate types - protocol/ — package surfaces for cggmp21, frost, corona_general, tfhe_keygen - runtime/ — F-chain and M-chain adapters All Go files carry the Lux license header. Nothing here is generated or fixture data; this is the signed-off design surface for the ThresholdVM library used by M-chain and F-chain.
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
# ThresholdVM Design
|
||||
|
||||
> Status: Final for Quasar 3.0 activation (2025-12-25)
|
||||
> Supersedes the T-Chain monolith (LP-5013), per LP-134
|
||||
|
||||
## 1. Position in the topology
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph PChain["P-Chain (Platform / staking)"]
|
||||
Stake[Validator stake + delegation]
|
||||
end
|
||||
|
||||
subgraph Substrate["thresholdvm (library, this repo)"]
|
||||
SM[Ceremony state machine]
|
||||
Share[Share envelope: offset/len]
|
||||
Lane[QuasarCertLane registration]
|
||||
Subject[certificate_subject binding]
|
||||
end
|
||||
|
||||
subgraph MChain["M-Chain (MPC)"]
|
||||
MV[M-Chain validator subset]
|
||||
MProto[CGGMP21 / FROST / Corona-gen]
|
||||
MRoot[mchain_ceremony_root]
|
||||
end
|
||||
|
||||
subgraph FChain["F-Chain (FHE)"]
|
||||
FV[F-Chain validator subset]
|
||||
FProto[TFHE / bootstrap]
|
||||
FRoot[fchain_fhe_root]
|
||||
end
|
||||
|
||||
subgraph Quasar["Quasar 3.0 cert pipeline"]
|
||||
Cert[QuasarCert artifact]
|
||||
end
|
||||
|
||||
Stake -->|delegate| MV
|
||||
Stake -->|delegate| FV
|
||||
|
||||
Substrate -.imported.-> MChain
|
||||
Substrate -.imported.-> FChain
|
||||
|
||||
MChain -->|MChainCGGMP21 / FROST / CoronaGen lanes| Quasar
|
||||
FChain -->|FChainTFHE / FChainBootstrap lanes| Quasar
|
||||
|
||||
MChain -->|bootstrap-key handoff envelope| FChain
|
||||
Quasar -->|certificate_subject binds both roots| MRoot
|
||||
Quasar -->|certificate_subject binds both roots| FRoot
|
||||
```
|
||||
|
||||
The substrate has **zero** runtime presence. It compiles into both
|
||||
chains' binaries.
|
||||
|
||||
## 2. Why M and F are separate operational chains
|
||||
|
||||
| Dimension | M-Chain | F-Chain |
|
||||
|---|---|---|
|
||||
| Workload | sign-oriented (latency-critical, sub-second) | compute-oriented (throughput-critical, multi-second) |
|
||||
| Block cadence | ~500 ms | ~2 s typical |
|
||||
| Validator profile | bandwidth + low latency | GPU + memory bandwidth (TFHE bootstrap is GPU-bound) |
|
||||
| Failure cost | a missed signature delays a bridge tx | a slow FHE compute delays a confidential contract |
|
||||
| Stake profile | wide / many small validators (Bitcoin-like) | narrower / GPU operators self-select |
|
||||
| Ceremony root | `mchain_ceremony_root` | `fchain_fhe_root` |
|
||||
| QuasarMode | Nebula (DAG of partials) | Nebula (computation graph) |
|
||||
|
||||
Sharing one validator set would force the GPU operators on F-Chain to
|
||||
also keep up with M-Chain's 500 ms cadence (or vice versa). Keeping
|
||||
the chains separate lets each scale on its own validator subset, both
|
||||
permissionlessly carved out of P-Chain stake.
|
||||
|
||||
## 3. The shared state machine
|
||||
|
||||
```
|
||||
StateRegistered -- all participants for ceremony C have committed stake
|
||||
|
|
||||
v
|
||||
StateRound1 -- each participant broadcasts commitments (per-protocol payload)
|
||||
|
|
||||
v
|
||||
StateRound2 -- each participant distributes shares (per-protocol payload)
|
||||
|
|
||||
v
|
||||
StateFinalized -- aggregator emits CertLane artifact, ceremony root advances
|
||||
```
|
||||
|
||||
Round semantics are protocol-specific (FROST is 2-round, CGGMP21
|
||||
pre-sign + sign, TFHE keygen is multi-round) but the **envelope** is
|
||||
identical: per-round payload is carried by `(offset, len)` indirection
|
||||
into a single artifact buffer.
|
||||
|
||||
The state machine is implemented in `types/ceremony.go` as a simple
|
||||
explicit-transition function. No goroutines, no timers in the
|
||||
substrate — each chain's runtime drives transitions on block ticks.
|
||||
|
||||
## 4. Share envelope (per-protocol payload via offset/len)
|
||||
|
||||
```go
|
||||
// types/share.go
|
||||
type Share struct {
|
||||
CeremonyID [32]byte // hash(ceremony_descriptor)
|
||||
ParticipantID uint32 // index into ParticipantSet
|
||||
Round uint8 // 1 or 2 for FROST/CGGMP21; up to 4 for Corona/TFHE
|
||||
Lane CertLane // dispatches per-protocol verifier
|
||||
PayloadOffset uint32 // offset into ceremony's payload arena
|
||||
PayloadLen uint32 // length of this share's payload
|
||||
Signature [64]byte // BLS or ML-DSA signature over (CeremonyID, Round, payload)
|
||||
}
|
||||
```
|
||||
|
||||
This matches the `(artifact_offset, artifact_len)` indirection used by
|
||||
`QuasarCertIngress` (LP-132 §drain_cert_lane). New protocols append a
|
||||
new `CertLane` enum value and a new verifier; **the envelope never
|
||||
changes**.
|
||||
|
||||
## 5. QuasarCertLane registration
|
||||
|
||||
```go
|
||||
// cert/lane.go
|
||||
type LaneVerifier interface {
|
||||
Lane() CertLane
|
||||
Verify(subject [32]byte, share Share, payload []byte) error
|
||||
}
|
||||
|
||||
type LaneRegistry interface {
|
||||
Register(LaneVerifier) error
|
||||
Verifier(CertLane) (LaneVerifier, error)
|
||||
}
|
||||
```
|
||||
|
||||
- M-Chain's runtime calls `Register(MChainCGGMP21Verifier{})`,
|
||||
`Register(MChainFROSTVerifier{})`,
|
||||
`Register(MChainCoronaGenVerifier{})` at boot.
|
||||
- F-Chain's runtime calls `Register(FChainTFHEVerifier{})`,
|
||||
`Register(FChainBootstrapVerifier{})` at boot.
|
||||
- Each chain's registry instance is private to that chain's process.
|
||||
The substrate provides the type, not a global.
|
||||
|
||||
## 6. Certificate-subject binding
|
||||
|
||||
LP-134 extends `QuasarRoundDescriptor`:
|
||||
|
||||
```cpp
|
||||
uint8_t mchain_ceremony_root[32];
|
||||
uint8_t fchain_fhe_root[32];
|
||||
// ... and seven other roots ...
|
||||
uint8_t certificate_subject[32]; // binds all of them
|
||||
```
|
||||
|
||||
`certificate_subject = H(parent_block || state_root || exec_root || pchain_validator_root || qchain_ceremony_root || zchain_vk_root || achain_attestation_root || bchain_bridge_root || mchain_ceremony_root || fchain_fhe_root)`
|
||||
|
||||
The substrate's `cert/subject.go` exposes `BindSubject(...)` and
|
||||
`Verify(subject, lane, share)`. **Both** `mchain_ceremony_root` and
|
||||
`fchain_fhe_root` are inputs, regardless of which chain emitted the
|
||||
ceremony — this is the structural property that makes
|
||||
cross-chain-replay impossible (a cert artifact for an M-Chain
|
||||
ceremony cannot be replayed against F-Chain because the F-Chain root
|
||||
on the destination round disagrees by construction).
|
||||
|
||||
## 7. Permissionless participation
|
||||
|
||||
The substrate enforces, via `types/participant.go`:
|
||||
|
||||
1. `ParticipantSet` is derived **deterministically** from
|
||||
`pchain_validator_root` + chain-specific delegation. No allowlist.
|
||||
2. Selection is done by VRF-weighted sampling on stake delegated to
|
||||
M-Chain or F-Chain respectively. Same security analysis as Lux's
|
||||
existing P-Chain selection.
|
||||
3. Threshold parameters (`t-of-n`) are policy controlled by the
|
||||
chain's governance, but minimum `t >= n/2 + 1` is enforced at the
|
||||
library level.
|
||||
4. Resharing (LP-077 LSS) is supported via the same envelope —
|
||||
resharing is just a ceremony whose output is a new key share for a
|
||||
new participant set.
|
||||
|
||||
## 8. Cross-chain handoff (M → F)
|
||||
|
||||
Most ceremonies live entirely on one chain. The exception is **TFHE
|
||||
bootstrap-key generation**, which uses MPC (M-Chain) to generate a
|
||||
key the FHE engine (F-Chain) consumes:
|
||||
|
||||
```
|
||||
1. M-Chain runs FROST DKG over the TFHE secret-key polynomial.
|
||||
2. On finalize, M-Chain emits a CertLane artifact with lane =
|
||||
MChainFROST. The artifact's payload encodes the TFHE evaluation
|
||||
key + bootstrap material.
|
||||
3. F-Chain ingests the artifact at its next round via lane =
|
||||
FChainBootstrap (which wraps the upstream MChainFROST cert).
|
||||
4. fchain_fhe_root advances to commit the new key.
|
||||
5. Quasar 3.0's certificate_subject in the round binds both
|
||||
mchain_ceremony_root (with the FROST output) and fchain_fhe_root
|
||||
(with the new bootstrap key) — atomic from the network's view.
|
||||
```
|
||||
|
||||
The handoff envelope is just a `Share` with `Lane=FChainBootstrap`
|
||||
that wraps the upstream `Lane=MChainFROST` artifact via offset/len
|
||||
into the same buffer. No new envelope type.
|
||||
|
||||
## 9. Migration adapters (T-Chain → M/F)
|
||||
|
||||
The substrate hosts:
|
||||
|
||||
- `cert/lane.go`: legacy `TChainSign` and `TChainFHE` lane numbers
|
||||
are accepted during the one-epoch grace window after activation
|
||||
(2025-12-25 → next epoch). The verifier dispatches them to
|
||||
`MChain*` and `FChain*` respectively.
|
||||
- After grace, the dispatch table drops the legacy entries. The
|
||||
substrate has no grace logic of its own — it just exposes
|
||||
`RegisterLegacyAlias(legacyLane, modernLane)` which the chain's
|
||||
runtime wires up at boot for one epoch.
|
||||
|
||||
## 10. Type-level orthogonality
|
||||
|
||||
The split is enforced by Go interface, not convention:
|
||||
|
||||
- `runtime.MChainAdapter` exposes only MPC ceremony hooks (`OnDKG`,
|
||||
`OnSign`, `OnReshare`).
|
||||
- `runtime.FChainAdapter` exposes only FHE compute hooks (`OnKeygen`,
|
||||
`OnEval`, `OnBootstrap`).
|
||||
- The substrate's lane registry refuses to accept a verifier whose
|
||||
`Lane()` value is in the M-Chain range (5–7) from the F-Chain
|
||||
registry, and vice versa. A misconfigured chain fails at boot, not
|
||||
at runtime.
|
||||
|
||||
This makes "M-Chain accidentally hosts FHE compute" a compile-time
|
||||
or boot-time failure, not a silent semantic bug.
|
||||
|
||||
## 11. References
|
||||
|
||||
| LP | Topic |
|
||||
|---|---|
|
||||
| LP-019 | Threshold MPC for Bridge Signing |
|
||||
| LP-013 | FHE on GPU |
|
||||
| LP-076 | Universal Threshold Cryptography Framework |
|
||||
| LP-077 | Linear Secret Sharing (resharing) |
|
||||
| LP-132 | QuasarGPU Execution Adapter |
|
||||
| LP-134 | Lux Chain Topology |
|
||||
| LP-020 | Quasar Consensus 3.0 |
|
||||
| LP-5013 | T-Chain MPC Custody (deprecated) |
|
||||
@@ -0,0 +1,48 @@
|
||||
# LLM context — `chains/thresholdvm/`
|
||||
|
||||
## What this is
|
||||
|
||||
ThresholdVM is a **library substrate**, not a Lux chain. It is imported by:
|
||||
|
||||
- `chains/mchain/` — MPC ceremonies (CGGMP21, FROST, Corona-general)
|
||||
- `chains/fchain/` — FHE compute (TFHE bootstrap, encrypted EVM)
|
||||
|
||||
The substrate hosts the ceremony state machine, share envelope,
|
||||
QuasarCertLane registration, and certificate-subject binding logic
|
||||
shared by both chains.
|
||||
|
||||
## 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 → thresholdvm` and `fchain → thresholdvm`;
|
||||
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. The legacy
|
||||
`vm.go`, `block.go`, `factory.go`, `fhe/`, `cmd/` files in the
|
||||
parent directory are the deprecated T-Chain shim from LP-5013.
|
||||
They exist only for one-epoch grace-window migration, then are
|
||||
removed.
|
||||
|
||||
## Status
|
||||
|
||||
- Substrate ships with Quasar 3.0 activation on **2025-12-25**.
|
||||
- LP-5013 (T-Chain) is **deprecated** by LP-134.
|
||||
- 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 |
|
||||
@@ -0,0 +1,116 @@
|
||||
# ThresholdVM
|
||||
|
||||
**ThresholdVM is a Go library, not a chain.**
|
||||
|
||||
It is the shared substrate that **M-Chain** (MPC ceremonies) and **F-Chain**
|
||||
(FHE compute) build on. Both chains import this package; neither chain *is*
|
||||
this package.
|
||||
|
||||
```
|
||||
+--------------------------------+
|
||||
| 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
|
||||
```
|
||||
|
||||
## What this directory contains
|
||||
|
||||
| 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/thresholdvm/` (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.
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package cert provides the QuasarCertLane registration and
|
||||
// certificate-subject binding logic shared by M-Chain and F-Chain.
|
||||
//
|
||||
// The lane registry is **not** a global. Each chain constructs its
|
||||
// own LaneRegistry at boot, registers only the verifiers it owns
|
||||
// (M-Chain registers lanes 5..7, F-Chain registers lanes 8..9), and
|
||||
// the substrate refuses cross-registration at runtime — a misrouted
|
||||
// verifier fails fast.
|
||||
package cert
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/chains/thresholdvm/types"
|
||||
)
|
||||
|
||||
// LaneVerifier is the contract a chain implements to validate shares
|
||||
// on a given cert lane. Verifiers are stateless with respect to the
|
||||
// substrate; any mutable state lives in the chain's own runtime.
|
||||
type LaneVerifier interface {
|
||||
// Lane returns the lane identifier this verifier handles.
|
||||
Lane() types.CertLane
|
||||
// Verify validates a share's payload against the certificate
|
||||
// subject. The payload was extracted via Share.PayloadFrom on
|
||||
// the ceremony's PayloadArena.
|
||||
Verify(subject [32]byte, share types.Share, payload []byte) error
|
||||
}
|
||||
|
||||
// Owner identifies which operational chain owns a lane registry. The
|
||||
// substrate uses Owner to enforce orthogonality: an M-Chain registry
|
||||
// refuses F-Chain lanes and vice versa.
|
||||
type Owner uint8
|
||||
|
||||
const (
|
||||
OwnerUnknown Owner = 0
|
||||
OwnerMChain Owner = 1
|
||||
OwnerFChain Owner = 2
|
||||
)
|
||||
|
||||
// LaneRegistry holds the verifier dispatch table for one chain.
|
||||
//
|
||||
// One registry per chain process. Constructed at boot, written
|
||||
// during boot, read-only thereafter. No locks needed: registrations
|
||||
// happen serially before the chain starts accepting blocks.
|
||||
type LaneRegistry struct {
|
||||
owner Owner
|
||||
verifier map[types.CertLane]LaneVerifier
|
||||
// aliases lets the host wire legacy-lane-id → modern-lane during
|
||||
// the LP-134 grace window. Empty after the grace epoch closes.
|
||||
aliases map[types.CertLane]types.CertLane
|
||||
}
|
||||
|
||||
// NewRegistry constructs a registry owned by the given chain.
|
||||
func NewRegistry(owner Owner) *LaneRegistry {
|
||||
return &LaneRegistry{
|
||||
owner: owner,
|
||||
verifier: make(map[types.CertLane]LaneVerifier),
|
||||
aliases: make(map[types.CertLane]types.CertLane),
|
||||
}
|
||||
}
|
||||
|
||||
// Register adds a verifier to the registry. Returns an error if the
|
||||
// lane does not belong to the registry's owner — this enforces M/F
|
||||
// orthogonality at the type level.
|
||||
func (r *LaneRegistry) Register(v LaneVerifier) error {
|
||||
if v == nil {
|
||||
return errors.New("registry: nil verifier")
|
||||
}
|
||||
lane := v.Lane()
|
||||
switch r.owner {
|
||||
case OwnerMChain:
|
||||
if !lane.IsMChain() {
|
||||
return fmt.Errorf("registry: lane %d is not owned by M-Chain", lane)
|
||||
}
|
||||
case OwnerFChain:
|
||||
if !lane.IsFChain() {
|
||||
return fmt.Errorf("registry: lane %d is not owned by F-Chain", lane)
|
||||
}
|
||||
default:
|
||||
return errors.New("registry: owner unset")
|
||||
}
|
||||
if _, exists := r.verifier[lane]; exists {
|
||||
return fmt.Errorf("registry: lane %d already registered", lane)
|
||||
}
|
||||
r.verifier[lane] = v
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterLegacyAlias maps a legacy LP-5013 T-Chain lane to the
|
||||
// modern M/F lane during the grace window. After the window closes,
|
||||
// the host calls ClearAliases() and any legacy share is rejected.
|
||||
func (r *LaneRegistry) RegisterLegacyAlias(legacy, modern types.CertLane) error {
|
||||
if _, ok := r.verifier[modern]; !ok {
|
||||
return fmt.Errorf("registry: cannot alias to unregistered lane %d", modern)
|
||||
}
|
||||
r.aliases[legacy] = modern
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearAliases removes all legacy aliases. Called by the host at the
|
||||
// end of the grace epoch.
|
||||
func (r *LaneRegistry) ClearAliases() {
|
||||
r.aliases = make(map[types.CertLane]types.CertLane)
|
||||
}
|
||||
|
||||
// Verifier resolves a lane (including grace-window aliases) to its
|
||||
// verifier. Returns an error if no verifier is registered.
|
||||
func (r *LaneRegistry) Verifier(lane types.CertLane) (LaneVerifier, error) {
|
||||
if alias, ok := r.aliases[lane]; ok {
|
||||
lane = alias
|
||||
}
|
||||
v, ok := r.verifier[lane]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("registry: no verifier for lane %d", lane)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Verify dispatches a share to its lane verifier. Convenience wrapper
|
||||
// for the chain runtime — validates the share envelope, extracts the
|
||||
// payload window, and calls the verifier.
|
||||
func (r *LaneRegistry) Verify(subject [32]byte, share types.Share, arena []byte, set *types.ParticipantSet) error {
|
||||
if err := share.Validate(set); err != nil {
|
||||
return err
|
||||
}
|
||||
v, err := r.Verifier(share.Lane)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload, err := share.PayloadFrom(arena)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.Verify(subject, share, payload)
|
||||
}
|
||||
|
||||
// Owner reports which chain owns this registry. Used by tests and
|
||||
// boot-time sanity checks.
|
||||
func (r *LaneRegistry) Owner() Owner { return r.owner }
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package cert
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/chains/thresholdvm/types"
|
||||
)
|
||||
|
||||
type stubVerifier struct{ lane types.CertLane }
|
||||
|
||||
func (s stubVerifier) Lane() types.CertLane { return s.lane }
|
||||
func (s stubVerifier) Verify(_ [32]byte, _ types.Share, _ []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Orthogonality: the M-Chain registry refuses F-Chain lanes.
|
||||
func TestOrthogonality_MChainRejectsFChainLane(t *testing.T) {
|
||||
r := NewRegistry(OwnerMChain)
|
||||
err := r.Register(stubVerifier{lane: types.LaneFChainTFHE})
|
||||
if err == nil {
|
||||
t.Fatal("expected M-Chain registry to reject FChainTFHE lane")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "not owned by M-Chain") {
|
||||
t.Fatalf("expected ownership error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Orthogonality: the F-Chain registry refuses M-Chain lanes.
|
||||
func TestOrthogonality_FChainRejectsMChainLane(t *testing.T) {
|
||||
r := NewRegistry(OwnerFChain)
|
||||
err := r.Register(stubVerifier{lane: types.LaneMChainCGGMP21})
|
||||
if err == nil {
|
||||
t.Fatal("expected F-Chain registry to reject MChainCGGMP21 lane")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "not owned by F-Chain") {
|
||||
t.Fatalf("expected ownership error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Happy path: M-Chain registers all three of its lanes.
|
||||
func TestRegister_MChainOwnedLanes(t *testing.T) {
|
||||
r := NewRegistry(OwnerMChain)
|
||||
for _, lane := range []types.CertLane{
|
||||
types.LaneMChainCGGMP21,
|
||||
types.LaneMChainFROST,
|
||||
types.LaneMChainCoronaGen,
|
||||
} {
|
||||
if err := r.Register(stubVerifier{lane: lane}); err != nil {
|
||||
t.Fatalf("register %d: %v", lane, err)
|
||||
}
|
||||
}
|
||||
for _, lane := range []types.CertLane{
|
||||
types.LaneMChainCGGMP21,
|
||||
types.LaneMChainFROST,
|
||||
types.LaneMChainCoronaGen,
|
||||
} {
|
||||
if _, err := r.Verifier(lane); err != nil {
|
||||
t.Fatalf("lookup %d: %v", lane, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Happy path: F-Chain registers its two lanes.
|
||||
func TestRegister_FChainOwnedLanes(t *testing.T) {
|
||||
r := NewRegistry(OwnerFChain)
|
||||
for _, lane := range []types.CertLane{
|
||||
types.LaneFChainTFHE,
|
||||
types.LaneFChainBootstrap,
|
||||
} {
|
||||
if err := r.Register(stubVerifier{lane: lane}); err != nil {
|
||||
t.Fatalf("register %d: %v", lane, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Double-registration is rejected.
|
||||
func TestRegister_RejectsDoubleRegistration(t *testing.T) {
|
||||
r := NewRegistry(OwnerMChain)
|
||||
if err := r.Register(stubVerifier{lane: types.LaneMChainFROST}); err != nil {
|
||||
t.Fatalf("first register: %v", err)
|
||||
}
|
||||
if err := r.Register(stubVerifier{lane: types.LaneMChainFROST}); err == nil {
|
||||
t.Fatal("expected second registration to fail")
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy alias dispatch: a legacy lane id resolves to the modern verifier.
|
||||
func TestLegacyAlias_DispatchesToModern(t *testing.T) {
|
||||
r := NewRegistry(OwnerMChain)
|
||||
if err := r.Register(stubVerifier{lane: types.LaneMChainCGGMP21}); err != nil {
|
||||
t.Fatalf("register: %v", err)
|
||||
}
|
||||
const legacyTChainSign types.CertLane = 200 // arbitrary legacy id
|
||||
if err := r.RegisterLegacyAlias(legacyTChainSign, types.LaneMChainCGGMP21); err != nil {
|
||||
t.Fatalf("alias: %v", err)
|
||||
}
|
||||
v, err := r.Verifier(legacyTChainSign)
|
||||
if err != nil {
|
||||
t.Fatalf("legacy lookup: %v", err)
|
||||
}
|
||||
if v.Lane() != types.LaneMChainCGGMP21 {
|
||||
t.Fatalf("legacy alias should dispatch to MChainCGGMP21, got %d", v.Lane())
|
||||
}
|
||||
r.ClearAliases()
|
||||
if _, err := r.Verifier(legacyTChainSign); err == nil {
|
||||
t.Fatal("after ClearAliases, legacy id should not resolve")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Roots is the set of upstream roots a Quasar 3.0 certificate_subject
|
||||
// binds, mirroring the QuasarRoundDescriptor in LP-134.
|
||||
//
|
||||
// All seven roots are required, including both MChain and FChain —
|
||||
// this is the structural property that makes cross-chain replay
|
||||
// impossible. The substrate does not let a caller skip a root: a zero
|
||||
// value is still a value, and the chain that owns the root is
|
||||
// expected to provide its current root on every round.
|
||||
type Roots struct {
|
||||
ParentBlock [32]byte
|
||||
StateRoot [32]byte
|
||||
ExecRoot [32]byte
|
||||
PChainValidator [32]byte
|
||||
QChainCeremony [32]byte
|
||||
ZChainVK [32]byte
|
||||
AChainAttestation [32]byte
|
||||
BChainBridge [32]byte
|
||||
MChainCeremony [32]byte
|
||||
FChainFHE [32]byte
|
||||
}
|
||||
|
||||
// BindSubject computes certificate_subject = H(... all roots ...).
|
||||
// The hash domain is sha256 with a 1-byte version tag (0x01 for
|
||||
// LP-134 v3.1) so future descriptor extensions can change the input
|
||||
// set without colliding.
|
||||
func BindSubject(r Roots) [32]byte {
|
||||
h := sha256.New()
|
||||
h.Write([]byte{0x01}) // LP-134 v3.1 descriptor version
|
||||
h.Write(r.ParentBlock[:])
|
||||
h.Write(r.StateRoot[:])
|
||||
h.Write(r.ExecRoot[:])
|
||||
h.Write(r.PChainValidator[:])
|
||||
h.Write(r.QChainCeremony[:])
|
||||
h.Write(r.ZChainVK[:])
|
||||
h.Write(r.AChainAttestation[:])
|
||||
h.Write(r.BChainBridge[:])
|
||||
h.Write(r.MChainCeremony[:])
|
||||
h.Write(r.FChainFHE[:])
|
||||
var out [32]byte
|
||||
copy(out[:], h.Sum(nil))
|
||||
return out
|
||||
}
|
||||
|
||||
// VerifySubject re-computes the subject from its inputs and checks it
|
||||
// matches the value carried on the wire. Used by both M-Chain and
|
||||
// F-Chain at certificate-ingress time.
|
||||
func VerifySubject(claimed [32]byte, r Roots) error {
|
||||
if BindSubject(r) != claimed {
|
||||
return errors.New("subject: claimed certificate_subject does not match descriptor roots")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RequireBothChains reports an error if either MChainCeremony or
|
||||
// FChainFHE is zero — the LP-134 invariant that every round binds
|
||||
// **both** chain roots, even on rounds where one chain does not
|
||||
// finalize a ceremony (the unchanged root from the previous round
|
||||
// satisfies the binding).
|
||||
func (r Roots) RequireBothChains() error {
|
||||
if r.MChainCeremony == ([32]byte{}) {
|
||||
return errors.New("subject: mchain_ceremony_root is zero — boot-time root not yet wired")
|
||||
}
|
||||
if r.FChainFHE == ([32]byte{}) {
|
||||
return errors.New("subject: fchain_fhe_root is zero — boot-time root not yet wired")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
# Architecture
|
||||
|
||||
ThresholdVM is the **shared library substrate** beneath M-Chain and
|
||||
F-Chain. The substrate has zero runtime presence; it compiles into
|
||||
both chains' binaries.
|
||||
|
||||
## The three pieces
|
||||
|
||||
```
|
||||
+-----------------------------------------------------------+
|
||||
| Quasar 3.0 |
|
||||
| (consensus, certificate_subject binds all chain roots) |
|
||||
+-----------+--------------------------+--------------------+
|
||||
| |
|
||||
| mchain_ceremony_root | fchain_fhe_root
|
||||
| |
|
||||
+--------v---------+ +---------v--------+
|
||||
| M-Chain | | F-Chain |
|
||||
| validators | | validators |
|
||||
| + MPC drivers | | + GPU drivers |
|
||||
| + lane registry | | + lane registry |
|
||||
| (MChainCGGMP21, | | (FChainTFHE, |
|
||||
| MChainFROST, | | FChainBootstr) |
|
||||
| MChainRingTl) | | |
|
||||
+--------+---------+ +---------+--------+
|
||||
| |
|
||||
| imports |
|
||||
| |
|
||||
+----------+---------------+
|
||||
|
|
||||
+--------v---------+
|
||||
| thresholdvm |
|
||||
| (this library) |
|
||||
| |
|
||||
| types/ |
|
||||
| protocol/*/iface |
|
||||
| cert/lane,subj |
|
||||
| runtime/*Adapter |
|
||||
+------------------+
|
||||
```
|
||||
|
||||
## What lives where
|
||||
|
||||
| Concern | Owner |
|
||||
|---|---|
|
||||
| Ceremony state machine (Registered → Round1 → Round2 → Finalized) | substrate (`types/ceremony.go`) |
|
||||
| Share envelope `(offset, len)` indirection | substrate (`types/share.go`) |
|
||||
| Participant set + Selector contract | substrate (`types/participant.go`) |
|
||||
| Cert lane registry (per-owner enforcement) | substrate (`cert/lane.go`) |
|
||||
| `certificate_subject` binding | substrate (`cert/subject.go`) |
|
||||
| CGGMP21 / FROST / Corona-general drivers | M-Chain (`chains/mchain/protocol/*/`) |
|
||||
| TFHE bootstrap-key generation | M-Chain producer + F-Chain consumer |
|
||||
| TFHE compute kernels | F-Chain (`chains/fchain/`) |
|
||||
| Block production, gossip, p2p | each chain's VM |
|
||||
| Validator selection (stake-weighted VRF) | each chain's `Selector` impl |
|
||||
| Persistence | each chain's database |
|
||||
|
||||
## Why orthogonal at the type level
|
||||
|
||||
The substrate's `LaneRegistry` carries an `Owner` field. `Register`
|
||||
checks the verifier's lane against the owner's allowed range:
|
||||
|
||||
- `OwnerMChain` accepts lanes 5..7
|
||||
- `OwnerFChain` accepts lanes 8..9
|
||||
|
||||
A misrouted verifier (e.g. F-Chain trying to register a CGGMP21
|
||||
verifier) returns an error from `Register` at boot. The chain fails
|
||||
to start, not at runtime in the middle of a ceremony.
|
||||
|
||||
Similarly, `runtime.MChainAdapter` and `runtime.FChainAdapter` are
|
||||
disjoint interfaces. A chain implementing both is a code smell that
|
||||
shows up in code review (and in `go doc` output).
|
||||
|
||||
## Why one substrate, two chains
|
||||
|
||||
The state machine is identical. The envelope is identical. The cert
|
||||
binding is identical. The only thing that differs across the two
|
||||
chains is the per-protocol payload — which is opaque to the
|
||||
substrate and dispatched by lane.
|
||||
|
||||
If we kept M-Chain and F-Chain on separate substrates, we would have
|
||||
two copies of the state machine, two copies of the share envelope,
|
||||
two copies of the subject binding. Drift is inevitable; bugs would
|
||||
fix on one side and not the other.
|
||||
|
||||
The substrate is the orthogonal piece. The chains are the
|
||||
operational pieces. PHILOSOPHY.md §3.
|
||||
|
||||
## Cross-chain handoff
|
||||
|
||||
The only cross-chain ceremony today is **TFHE bootstrap-key gen**:
|
||||
|
||||
1. M-Chain runs FROST DKG on the TFHE secret-key polynomial. The
|
||||
ceremony has `Kind = KindTFHEKeygen`, `Lane = LaneMChainFROST`.
|
||||
2. M-Chain finalizes; the proof's payload is the serialized TFHE
|
||||
evaluation key.
|
||||
3. The substrate's handoff envelope wraps the M-Chain proof into a
|
||||
F-Chain `Share` with `Lane = LaneFChainBootstrap`. The wrapper's
|
||||
payload is `(offset, len)` into the upstream artifact buffer.
|
||||
4. F-Chain ingests via `OnBootstrapHandoff`, verifies the upstream
|
||||
M-Chain ceremony root from the round descriptor, installs the
|
||||
key, advances `fchain_fhe_root`.
|
||||
5. Quasar 3.0's `certificate_subject` for the round binds both
|
||||
`mchain_ceremony_root` (post-FROST) and `fchain_fhe_root`
|
||||
(post-install). Atomic from the network's view.
|
||||
|
||||
No new envelope type. The handoff is just two shares on two lanes
|
||||
with a deterministic wrapping.
|
||||
|
||||
## References
|
||||
|
||||
| LP | Topic |
|
||||
|---|---|
|
||||
| LP-019 | Threshold MPC |
|
||||
| LP-013 | FHE on GPU |
|
||||
| LP-076 | Universal Threshold Cryptography |
|
||||
| LP-132 | QuasarGPU Execution Adapter |
|
||||
| LP-134 | Lux Chain Topology |
|
||||
| LP-020 | Quasar Consensus 3.0 |
|
||||
@@ -0,0 +1,101 @@
|
||||
# F-Chain Integration
|
||||
|
||||
This document describes how `chains/fchain/` plugs into the
|
||||
ThresholdVM substrate.
|
||||
|
||||
## Boot sequence
|
||||
|
||||
```
|
||||
1. F-Chain VM starts.
|
||||
2. F-Chain constructs cert.NewRegistry(cert.OwnerFChain).
|
||||
3. F-Chain registers each protocol verifier:
|
||||
reg.Register(tfheVerifier{...}) // LaneFChainTFHE
|
||||
reg.Register(bootstrapVerifier{...}) // LaneFChainBootstrap
|
||||
4. During the LP-134 grace epoch, F-Chain wires legacy aliases:
|
||||
reg.RegisterLegacyAlias(LaneTChainFHE, LaneFChainTFHE)
|
||||
5. F-Chain implements runtime.FChainAdapter.
|
||||
6. F-Chain begins accepting blocks.
|
||||
```
|
||||
|
||||
## Validator subset
|
||||
|
||||
F-Chain validators are the subset of P-Chain validators with stake
|
||||
delegated to F-Chain. GPU operators self-select by delegating here
|
||||
— no allowlist; the chain rejects participants who can't keep up
|
||||
with bootstrap latency, but only at the validator-set level (slow
|
||||
nodes get pruned by economic incentives, not by an admin key).
|
||||
|
||||
## Ceremony cadence
|
||||
|
||||
F-Chain block time targets **~2 s** to amortize TFHE bootstrap
|
||||
latency. This means:
|
||||
|
||||
- A TFHE compute attestation ceremony finalizes in ~4 s.
|
||||
- A bootstrap proof finalizes in ~2 s (single-round attestation).
|
||||
- Bootstrap-key handoff from M-Chain takes one F-Chain block to
|
||||
ingest.
|
||||
|
||||
## Lanes F-Chain owns
|
||||
|
||||
| Lane | Verifier | Driver |
|
||||
|---|---|---|
|
||||
| LaneFChainTFHE | F-Chain TFHE compute attestation verifier | F-Chain `drain_fhe` service |
|
||||
| LaneFChainBootstrap | F-Chain bootstrap-handoff verifier (`protocol/tfhe_keygen.Consumer`) | none — bootstrap proofs come from M-Chain |
|
||||
|
||||
F-Chain **does not** own LaneMChain*. The registry refuses
|
||||
registration of MChain lanes under OwnerFChain.
|
||||
|
||||
## Bootstrap handoff (M → F)
|
||||
|
||||
When M-Chain finalizes a KindTFHEKeygen ceremony:
|
||||
|
||||
```
|
||||
M-Chain proof:
|
||||
Lane = LaneMChainFROST
|
||||
Payload = serialized TFHE evaluation key
|
||||
|
||||
Wrapped F-Chain share:
|
||||
Lane = LaneFChainBootstrap
|
||||
PayloadOffset/Len = window into the upstream artifact buffer
|
||||
Verifier (F-Chain): tfhe_keygen.Consumer.VerifyHandoff(...)
|
||||
checks the upstream M-Chain ceremony root
|
||||
referenced by certificate_subject.
|
||||
```
|
||||
|
||||
F-Chain's `runtime.FChainAdapter.OnBootstrapHandoff` takes the
|
||||
upstream proof, verifies it under the M-Chain ceremony root in the
|
||||
round descriptor, installs the new TFHE evaluation key into the
|
||||
F-Chain key arena, and advances `fchain_fhe_root`.
|
||||
|
||||
In the same Quasar 3.0 round, `certificate_subject` binds both
|
||||
`mchain_ceremony_root` (post-FROST) and `fchain_fhe_root`
|
||||
(post-install). The handoff is atomic from the network's view —
|
||||
either both roots advance or neither does.
|
||||
|
||||
## TFHE compute attestations
|
||||
|
||||
For each `fhe_*` precompile call on C-Chain, F-Chain runs a small
|
||||
attestation ceremony:
|
||||
|
||||
1. The C-Chain transaction triggers a `FheCompute` event consumed by
|
||||
F-Chain's `drain_fhe` service (LP-132 §FheCompute).
|
||||
2. F-Chain validators run the TFHE op on GPU, produce a compute
|
||||
transcript, and emit a `LaneFChainTFHE` share carrying the
|
||||
transcript's hash.
|
||||
3. The substrate's `LaneRegistry.Verify` dispatches to F-Chain's
|
||||
TFHE verifier, which checks the transcript matches the input
|
||||
ciphertexts and the published evaluation key.
|
||||
4. On finalize, the transcript hash advances `fchain_fhe_root` and
|
||||
the ciphertext output is delivered to C-Chain.
|
||||
|
||||
## Migration from T-Chain
|
||||
|
||||
LP-5013's T-Chain hosted FHE compute under a different chain ID.
|
||||
The migration path is:
|
||||
|
||||
- All FHE compute moves to F-Chain unchanged. Kernels (LP-013)
|
||||
port across — they were never tied to T-Chain semantics.
|
||||
- During the grace epoch, F-Chain accepts legacy `LaneTChainFHE`
|
||||
shares via `RegisterLegacyAlias` and dispatches them to
|
||||
`LaneFChainTFHE`.
|
||||
- After the grace epoch, F-Chain calls `ClearAliases()`.
|
||||
@@ -0,0 +1,96 @@
|
||||
# M-Chain Integration
|
||||
|
||||
This document describes how `chains/mchain/` plugs into the
|
||||
ThresholdVM substrate.
|
||||
|
||||
## Boot sequence
|
||||
|
||||
```
|
||||
1. M-Chain VM starts.
|
||||
2. M-Chain constructs cert.NewRegistry(cert.OwnerMChain).
|
||||
3. M-Chain registers each protocol verifier:
|
||||
reg.Register(cggmp21Verifier{...}) // LaneMChainCGGMP21
|
||||
reg.Register(frostVerifier{...}) // LaneMChainFROST
|
||||
reg.Register(coronaGenVerifier{...}) // LaneMChainCoronaGen
|
||||
4. During the LP-134 grace epoch, M-Chain wires legacy aliases:
|
||||
reg.RegisterLegacyAlias(LaneTChainSign, LaneMChainCGGMP21)
|
||||
// ... per the LP-134 deprecation table.
|
||||
5. M-Chain implements runtime.MChainAdapter and hands itself to the
|
||||
substrate's ceremony driver.
|
||||
6. M-Chain begins accepting blocks. Each ceremony round is an entry
|
||||
in the block, decoded as types.Share, validated via reg.Verify(),
|
||||
and accumulated into a types.CeremonyRound. At round-2 close,
|
||||
the substrate calls OnFinalize and the M-Chain validator subset
|
||||
emits the cert.
|
||||
```
|
||||
|
||||
## Validator subset
|
||||
|
||||
M-Chain validators are the subset of P-Chain validators with stake
|
||||
delegated to M-Chain. The substrate's `Selector` interface (see
|
||||
`types/participant.go`) is implemented by M-Chain over its own
|
||||
delegation table.
|
||||
|
||||
VRF seed: `H(epoch || pchain_validator_root || ceremony_kind)`.
|
||||
Stake-weighted: probability of selection ∝ delegated stake at the
|
||||
epoch cutoff. Same security analysis as Lux's existing P-Chain VRF.
|
||||
|
||||
Permissionless: any P-Chain validator can delegate to M-Chain. No
|
||||
allowlist, no admin key.
|
||||
|
||||
## Ceremony cadence
|
||||
|
||||
M-Chain block time targets **~500 ms**. This means:
|
||||
|
||||
- A 2-round CGGMP21 / FROST ceremony finalizes in ~1 s.
|
||||
- A pre-sign + sign CGGMP21 sequence is ~1.5 s.
|
||||
- Corona-general (2-round) is ~1 s.
|
||||
|
||||
GPU acceleration (LP-132) is optional; current M-Chain validators
|
||||
are CPU-only. F-Chain is where GPU lives.
|
||||
|
||||
## Lanes M-Chain owns
|
||||
|
||||
| Lane | Verifier | Driver |
|
||||
|---|---|---|
|
||||
| LaneMChainCGGMP21 | `protocol/cggmp21.Verifier` | `protocol/cggmp21.Driver` |
|
||||
| LaneMChainFROST | `protocol/frost.Verifier` | `protocol/frost.Driver` |
|
||||
| LaneMChainCoronaGen | `protocol/corona_general.Verifier` | `protocol/corona_general.Driver` |
|
||||
|
||||
M-Chain **does not** own LaneFChainTFHE or LaneFChainBootstrap. The
|
||||
substrate's registry refuses to register them under OwnerMChain.
|
||||
|
||||
## TFHE keygen — the cross-chain ceremony
|
||||
|
||||
M-Chain runs the FROST DKG that produces the TFHE evaluation key.
|
||||
Concretely:
|
||||
|
||||
```go
|
||||
// chains/mchain/ceremony/tfhe_keygen.go
|
||||
ceremony := types.Ceremony{
|
||||
Kind: types.KindTFHEKeygen, // cross-chain
|
||||
// ... rest as for any FROST DKG ...
|
||||
}
|
||||
proof, err := frostDriver.Finalize(ctx, ceremony.ID, round2Payloads)
|
||||
// proof.Lane == LaneMChainFROST
|
||||
// proof.Payload == serialized TFHE evaluation key
|
||||
adapter.OnFinalize(ctx, ceremony, proof)
|
||||
```
|
||||
|
||||
The substrate notices `Kind == KindTFHEKeygen` and routes the proof
|
||||
into the F-Chain handoff envelope (see
|
||||
`docs/F_CHAIN_INTEGRATION.md` §"Bootstrap handoff").
|
||||
|
||||
## Migration from T-Chain
|
||||
|
||||
LP-5013's T-Chain hosted both MPC and FHE. After the LP-134
|
||||
activation epoch:
|
||||
|
||||
- All T-Chain MPC ceremonies move to M-Chain unchanged. Same
|
||||
protocol semantics, same threshold parameters; only the chain ID
|
||||
and root labels change.
|
||||
- During the grace epoch, M-Chain's `LaneRegistry` accepts legacy
|
||||
T-Chain lane numbers via `RegisterLegacyAlias` and dispatches
|
||||
them to the modern `MChain*` verifiers.
|
||||
- After the grace epoch, M-Chain calls `ClearAliases()` and drops
|
||||
legacy support. Any client still emitting T-Chain shares fails.
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package cggmp21 declares the M-Chain CGGMP21 protocol surface.
|
||||
//
|
||||
// This package contains interfaces only. The concrete implementation
|
||||
// (key generation, pre-signing, signing, verification, identifiable
|
||||
// abort) lives in chains/mchain/protocol/cggmp21/. The substrate
|
||||
// imports this package; M-Chain implements it.
|
||||
package cggmp21
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/luxfi/chains/thresholdvm/types"
|
||||
)
|
||||
|
||||
// Verifier validates a CGGMP21 share payload. It is registered into
|
||||
// the M-Chain LaneRegistry as the LaneMChainCGGMP21 verifier.
|
||||
type Verifier interface {
|
||||
// VerifyShare checks that the per-round payload is a well-formed
|
||||
// CGGMP21 commitment / signature share. It does not produce or
|
||||
// hold any secret material.
|
||||
VerifyShare(subject [32]byte, share types.Share, payload []byte) error
|
||||
|
||||
// VerifyFinal checks that the aggregated proof is a valid
|
||||
// secp256k1 ECDSA signature under the group public key encoded in
|
||||
// the proof.
|
||||
VerifyFinal(subject [32]byte, proof types.Proof) error
|
||||
}
|
||||
|
||||
// Driver runs the protocol on a participant's behalf. Only validators
|
||||
// selected into the ceremony's participant set instantiate a Driver;
|
||||
// everyone else only verifies.
|
||||
type Driver interface {
|
||||
// Round1 emits the participant's round-1 payload (commitments).
|
||||
Round1(ctx context.Context, ceremony types.CeremonyID) ([]byte, error)
|
||||
|
||||
// Round2 emits the participant's round-2 payload (encrypted
|
||||
// shares), given the round-1 commitments from the rest of the
|
||||
// participant set.
|
||||
Round2(ctx context.Context, ceremony types.CeremonyID, round1 [][]byte) ([]byte, error)
|
||||
|
||||
// Finalize aggregates all round-2 payloads into the final proof.
|
||||
// Returns ErrIdentifiableAbort if a participant is detected
|
||||
// cheating; the abort proof identifies the cheater.
|
||||
Finalize(ctx context.Context, ceremony types.CeremonyID, round2 [][]byte) (types.Proof, error)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package corona_general declares the M-Chain general-purpose
|
||||
// Corona (Ring-LWE) threshold protocol surface.
|
||||
//
|
||||
// This is **not** consensus-Corona. Consensus uses Corona at
|
||||
// fixed parameters as the post-quantum cert lane (Q-Chain runs that
|
||||
// ceremony, see LP-073). General-purpose Corona is for app-level
|
||||
// threshold signing under a different parameter set; it lives on
|
||||
// M-Chain.
|
||||
//
|
||||
// Interface-only; implementation in chains/mchain/protocol/corona/.
|
||||
package corona_general
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/luxfi/chains/thresholdvm/types"
|
||||
)
|
||||
|
||||
// Verifier validates Corona-general share payloads. Registered as
|
||||
// the LaneMChainCoronaGen verifier in the M-Chain LaneRegistry.
|
||||
type Verifier interface {
|
||||
VerifyShare(subject [32]byte, share types.Share, payload []byte) error
|
||||
VerifyFinal(subject [32]byte, proof types.Proof) error
|
||||
}
|
||||
|
||||
// Driver runs the 2-round lattice protocol for a selected participant.
|
||||
type Driver interface {
|
||||
Round1(ctx context.Context, ceremony types.CeremonyID) ([]byte, error)
|
||||
Round2(ctx context.Context, ceremony types.CeremonyID, round1 [][]byte) ([]byte, error)
|
||||
Finalize(ctx context.Context, ceremony types.CeremonyID, round2 [][]byte) (types.Proof, error)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package frost declares the M-Chain FROST protocol surface.
|
||||
//
|
||||
// FROST is a 2-round Schnorr threshold scheme over Ed25519. This
|
||||
// package is interface-only; the implementation lives in
|
||||
// chains/mchain/protocol/frost/.
|
||||
package frost
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/luxfi/chains/thresholdvm/types"
|
||||
)
|
||||
|
||||
// Verifier validates FROST share payloads. Registered as the
|
||||
// LaneMChainFROST verifier in the M-Chain LaneRegistry.
|
||||
type Verifier interface {
|
||||
VerifyShare(subject [32]byte, share types.Share, payload []byte) error
|
||||
VerifyFinal(subject [32]byte, proof types.Proof) error
|
||||
}
|
||||
|
||||
// Driver runs FROST on a selected participant's behalf.
|
||||
type Driver interface {
|
||||
// Round1 emits the participant's nonce commitments (D_i, E_i).
|
||||
Round1(ctx context.Context, ceremony types.CeremonyID) ([]byte, error)
|
||||
// Round2 emits the signature share z_i.
|
||||
Round2(ctx context.Context, ceremony types.CeremonyID, round1 [][]byte) ([]byte, error)
|
||||
// Finalize aggregates {z_i} into the single Ed25519 (R, z).
|
||||
Finalize(ctx context.Context, ceremony types.CeremonyID, round2 [][]byte) (types.Proof, error)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package tfhe_keygen declares the cross-chain TFHE bootstrap-key
|
||||
// generation surface.
|
||||
//
|
||||
// The ceremony **runs on M-Chain** (it is an MPC ceremony of FROST
|
||||
// shape over the TFHE secret-key polynomial), but its **output is
|
||||
// consumed by F-Chain** as the new bootstrap key. The handoff is
|
||||
// represented in the substrate as a single Ceremony with kind
|
||||
// KindTFHEKeygen and Lane=LaneMChainFROST at finalize, then ingested
|
||||
// on F-Chain via Lane=LaneFChainBootstrap that wraps the upstream
|
||||
// artifact.
|
||||
//
|
||||
// Interface-only; implementations split between
|
||||
// chains/mchain/protocol/tfhe_keygen/ (the producer) and
|
||||
// chains/fchain/protocol/tfhe_bootstrap/ (the consumer).
|
||||
package tfhe_keygen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/luxfi/chains/thresholdvm/types"
|
||||
)
|
||||
|
||||
// Producer is implemented by M-Chain. It runs the MPC ceremony that
|
||||
// produces a TFHE evaluation key.
|
||||
type Producer interface {
|
||||
Round1(ctx context.Context, ceremony types.CeremonyID) ([]byte, error)
|
||||
Round2(ctx context.Context, ceremony types.CeremonyID, round1 [][]byte) ([]byte, error)
|
||||
// Finalize emits a Proof whose Payload is the serialized TFHE
|
||||
// evaluation key plus bootstrap material.
|
||||
Finalize(ctx context.Context, ceremony types.CeremonyID, round2 [][]byte) (types.Proof, error)
|
||||
}
|
||||
|
||||
// Consumer is implemented by F-Chain. It accepts the M-Chain proof,
|
||||
// re-verifies under the M-Chain ceremony root, and binds the new key
|
||||
// into fchain_fhe_root.
|
||||
type Consumer interface {
|
||||
// Ingest verifies the upstream proof and installs the bootstrap
|
||||
// key for use by F-Chain's FHE compute pipeline (LP-013).
|
||||
Ingest(ctx context.Context, mchainSubject [32]byte, proof types.Proof) error
|
||||
// VerifyHandoff is the per-share verifier on F-Chain's
|
||||
// LaneFChainBootstrap. It validates that a F-Chain bootstrap
|
||||
// share correctly references an M-Chain TFHE-keygen ceremony.
|
||||
VerifyHandoff(subject [32]byte, share types.Share, payload []byte) error
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/luxfi/chains/thresholdvm/cert"
|
||||
"github.com/luxfi/chains/thresholdvm/types"
|
||||
)
|
||||
|
||||
// FChainAdapter is the contract F-Chain implements. Same state
|
||||
// machine as M-Chain, different hooks: F-Chain consumes upstream MPC
|
||||
// outputs and emits FHE compute attestations.
|
||||
type FChainAdapter interface {
|
||||
// Selector returns the F-Chain participant selector. Stake-weighted
|
||||
// over P-Chain stake delegated to F-Chain. GPU operators
|
||||
// self-select by delegating here rather than to M-Chain.
|
||||
Selector() types.Selector
|
||||
|
||||
// Registry returns the F-Chain LaneRegistry. Must be owned by
|
||||
// OwnerFChain and have verifiers for LaneFChainTFHE,
|
||||
// LaneFChainBootstrap registered.
|
||||
Registry() *cert.LaneRegistry
|
||||
|
||||
// FHERoot returns fchain_fhe_root at the current epoch. Bound
|
||||
// into certificate_subject alongside mchain_ceremony_root.
|
||||
FHERoot(ctx context.Context, epoch uint64) ([32]byte, error)
|
||||
|
||||
// OnBootstrapHandoff is called by the substrate when M-Chain
|
||||
// finalizes a KindTFHEKeygen ceremony and hands off the bootstrap
|
||||
// key. F-Chain re-verifies under the M-Chain ceremony root and
|
||||
// installs the key.
|
||||
OnBootstrapHandoff(ctx context.Context, mchainSubject [32]byte, proof types.Proof) error
|
||||
|
||||
// OnEval is called for a TFHE compute ceremony. The participant
|
||||
// set is the F-Chain GPU operators selected by VRF.
|
||||
OnEval(ctx context.Context, ceremony types.Ceremony, set *types.ParticipantSet) error
|
||||
|
||||
// OnFinalize emits the finalized cert artifact onto Quasar 3.0's
|
||||
// CertLane and advances fchain_fhe_root.
|
||||
OnFinalize(ctx context.Context, ceremony types.Ceremony, proof types.Proof) error
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package runtime defines the adapter contracts the host chains
|
||||
// (M-Chain and F-Chain) implement to plug into the ThresholdVM
|
||||
// substrate.
|
||||
//
|
||||
// The substrate calls into adapters; adapters never call into the
|
||||
// substrate. This is the orthogonality boundary at the type level:
|
||||
// MChainAdapter exposes only MPC hooks, FChainAdapter exposes only
|
||||
// FHE hooks. A chain that implements both interfaces is a
|
||||
// configuration error.
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/luxfi/chains/thresholdvm/cert"
|
||||
"github.com/luxfi/chains/thresholdvm/types"
|
||||
)
|
||||
|
||||
// MChainAdapter is the contract M-Chain implements. The substrate
|
||||
// drives ceremonies by calling these hooks; M-Chain provides the
|
||||
// validator-set selection, persistence, and cert-emission.
|
||||
type MChainAdapter interface {
|
||||
// Selector returns the M-Chain participant selector. Stake-weighted
|
||||
// VRF over P-Chain stake delegated to M-Chain.
|
||||
Selector() types.Selector
|
||||
|
||||
// Registry returns the M-Chain LaneRegistry. Must be owned by
|
||||
// OwnerMChain and have verifiers for LaneMChainCGGMP21,
|
||||
// LaneMChainFROST, LaneMChainCoronaGen registered.
|
||||
Registry() *cert.LaneRegistry
|
||||
|
||||
// CeremonyRoot returns mchain_ceremony_root at the current epoch.
|
||||
// Bound into certificate_subject by Quasar 3.0.
|
||||
CeremonyRoot(ctx context.Context, epoch uint64) ([32]byte, error)
|
||||
|
||||
// OnDKG is called by the substrate when a new DKG ceremony enters
|
||||
// StateRegistered. M-Chain persists the participant set and
|
||||
// allocates the payload arena.
|
||||
OnDKG(ctx context.Context, ceremony types.Ceremony, set *types.ParticipantSet) error
|
||||
|
||||
// OnSign is called for a signing ceremony. Same shape as OnDKG;
|
||||
// distinguished only by Ceremony.Kind.
|
||||
OnSign(ctx context.Context, ceremony types.Ceremony, set *types.ParticipantSet) error
|
||||
|
||||
// OnReshare is called for an LSS resharing ceremony (LP-077). The
|
||||
// new participant set replaces the old one atomically at
|
||||
// finalize.
|
||||
OnReshare(ctx context.Context, ceremony types.Ceremony, oldSet, newSet *types.ParticipantSet) error
|
||||
|
||||
// OnFinalize emits the finalized cert artifact onto Quasar 3.0's
|
||||
// CertLane and advances mchain_ceremony_root. If the ceremony's
|
||||
// kind is KindTFHEKeygen, the proof is also handed off to F-Chain
|
||||
// via the cross-chain envelope.
|
||||
OnFinalize(ctx context.Context, ceremony types.Ceremony, proof types.Proof) error
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package types defines the data types of the ThresholdVM substrate.
|
||||
//
|
||||
// These types are imported by both M-Chain (chains/mchain) and F-Chain
|
||||
// (chains/fchain). The substrate has no runtime; types here are pure
|
||||
// values, the state machine is an explicit-transition function, and
|
||||
// the registry types in cert/ are constructed (not singletons) by the
|
||||
// host chain.
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// CeremonyState is the position of a ceremony in the shared state
|
||||
// machine. The same machine is used by every protocol on M-Chain and
|
||||
// F-Chain; only the per-round payload differs.
|
||||
type CeremonyState uint8
|
||||
|
||||
const (
|
||||
StateUnknown CeremonyState = 0
|
||||
StateRegistered CeremonyState = 1 // participant set committed, no shares yet
|
||||
StateRound1 CeremonyState = 2 // round-1 commitments published
|
||||
StateRound2 CeremonyState = 3 // round-2 shares published
|
||||
StateFinalized CeremonyState = 4 // cert artifact emitted, root advanced
|
||||
StateAborted CeremonyState = 5 // identifiable abort (CGGMP21 etc.) or timeout
|
||||
)
|
||||
|
||||
// String returns a stable label for logs.
|
||||
func (s CeremonyState) String() string {
|
||||
switch s {
|
||||
case StateRegistered:
|
||||
return "registered"
|
||||
case StateRound1:
|
||||
return "round1"
|
||||
case StateRound2:
|
||||
return "round2"
|
||||
case StateFinalized:
|
||||
return "finalized"
|
||||
case StateAborted:
|
||||
return "aborted"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// CeremonyKind names the protocol the ceremony executes. The kind
|
||||
// determines which CertLane the finalized artifact uses and which
|
||||
// verifier validates each round's payload. Adding a new kind appends
|
||||
// here and adds a CertLane in cert/lane.go — never reorders.
|
||||
type CeremonyKind uint8
|
||||
|
||||
const (
|
||||
KindUnknown CeremonyKind = 0
|
||||
KindCGGMP21 CeremonyKind = 1 // M-Chain: ECDSA threshold
|
||||
KindFROST CeremonyKind = 2 // M-Chain: Schnorr/EdDSA threshold
|
||||
KindCoronaGen CeremonyKind = 3 // M-Chain: PQ general-purpose threshold
|
||||
KindTFHEKeygen CeremonyKind = 4 // M-Chain → F-Chain: TFHE bootstrap-key gen
|
||||
KindTFHECompute CeremonyKind = 5 // F-Chain: encrypted compute attestation
|
||||
KindTFHEBootstrap CeremonyKind = 6 // F-Chain: blind-rotate / bootstrap proof
|
||||
)
|
||||
|
||||
// IsMChain reports whether the kind belongs to the M-Chain operational chain.
|
||||
func (k CeremonyKind) IsMChain() bool {
|
||||
return k == KindCGGMP21 || k == KindFROST || k == KindCoronaGen
|
||||
}
|
||||
|
||||
// IsFChain reports whether the kind belongs to the F-Chain operational chain.
|
||||
func (k CeremonyKind) IsFChain() bool {
|
||||
return k == KindTFHECompute || k == KindTFHEBootstrap
|
||||
}
|
||||
|
||||
// IsHandoff reports whether the kind is a cross-chain (M → F) ceremony.
|
||||
// Handoff ceremonies originate on M-Chain and finalize into F-Chain via
|
||||
// the bootstrap-handoff envelope.
|
||||
func (k CeremonyKind) IsHandoff() bool {
|
||||
return k == KindTFHEKeygen
|
||||
}
|
||||
|
||||
// CeremonyID is the canonical identifier for a ceremony, derived from
|
||||
// the Quasar 3.0 round descriptor and the ceremony kind. It is the
|
||||
// 32-byte hash that ties shares, proofs, and cert subjects together.
|
||||
type CeremonyID [32]byte
|
||||
|
||||
// Ceremony describes a single threshold ceremony on either chain.
|
||||
//
|
||||
// Ceremony is a pure data type. State transitions are produced by
|
||||
// Transition() in this package; the host chain (M-Chain or F-Chain)
|
||||
// drives transitions on its own block ticks.
|
||||
type Ceremony struct {
|
||||
ID CeremonyID
|
||||
Kind CeremonyKind
|
||||
State CeremonyState
|
||||
Round uint8 // 1..N, valid only when State is Round1 or Round2
|
||||
Threshold uint16
|
||||
Total uint16 // total participants
|
||||
StartEpoch uint64
|
||||
Subject [32]byte // certificate_subject the ceremony binds into
|
||||
// PayloadArena is the per-ceremony buffer that all share payloads
|
||||
// index into via (offset, len). Owned by the host chain; the
|
||||
// substrate never allocates.
|
||||
PayloadArena []byte
|
||||
}
|
||||
|
||||
// Validate checks structural invariants. Use at boundaries only; the
|
||||
// substrate trusts its own callers (the host chain) for routine
|
||||
// transitions per PHILOSOPHY.md "defensive programming against
|
||||
// yourself".
|
||||
func (c *Ceremony) Validate() error {
|
||||
if c == nil {
|
||||
return errors.New("ceremony: nil")
|
||||
}
|
||||
if c.Kind == KindUnknown {
|
||||
return errors.New("ceremony: kind unset")
|
||||
}
|
||||
if c.Total == 0 {
|
||||
return errors.New("ceremony: total participants is zero")
|
||||
}
|
||||
// Honest-majority safety floor (LP-076 §Security).
|
||||
if uint32(c.Threshold)*2 <= uint32(c.Total) {
|
||||
return fmt.Errorf("ceremony: threshold %d <= n/2 for total %d", c.Threshold, c.Total)
|
||||
}
|
||||
if c.Threshold > c.Total {
|
||||
return fmt.Errorf("ceremony: threshold %d > total %d", c.Threshold, c.Total)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Transition advances the ceremony state. It is the only function
|
||||
// that mutates State. Returns the post-transition copy or an error
|
||||
// if the transition is illegal.
|
||||
//
|
||||
// Legal transitions:
|
||||
//
|
||||
// Registered -> Round1
|
||||
// Round1 -> Round2
|
||||
// Round2 -> Finalized
|
||||
// * -> Aborted (always permitted)
|
||||
func (c Ceremony) Transition(next CeremonyState) (Ceremony, error) {
|
||||
if next == StateAborted {
|
||||
c.State = StateAborted
|
||||
return c, nil
|
||||
}
|
||||
switch c.State {
|
||||
case StateRegistered:
|
||||
if next != StateRound1 {
|
||||
return c, fmt.Errorf("ceremony: registered -> %s not legal", next)
|
||||
}
|
||||
c.State = StateRound1
|
||||
c.Round = 1
|
||||
case StateRound1:
|
||||
if next != StateRound2 {
|
||||
return c, fmt.Errorf("ceremony: round1 -> %s not legal", next)
|
||||
}
|
||||
c.State = StateRound2
|
||||
c.Round = 2
|
||||
case StateRound2:
|
||||
if next != StateFinalized {
|
||||
return c, fmt.Errorf("ceremony: round2 -> %s not legal", next)
|
||||
}
|
||||
c.State = StateFinalized
|
||||
default:
|
||||
return c, fmt.Errorf("ceremony: %s -> %s not legal", c.State, next)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// CeremonyRound is the per-round bag of share commitments for a single
|
||||
// ceremony. The host chain accumulates these in memory while the
|
||||
// ceremony is in Round1 / Round2 and finalizes them into a CertLane
|
||||
// artifact at Finalize.
|
||||
type CeremonyRound struct {
|
||||
CeremonyID CeremonyID
|
||||
Round uint8
|
||||
Shares []Share // populated as each participant publishes
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package types
|
||||
|
||||
import "testing"
|
||||
|
||||
func newCeremony() Ceremony {
|
||||
return Ceremony{
|
||||
ID: CeremonyID{0xAA},
|
||||
Kind: KindFROST,
|
||||
State: StateRegistered,
|
||||
Threshold: 3,
|
||||
Total: 5,
|
||||
}
|
||||
}
|
||||
|
||||
func TestCeremony_LegalTransitions(t *testing.T) {
|
||||
c := newCeremony()
|
||||
if err := c.Validate(); err != nil {
|
||||
t.Fatalf("validate: %v", err)
|
||||
}
|
||||
c, err := c.Transition(StateRound1)
|
||||
if err != nil || c.State != StateRound1 || c.Round != 1 {
|
||||
t.Fatalf("registered->round1: %v state=%s round=%d", err, c.State, c.Round)
|
||||
}
|
||||
c, err = c.Transition(StateRound2)
|
||||
if err != nil || c.State != StateRound2 || c.Round != 2 {
|
||||
t.Fatalf("round1->round2: %v state=%s round=%d", err, c.State, c.Round)
|
||||
}
|
||||
c, err = c.Transition(StateFinalized)
|
||||
if err != nil || c.State != StateFinalized {
|
||||
t.Fatalf("round2->finalized: %v state=%s", err, c.State)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCeremony_IllegalTransitionsRejected(t *testing.T) {
|
||||
c := newCeremony()
|
||||
if _, err := c.Transition(StateFinalized); err == nil {
|
||||
t.Fatal("registered -> finalized should be rejected")
|
||||
}
|
||||
if _, err := c.Transition(StateRound2); err == nil {
|
||||
t.Fatal("registered -> round2 should be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCeremony_AbortAlwaysAllowed(t *testing.T) {
|
||||
c := newCeremony()
|
||||
c, err := c.Transition(StateAborted)
|
||||
if err != nil || c.State != StateAborted {
|
||||
t.Fatalf("registered -> aborted: %v state=%s", err, c.State)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCeremony_HonestMajorityFloor(t *testing.T) {
|
||||
c := Ceremony{Kind: KindFROST, Threshold: 2, Total: 5}
|
||||
if err := c.Validate(); err == nil {
|
||||
t.Fatal("threshold <= n/2 should be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCeremonyKind_OrthogonalityFlags(t *testing.T) {
|
||||
if !KindFROST.IsMChain() || KindFROST.IsFChain() {
|
||||
t.Fatal("FROST is M-Chain only")
|
||||
}
|
||||
if !KindTFHEBootstrap.IsFChain() || KindTFHEBootstrap.IsMChain() {
|
||||
t.Fatal("TFHEBootstrap is F-Chain only")
|
||||
}
|
||||
if !KindTFHEKeygen.IsHandoff() {
|
||||
t.Fatal("TFHEKeygen is the cross-chain handoff kind")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// NodeID identifies a Lux validator. Same definition as the rest of
|
||||
// luxfi/node — 32 bytes, content-addressed.
|
||||
type NodeID [32]byte
|
||||
|
||||
// Participant is one validator selected (via stake-weighted VRF) into
|
||||
// the participant set of a ceremony. The substrate does not pick
|
||||
// participants; the host chain (M-Chain or F-Chain) does, then hands
|
||||
// the resulting set here.
|
||||
type Participant struct {
|
||||
Node NodeID
|
||||
Index uint32 // canonical index inside the ParticipantSet (0..n-1)
|
||||
Weight uint64 // stake weight at selection time, for accounting only
|
||||
}
|
||||
|
||||
// ParticipantSet is the deterministic, ordered set of validators for
|
||||
// one ceremony. The set is fixed at registration; resharing is a new
|
||||
// ceremony with a new set, never an in-place mutation.
|
||||
type ParticipantSet struct {
|
||||
CeremonyID CeremonyID
|
||||
Members []Participant // sorted ascending by NodeID; Index == position
|
||||
}
|
||||
|
||||
// NewParticipantSet builds a ParticipantSet, sorting members by NodeID
|
||||
// and assigning Index = position. Returns an error if the input has
|
||||
// duplicate nodes or is empty.
|
||||
func NewParticipantSet(ceremonyID CeremonyID, members []Participant) (*ParticipantSet, error) {
|
||||
if len(members) == 0 {
|
||||
return nil, errors.New("participant: empty set")
|
||||
}
|
||||
out := make([]Participant, len(members))
|
||||
copy(out, members)
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
for k := 0; k < len(out[i].Node); k++ {
|
||||
if out[i].Node[k] != out[j].Node[k] {
|
||||
return out[i].Node[k] < out[j].Node[k]
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
for i := 1; i < len(out); i++ {
|
||||
if out[i-1].Node == out[i].Node {
|
||||
return nil, errors.New("participant: duplicate node in set")
|
||||
}
|
||||
}
|
||||
for i := range out {
|
||||
out[i].Index = uint32(i)
|
||||
}
|
||||
return &ParticipantSet{CeremonyID: ceremonyID, Members: out}, nil
|
||||
}
|
||||
|
||||
// Lookup returns the participant for a node, or false if absent.
|
||||
func (ps *ParticipantSet) Lookup(node NodeID) (Participant, bool) {
|
||||
for _, p := range ps.Members {
|
||||
if p.Node == node {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
return Participant{}, false
|
||||
}
|
||||
|
||||
// Digest returns the canonical 32-byte digest of the set. Used as the
|
||||
// participant_root when a ceremony is registered, and as input to the
|
||||
// stake-weighted VRF for the next ceremony.
|
||||
func (ps *ParticipantSet) Digest() [32]byte {
|
||||
h := sha256.New()
|
||||
h.Write(ps.CeremonyID[:])
|
||||
var buf [12]byte
|
||||
for _, m := range ps.Members {
|
||||
h.Write(m.Node[:])
|
||||
binary.BigEndian.PutUint32(buf[0:4], m.Index)
|
||||
binary.BigEndian.PutUint64(buf[4:12], m.Weight)
|
||||
h.Write(buf[:])
|
||||
}
|
||||
var out [32]byte
|
||||
copy(out[:], h.Sum(nil))
|
||||
return out
|
||||
}
|
||||
|
||||
// Selector is implemented by the host chain (M-Chain or F-Chain). The
|
||||
// substrate consumes a Selector to produce a ParticipantSet at
|
||||
// ceremony registration time.
|
||||
//
|
||||
// Selectors must be:
|
||||
//
|
||||
// - deterministic: same input -> same output, byte for byte
|
||||
// - permissionless: any validator with stake delegated to the chain
|
||||
// is eligible (no allowlist)
|
||||
// - stake-weighted: probability of selection is proportional to
|
||||
// stake delegated to the chain at the cutoff epoch
|
||||
//
|
||||
// The substrate does not implement Selector; M-Chain and F-Chain each
|
||||
// provide one wired to their stake delegation.
|
||||
type Selector interface {
|
||||
Select(ceremonyID CeremonyID, total uint16, seed [32]byte) (*ParticipantSet, error)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package types
|
||||
|
||||
// Proof is the verification artifact emitted at ceremony Finalize. It
|
||||
// is consumed by Quasar 3.0's cert pipeline (LP-020) and bound into
|
||||
// certificate_subject (LP-134).
|
||||
//
|
||||
// The substrate defines the envelope; the per-protocol payload (the
|
||||
// actual signature, group public key, FHE key, etc.) is opaque bytes
|
||||
// indexed by Lane. Verifiers in cert/lane.go decode it.
|
||||
type Proof struct {
|
||||
CeremonyID CeremonyID
|
||||
Kind CeremonyKind
|
||||
Lane CertLane
|
||||
// Payload is the protocol-specific final artifact: a signature
|
||||
// for sign-oriented ceremonies, a TFHE evaluation key for keygen
|
||||
// ceremonies, etc.
|
||||
Payload []byte
|
||||
// Aggregate is the aggregate signature over the participant
|
||||
// commitments (used when the chain itself attests to the
|
||||
// ceremony before forwarding to Quasar).
|
||||
Aggregate [64]byte
|
||||
}
|
||||
|
||||
// Empty reports whether p is the zero value.
|
||||
func (p Proof) Empty() bool {
|
||||
return p.Kind == KindUnknown && len(p.Payload) == 0
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// CertLane mirrors LP-134 §QuasarCertLane. We declare it here in the
|
||||
// substrate because the share envelope is the wire form on which
|
||||
// every chain's verifier dispatches. The values must match LP-134
|
||||
// exactly. New lanes append; values never move.
|
||||
type CertLane uint8
|
||||
|
||||
const (
|
||||
LaneBLS CertLane = 0
|
||||
LaneCorona CertLane = 1
|
||||
LaneMLDSAGroth16 CertLane = 2
|
||||
LaneAChainAttest CertLane = 3
|
||||
LaneBChainBridge CertLane = 4
|
||||
LaneMChainCGGMP21 CertLane = 5
|
||||
LaneMChainFROST CertLane = 6
|
||||
LaneMChainCoronaGen CertLane = 7
|
||||
LaneFChainTFHE CertLane = 8
|
||||
LaneFChainBootstrap CertLane = 9
|
||||
)
|
||||
|
||||
// IsMChain reports whether the lane is owned by M-Chain.
|
||||
func (l CertLane) IsMChain() bool {
|
||||
return l == LaneMChainCGGMP21 || l == LaneMChainFROST || l == LaneMChainCoronaGen
|
||||
}
|
||||
|
||||
// IsFChain reports whether the lane is owned by F-Chain.
|
||||
func (l CertLane) IsFChain() bool {
|
||||
return l == LaneFChainTFHE || l == LaneFChainBootstrap
|
||||
}
|
||||
|
||||
// Share is the per-participant, per-round share envelope.
|
||||
//
|
||||
// The envelope is fixed; per-protocol payload is carried via
|
||||
// (PayloadOffset, PayloadLen) indirection into a Ceremony's
|
||||
// PayloadArena. This matches the QuasarCertIngress wire ABI from
|
||||
// LP-132 §drain_cert_lane: the verifier never decodes the share
|
||||
// itself, only the payload window it points at.
|
||||
type Share struct {
|
||||
CeremonyID CeremonyID
|
||||
ParticipantID uint32 // index into ParticipantSet.Members
|
||||
Round uint8 // 1..N, ceremony-kind specific
|
||||
Lane CertLane // dispatches to the per-protocol verifier
|
||||
PayloadOffset uint32
|
||||
PayloadLen uint32
|
||||
Signature [64]byte // BLS or ML-DSA over (CeremonyID, Round, payload)
|
||||
}
|
||||
|
||||
// PayloadFrom returns a slice into arena described by the share. It
|
||||
// returns an error only if the share is structurally inconsistent
|
||||
// with the arena length; payload semantics are the verifier's
|
||||
// responsibility.
|
||||
func (s Share) PayloadFrom(arena []byte) ([]byte, error) {
|
||||
end := uint64(s.PayloadOffset) + uint64(s.PayloadLen)
|
||||
if end > uint64(len(arena)) {
|
||||
return nil, fmt.Errorf("share: payload [%d..%d] out of arena (%d)",
|
||||
s.PayloadOffset, end, len(arena))
|
||||
}
|
||||
return arena[s.PayloadOffset : s.PayloadOffset+s.PayloadLen], nil
|
||||
}
|
||||
|
||||
// Validate checks the envelope-level invariants. Payload validation
|
||||
// is the verifier's job (each protocol verifies its own payload).
|
||||
func (s Share) Validate(set *ParticipantSet) error {
|
||||
if set == nil {
|
||||
return errors.New("share: nil participant set")
|
||||
}
|
||||
if s.CeremonyID != set.CeremonyID {
|
||||
return errors.New("share: ceremony id does not match participant set")
|
||||
}
|
||||
if s.ParticipantID >= uint32(len(set.Members)) {
|
||||
return fmt.Errorf("share: participant %d >= set size %d",
|
||||
s.ParticipantID, len(set.Members))
|
||||
}
|
||||
if s.Round == 0 {
|
||||
return errors.New("share: round 0 reserved")
|
||||
}
|
||||
if s.PayloadLen == 0 {
|
||||
return errors.New("share: empty payload")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user