mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
docs/ and DESIGN.md described protocol/{cggmp21,frost,corona_general,
tfhe_keygen} — four interface-only packages with no implementation and no
importer, deleted this pass — and pointed at a chains/mchain/ tree that does
not exist, which LLM.md already says never did. Prose describing an unbuilt
system reads as a contract; the reader who believes it looks for code that
isn't there.
What is true moves into LLM.md: the constants that must stay constants because
Verify enforces them, the two checks that must read consensus state rather than
the document being checked, and the boot fault that made a share-holding
validator unable to restart.
226 lines
6.9 KiB
Go
226 lines
6.9 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package mpcvm
|
|
|
|
// executor.go — the one place that knows the threshold library exists.
|
|
//
|
|
// M-Chain runs exactly one threshold protocol: CGGMP21 (cmp), the threshold
|
|
// ECDSA that produces signatures an external chain's ecrecover accepts. There
|
|
// is no protocol registry and no selection: a custody key's Kind names the
|
|
// protocol it was generated with, and cggmp21 is the only value that can be
|
|
// written. A registry offering four protocols and implementing one is not a
|
|
// choice, and a caller that reaches the other three finds an error where a
|
|
// signature should be.
|
|
//
|
|
// Everything above this file speaks in ceremonies; everything below it is
|
|
// github.com/luxfi/threshold. The MessageRouter is the seam: the executor
|
|
// drives a per-party protocol.Handler and the router moves that handler's
|
|
// opaque messages to the other parties (transport.go).
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/luxfi/log"
|
|
"github.com/luxfi/threshold/pkg/ecdsa"
|
|
"github.com/luxfi/threshold/pkg/math/curve"
|
|
"github.com/luxfi/threshold/pkg/party"
|
|
"github.com/luxfi/threshold/pkg/pool"
|
|
"github.com/luxfi/threshold/pkg/protocol"
|
|
|
|
"github.com/luxfi/threshold/protocols/cmp"
|
|
cmpconfig "github.com/luxfi/threshold/protocols/cmp/config"
|
|
)
|
|
|
|
// MessageRouter carries one ceremony's protocol messages between parties. It
|
|
// never interprets them: the cryptography is the threshold library's, the
|
|
// delivery is the router's, and neither knows the other's business.
|
|
type MessageRouter interface {
|
|
// Send sends a message to the party it addresses, or broadcasts it to the
|
|
// whole ceremony when To is empty.
|
|
Send(msg *protocol.Message) error
|
|
// Receive yields messages addressed to this party.
|
|
Receive() <-chan *protocol.Message
|
|
}
|
|
|
|
// ProtocolExecutor drives threshold protocols to completion over a router.
|
|
type ProtocolExecutor struct {
|
|
pool *pool.Pool
|
|
logger log.Logger
|
|
|
|
// handlers are the ceremonies running right now, keyed by session id, so a
|
|
// ceremony can be torn down by name when it finishes or its context dies.
|
|
mu sync.RWMutex
|
|
handlers map[string]*protocol.Handler
|
|
}
|
|
|
|
// NewProtocolExecutor creates a new protocol executor.
|
|
func NewProtocolExecutor(workerPool *pool.Pool, logger log.Logger) *ProtocolExecutor {
|
|
return &ProtocolExecutor{
|
|
pool: workerPool,
|
|
logger: logger,
|
|
handlers: make(map[string]*protocol.Handler),
|
|
}
|
|
}
|
|
|
|
// CreateHandler starts one ceremony and registers it under sessionID.
|
|
func (pe *ProtocolExecutor) CreateHandler(
|
|
ctx context.Context,
|
|
sessionID string,
|
|
startFunc protocol.StartFunc,
|
|
) (*protocol.Handler, error) {
|
|
handler, err := protocol.NewHandler(
|
|
ctx,
|
|
pe.logger,
|
|
nil, // no metrics registry
|
|
startFunc,
|
|
[]byte(sessionID),
|
|
protocol.DefaultConfig(),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mpcvm: create ceremony handler: %w", err)
|
|
}
|
|
|
|
pe.mu.Lock()
|
|
pe.handlers[sessionID] = handler
|
|
pe.mu.Unlock()
|
|
|
|
return handler, nil
|
|
}
|
|
|
|
// RemoveHandler stops a ceremony and forgets it. Idempotent.
|
|
func (pe *ProtocolExecutor) RemoveHandler(sessionID string) {
|
|
pe.mu.Lock()
|
|
defer pe.mu.Unlock()
|
|
if handler, ok := pe.handlers[sessionID]; ok {
|
|
handler.Stop()
|
|
delete(pe.handlers, sessionID)
|
|
}
|
|
}
|
|
|
|
// Live reports how many ceremonies this executor is driving. It is what a
|
|
// leaked handler shows up in.
|
|
func (pe *ProtocolExecutor) Live() int {
|
|
pe.mu.RLock()
|
|
defer pe.mu.RUnlock()
|
|
return len(pe.handlers)
|
|
}
|
|
|
|
// RunCMPKeygen runs one distributed key generation to completion. threshold is
|
|
// the polynomial degree t, never the signer count: see quorum.Policy.Degree.
|
|
func (pe *ProtocolExecutor) RunCMPKeygen(
|
|
ctx context.Context,
|
|
sessionID string,
|
|
selfID party.ID,
|
|
participants []party.ID,
|
|
threshold int,
|
|
router MessageRouter,
|
|
) (*cmpconfig.Config, error) {
|
|
start := cmp.Keygen(curve.Secp256k1{}, selfID, participants, threshold, pe.pool)
|
|
return runProtocol[*cmpconfig.Config](ctx, pe, sessionID, start, router)
|
|
}
|
|
|
|
// RunCMPSign runs one signing ceremony to completion and returns the canonical
|
|
// Ethereum r‖s‖v encoding.
|
|
func (pe *ProtocolExecutor) RunCMPSign(
|
|
ctx context.Context,
|
|
sessionID string,
|
|
config *cmpconfig.Config,
|
|
signers []party.ID,
|
|
messageHash []byte,
|
|
router MessageRouter,
|
|
) (*ECDSASignature, error) {
|
|
start := cmp.Sign(config, signers, messageHash, pe.pool)
|
|
// cmp.Sign yields *ecdsa.Signature (R point, S scalar). Asserting the
|
|
// protocol result straight to *ECDSASignature never matches — it produced
|
|
// "unexpected result type" on every signature. Assert the real type, then
|
|
// convert.
|
|
sig, err := runProtocol[*ecdsa.Signature](ctx, pe, sessionID, start, router)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ecdsaSigToWrapper(sig)
|
|
}
|
|
|
|
// runProtocol drives one ceremony to its result, moving messages both ways over
|
|
// the router for as long as it runs.
|
|
func runProtocol[T any](
|
|
ctx context.Context,
|
|
pe *ProtocolExecutor,
|
|
sessionID string,
|
|
startFunc protocol.StartFunc,
|
|
router MessageRouter,
|
|
) (T, error) {
|
|
var zero T
|
|
|
|
handler, err := pe.CreateHandler(ctx, sessionID, startFunc)
|
|
if err != nil {
|
|
return zero, err
|
|
}
|
|
defer pe.RemoveHandler(sessionID)
|
|
|
|
done := make(chan struct{})
|
|
var routerErr error
|
|
|
|
// Outgoing: everything this party emits goes to the other parties.
|
|
go func() {
|
|
defer close(done)
|
|
for msg := range handler.Listen() {
|
|
if err := router.Send(msg); err != nil {
|
|
routerErr = err
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Incoming: everything the other parties emit reaches this handler.
|
|
go func() {
|
|
for msg := range router.Receive() {
|
|
handler.Accept(msg)
|
|
}
|
|
}()
|
|
|
|
result, err := handler.WaitForResult()
|
|
if err != nil {
|
|
return zero, fmt.Errorf("mpcvm: ceremony %s: %w", sessionID, err)
|
|
}
|
|
|
|
<-done
|
|
if routerErr != nil {
|
|
return zero, fmt.Errorf("mpcvm: ceremony %s transport: %w", sessionID, routerErr)
|
|
}
|
|
|
|
typed, ok := result.(T)
|
|
if !ok {
|
|
return zero, fmt.Errorf("mpcvm: ceremony %s produced %T, want %T", sessionID, result, zero)
|
|
}
|
|
return typed, nil
|
|
}
|
|
|
|
// ECDSASignature is a threshold ECDSA signature in the one encoding M-Chain
|
|
// stores and every verifier reads: r(32) ‖ s(32) ‖ v(1).
|
|
type ECDSASignature struct {
|
|
R []byte
|
|
S []byte
|
|
V byte
|
|
}
|
|
|
|
// ecdsaSigToWrapper converts a threshold-library ECDSA signature into the wire
|
|
// wrapper as canonical Ethereum r(32)‖s(32)‖v(1): low-S normalised, so it
|
|
// verifies under luxfi/crypto secp256k1.VerifySignature and on-chain ecrecover.
|
|
func ecdsaSigToWrapper(sig *ecdsa.Signature) (*ECDSASignature, error) {
|
|
if sig == nil {
|
|
return nil, fmt.Errorf("mpcvm: nil signature")
|
|
}
|
|
// SigEthereum builds a fixed 65-byte array and checks both component lengths
|
|
// on the way in, so a successful return is 65 bytes by construction. A
|
|
// length check here would restate that and could never fire.
|
|
eth, err := sig.SigEthereum()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mpcvm: encode signature: %w", err)
|
|
}
|
|
return &ECDSASignature{R: eth[0:32], S: eth[32:64], V: eth[64]}, nil
|
|
}
|