mpcvm: one way for a lane to reach its verifier, and no dead contracts

The lane registry had two paths to a verifier. Register refuses a lane that
does not belong to the registry's owner, and its comment says that is what
enforces M/F orthogonality. RegisterLegacyAlias then mapped any id at all onto
a registered verifier, checking only that the TARGET existed and never that the
legacy id belonged to the owner -- so the alias walked around the invariant
Register exists to hold. The test demonstrated it: an arbitrary id 200, owned by
neither chain, dispatching to an M-Chain verifier.

It bought a grace window for LP-5013 T-Chain lane ids. LP-134 dissolved T-Chain
with zero remainder, so those ids cannot arrive. Nothing outside the test ever
registered an alias. Gone, with the aliases map and ClearAliases: a lane this
chain does not own now has no verifier, which is the ownership rule still
holding at dispatch.

Deleting mpcvm/runtime's two adapter interfaces emptied the package. FChainAdapter
and MChainAdapter were declared, never implemented and never called -- every
reference to either was inside its own file.
This commit is contained in:
2026-08-28 11:03:37 -07:00
parent 6419047cdf
commit 2b45e26ccc
4 changed files with 3 additions and 151 deletions
+3 -26
View File
@@ -49,9 +49,6 @@ const (
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.
@@ -59,7 +56,6 @@ func NewRegistry(owner Owner) *LaneRegistry {
return &LaneRegistry{
owner: owner,
verifier: make(map[types.CertLane]LaneVerifier),
aliases: make(map[types.CertLane]types.CertLane),
}
}
@@ -90,29 +86,10 @@ func (r *LaneRegistry) Register(v LaneVerifier) error {
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.
// Verifier resolves a lane to its verifier. Returns an error if no verifier
// is registered — a lane this chain does not own has none, which is how
// Register's ownership rule keeps holding at dispatch time.
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)
-23
View File
@@ -87,26 +87,3 @@ func TestRegister_RejectsDoubleRegistration(t *testing.T) {
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")
}
}
-44
View File
@@ -1,44 +0,0 @@
// 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/mpcvm/cert"
"github.com/luxfi/chains/mpcvm/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
}
-58
View File
@@ -1,58 +0,0 @@
// 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/mpcvm/cert"
"github.com/luxfi/chains/mpcvm/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
}