mpcd: embed luxfi/threshold/pkg/thresholdd dispatcher (six schemes)

mpcd is the production threshold daemon. With this commit it embeds
the JSON-RPC dispatcher from luxfi/threshold/pkg/thresholdd on a new
--threshold-listen flag (default 127.0.0.1:7300, MPC_THRESHOLD_LISTEN
env var). The dispatcher exposes every supported scheme:

  cggmp21 (secp256k1 ECDSA, CGGMP21 fork)
  frost   (secp256k1 Schnorr, RFC 9591)
  bls     (BLS12-381 Shamir/Lagrange)
  sr25519 (Ristretto255, via mpc node's own session — separate path)
  pulsar  (M-LWE / FIPS 204 ML-DSA-65 threshold, via luxfi/corona/threshold)
  corona  (R-LWE / Ringtail, via luxfi/ringtail/threshold)
  doerner (reserved; upstream protocol non-functional, returns explicit error)

Wire format matches teleport/mpc/src/signers/rpc.ts byte-for-byte:

  POST /  {"jsonrpc":"2.0","id":N,"method":"<scheme>.<op>","params":{...}}

Methods per scheme: keygen / sign / verify. Public-key handles are
returned by keygen and bound on sign/verify.

Decomplecting note: the dispatcher carries zero policy. Profile
gating, auth, audit, and rate-limiting live on mpcd's existing API
surface (and on the cluster ingress in front of it). The threshold
listener is loopback by default; production exposure happens via the
KMS-facing ZAP server or the gateway.

There is no separate thresholdd daemon to deploy. The luxfi/threshold
repo's cmd/thresholdd remains as a developer / CI binary that imports
the same package — one wire, one implementation, two startup paths.

Tests: TestEmbeddedDispatcher_BLSRoundTrip (full keygen → sign →
verify → forgery rejection) and TestEmbeddedDispatcher_UnknownScheme
(-32601 method-not-found contract) both pass. README updated with the
new protocol table; full surface documented in
luxfi/threshold/LLM.md "thresholdd — unified JSON-RPC dispatcher".
This commit is contained in:
2026-05-13 15:56:56 -07:00
parent c6a0e2d1a6
commit 0ea64a63e7
5 changed files with 230 additions and 3 deletions
+15 -3
View File
@@ -1,6 +1,6 @@
# MPC
Threshold signing engine -- CGGMP21 (ECDSA), FROST (EdDSA), BLS, and SR25519. No full private key reconstruction, ever.
Threshold signing engine -- CGGMP21 (ECDSA), FROST (EdDSA/Schnorr), BLS, SR25519, and post-quantum lattice signatures (Pulsar M-LWE, Corona R-LWE, and double-lattice composition) via the embedded `thresholdd` dispatcher. No full private key reconstruction, ever.
```
go get github.com/luxfi/mpc
@@ -12,12 +12,24 @@ go get github.com/luxfi/mpc
### Protocols
| Protocol | Curve | Key Type | Use |
|----------|-------|----------|-----|
| Protocol | Curve / Lattice | Key Type | Use |
|----------|-----------------|----------|-----|
| CGGMP21 | secp256k1 | `secp256k1` | Bitcoin, Ethereum, Lux C-Chain, all EVM L2s, XRPL |
| FROST | Ed25519 | `ed25519` | Solana, TON, Polkadot, Cardano, Substrate chains |
| BLS | BLS12-381 | `bls` | Lux consensus, beacon chain, aggregated signatures |
| SR25519 | Ristretto255 | `sr25519` | Substrate/Polkadot native |
| Pulsar | M-LWE (FIPS 204 / ML-DSA-65 threshold) | `pulsar` | Post-quantum threshold signing — bridge anchors, strict-PQ profile |
| Corona | R-LWE (Ringtail) | `corona` | Post-quantum threshold signing — orthogonal lattice family to Pulsar |
| Double-lattice | Pulsar ⊕ Corona | `double-lattice` | Composed `u32_be(len) ‖ pulsar_sig ‖ corona_sig` — both must verify |
Pulsar, Corona, and double-lattice are exposed via the embedded
threshold dispatcher (`--threshold-listen`, default `127.0.0.1:7300`),
which is `luxfi/threshold/pkg/thresholdd`. The dispatcher is a JSON-RPC
2.0 surface shared with `cggmp21` / `frost` / `bls` — one wire, six
schemes — and is consumed by the teleport bus
(`~/work/lux/teleport/mpc/src/signers/`). The wire format is documented
in `~/work/lux/threshold/CLAUDE.md` under "thresholdd — unified
JSON-RPC daemon".
### Threshold Model
+41
View File
@@ -35,6 +35,8 @@ import (
uimpc "github.com/luxfi/mpc/ui"
"github.com/luxfi/threshold/pkg/thresholdd"
mpcapi "github.com/luxfi/mpc/pkg/api"
"github.com/luxfi/mpc/pkg/backup"
"github.com/luxfi/mpc/pkg/zapauth"
@@ -117,6 +119,12 @@ func main() {
Sources: cli.EnvVars("MPC_KMS_ZAP_LISTEN"),
Value: ":9653",
},
&cli.StringFlag{
Name: "threshold-listen",
Usage: "Embedded threshold JSON-RPC dispatcher listen address (luxfi/threshold/pkg/thresholdd surface: cggmp21/frost/pulsar/corona/bls/doerner). Empty disables. Default 127.0.0.1:7300 — process-local IPC; production fronts this via the cluster ingress + KMS.",
Sources: cli.EnvVars("MPC_THRESHOLD_LISTEN"),
Value: "127.0.0.1:7300",
},
// HSM / password provider flags
&cli.StringFlag{
Name: "hsm-provider",
@@ -771,6 +779,39 @@ func runNodeConsensus(ctx context.Context, c *cli.Command) error {
}
}
// Start embedded threshold JSON-RPC dispatcher
// (luxfi/threshold/pkg/thresholdd). One process, one wire, six
// schemes (cggmp21/frost/pulsar/corona/bls/doerner) — consumed by
// teleport/mpc and any other client that speaks the threshold bus.
//
// The dispatcher carries zero policy on purpose: profile gating,
// auth, and audit live on the API surface above (and on the gateway
// in front of mpcd in production). Default bind is loopback only;
// network exposure happens via the cluster ingress.
if thrAddr := c.String("threshold-listen"); thrAddr != "" {
thrSrv, thrErr := thresholdd.NewServer()
if thrErr != nil {
logger.Error("Failed to build threshold dispatcher", thrErr)
} else {
thrListener, listenErr := net.Listen("tcp", thrAddr)
if listenErr != nil {
logger.Error("Failed to bind threshold dispatcher", listenErr, "addr", thrAddr)
} else {
thrHTTP := &http.Server{
Handler: thrSrv,
ReadHeaderTimeout: 5 * time.Second,
}
go func() {
logger.Info("Threshold dispatcher starting", "addr", thrListener.Addr().String(), "schemes", "cggmp21,frost,pulsar,corona,bls,doerner")
if err := thrHTTP.Serve(thrListener); err != nil && err != http.ErrServerClosed {
logger.Error("Threshold dispatcher failed", err)
}
}()
defer thrHTTP.Close()
}
}
}
// Start Dashboard API server (ZapDB-backed, no external dependencies)
apiListenAddr := c.String("api-listen")
jwtSecret := c.String("jwt-secret")
+172
View File
@@ -0,0 +1,172 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Verifies the embedded threshold dispatcher (luxfi/threshold/pkg/thresholdd)
// is reachable from mpcd's process and answers a full keygen → sign → verify
// round-trip with at least one production scheme (BLS — fast keygen, no
// upstream flakiness).
//
// This is the smallest possible test that proves the consolidation is
// real: mpcd embeds the same package the standalone thresholdd CLI uses,
// the wire shape matches what teleport/mpc/src/signers/rpc.ts speaks,
// and the dispatcher refuses unknown schemes with -32601.
package main
import (
"bytes"
"encoding/hex"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/luxfi/threshold/pkg/thresholdd"
)
// rpcCall posts a JSON-RPC 2.0 request against the embedded dispatcher
// and unmarshals the result envelope. Mirrors the wire used by the
// teleport mpc bus (signers/rpc.ts).
func rpcCall(t *testing.T, url, method string, params any) (json.RawMessage, *struct {
Code int `json:"code"`
Message string `json:"message"`
}) {
t.Helper()
body, err := json.Marshal(map[string]any{
"jsonrpc": "2.0",
"id": 1,
"method": method,
"params": params,
})
if err != nil {
t.Fatalf("marshal: %v", err)
}
resp, err := http.Post(url, "application/json", bytes.NewReader(body))
if err != nil {
t.Fatalf("post: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Fatalf("status %d", resp.StatusCode)
}
var env struct {
Result json.RawMessage `json:"result"`
Error *struct {
Code int `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
if err := json.NewDecoder(resp.Body).Decode(&env); err != nil {
t.Fatalf("decode: %v", err)
}
return env.Result, env.Error
}
// TestEmbeddedDispatcher_BLSRoundTrip verifies the dispatcher mpcd
// embeds answers a real BLS keygen → sign → verify round-trip on the
// same wire teleport's PulsarSigner/CoronaSigner/Cggmp21Signer use.
func TestEmbeddedDispatcher_BLSRoundTrip(t *testing.T) {
srv, err := thresholdd.NewServer()
if err != nil {
t.Fatalf("NewServer: %v", err)
}
ts := httptest.NewServer(srv)
defer ts.Close()
// keygen
res, rpcErr := rpcCall(t, ts.URL, "bls.keygen", map[string]any{
"threshold": 2,
"participants": 3,
})
if rpcErr != nil {
t.Fatalf("bls.keygen error %d: %s", rpcErr.Code, rpcErr.Message)
}
var kg struct {
PublicKey string `json:"publicKey"`
Shares []string `json:"shares"`
}
if err := json.Unmarshal(res, &kg); err != nil {
t.Fatalf("unmarshal keygen: %v", err)
}
if kg.PublicKey == "" {
t.Fatalf("empty publicKey")
}
if len(kg.Shares) != 3 {
t.Fatalf("shares=%d want=3", len(kg.Shares))
}
// sign
msg := hex.EncodeToString([]byte("mpcd embedded dispatch smoke test"))
res, rpcErr = rpcCall(t, ts.URL, "bls.sign", map[string]any{
"messageHex": msg,
"pubKeyHex": kg.PublicKey,
})
if rpcErr != nil {
t.Fatalf("bls.sign error %d: %s", rpcErr.Code, rpcErr.Message)
}
var sg struct {
SignatureHex string `json:"signatureHex"`
}
if err := json.Unmarshal(res, &sg); err != nil {
t.Fatalf("unmarshal sign: %v", err)
}
if sg.SignatureHex == "" {
t.Fatalf("empty signatureHex")
}
// verify
res, rpcErr = rpcCall(t, ts.URL, "bls.verify", map[string]any{
"messageHex": msg,
"signatureHex": sg.SignatureHex,
"pubKeyHex": kg.PublicKey,
})
if rpcErr != nil {
t.Fatalf("bls.verify error %d: %s", rpcErr.Code, rpcErr.Message)
}
var vr struct {
OK bool `json:"ok"`
}
if err := json.Unmarshal(res, &vr); err != nil {
t.Fatalf("unmarshal verify: %v", err)
}
if !vr.OK {
t.Fatalf("verify: round-trip signature failed under embedded dispatcher")
}
// forgery rejection — different message under same signature must fail
wrong := hex.EncodeToString([]byte("forged"))
res, rpcErr = rpcCall(t, ts.URL, "bls.verify", map[string]any{
"messageHex": wrong,
"signatureHex": sg.SignatureHex,
"pubKeyHex": kg.PublicKey,
})
if rpcErr != nil {
t.Fatalf("bls.verify(wrong) error %d: %s", rpcErr.Code, rpcErr.Message)
}
_ = json.Unmarshal(res, &vr)
if vr.OK {
t.Fatalf("verify: forgery accepted (different message)")
}
}
// TestEmbeddedDispatcher_UnknownScheme verifies the dispatcher returns
// a JSON-RPC -32601 method-not-found for an unrecognized scheme — the
// teleport bus relies on this contract to route fall-back signers.
func TestEmbeddedDispatcher_UnknownScheme(t *testing.T) {
srv, err := thresholdd.NewServer()
if err != nil {
t.Fatalf("NewServer: %v", err)
}
ts := httptest.NewServer(srv)
defer ts.Close()
_, rpcErr := rpcCall(t, ts.URL, "bogus.keygen", map[string]any{
"threshold": 1,
"participants": 1,
})
if rpcErr == nil {
t.Fatalf("expected JSON-RPC error for bogus.keygen, got success")
}
if rpcErr.Code != -32601 {
t.Fatalf("expected -32601 method-not-found, got code=%d msg=%q", rpcErr.Code, rpcErr.Message)
}
}
+1
View File
@@ -3,5 +3,6 @@ go 1.26.3
// For local development with HSM: go work use ../hsm
use (
.
../threshold
./e2e
)
+1
View File
@@ -509,6 +509,7 @@ github.com/luxfi/crypto v1.17.37/go.mod h1:vu+pCHdyPQZULzrA+K+lTFYrg8vOtNdu+/5Vk
github.com/luxfi/crypto v1.17.38/go.mod h1:G2t1GQvPsrwnzwyVEj0LQDuX2AWZVI5kEAPyVeicc5o=
github.com/luxfi/crypto v1.17.39/go.mod h1:mChLWmW4CLR1wAN6CeJTveCzUv0DTzGQnYgq01x3W0U=
github.com/luxfi/crypto v1.17.43/go.mod h1:1M6uQ8I/7dzQwf5RdnKtqOWr0+iXRW0D9WfnMV+Cb1s=
github.com/luxfi/crypto/ipa v1.2.4/go.mod h1:43J6f6rcfUMrZt4cQectMOZb6Ps+fAEj8ZTPC3Kk+gE=
github.com/luxfi/czmq/v4 v4.2.2/go.mod h1:WFY9ldUbeU900P8bcIRr7Jim23veOhJL5A5zSpTogdU=
github.com/luxfi/database v1.17.35/go.mod h1:dYxaF30KGJQzuxA3VH7+WGU5/dhwRyzLgzSk9F3wje0=
github.com/luxfi/database v1.17.38/go.mod h1:q6BJNN+47VtkmkGrswQmC0ikERYAdlmZCYhGTXkF4mM=