mirror of
https://github.com/luxfi/bridge.git
synced 2026-09-24 00:26:46 +00:00
Adds the missing primitive the bridge needs to opt swaps into a PQ-safe
threshold protocol (pulsar / corona / magnetar) instead of always going
through the daemon's classical defaults (cggmp21 / frost).
This PR is data-model only — establishes the vocabulary and the helpers
(IsPostQuantum, CompatibleCurve, ProtocolFor). The wire-plumbing
through mchain.Client.signViaDashboard + signing_driver gating off
bridge.BridgeProfile is the next focused PR; it touches the dashboard
call path consistently across auth.go and client.go and so wants its
own review.
Protocol catalogue mirrors mpcd's dispatch matrix
(luxfi/mpc/pkg/api/handlers_kms.go + luxfi/threshold/protocols/*):
classical:
cggmp21 secp256k1, default ECDSA
frost ed25519 / sr25519, threshold Schnorr/EdDSA
bls BLS12-381 aggregate (classical Beam in Warp 2.0)
doerner 2-of-2 OT-based ECDSA
post-quantum:
pulsar-m-65 Module-LWE NIST L1 (~128-bit classical)
pulsar-m-87 Module-LWE NIST L3 (~192-bit classical)
corona Ring-LWE (rank-1 MLWE collapse — production lane on
primary network, Warp 2.0 Pulse)
magnetar SLH-DSA / FIPS 205 hash-based (per-validator only —
true threshold is research-grade per Bonte-Smart-Tan
2023)
ProtocolDefault ("") is the wire sentinel for "let the daemon infer
from curve" — preserves backward compatibility with every current
caller. The empty-string contract is locked in a regression test.
Tests pass: ./internal/mchain (4.0s) + full repo green.
This is the foundation for the v1.2.0 PQ posture. The bridge's profile
gate (top-level profile.go) already names pulsar-m-65 / pulsar-m-87 /
ml-dsa-65 / ml-dsa-87 as the strict-PQ schemes; the Protocol enum is
the seam between that profile and the mpcd request body.
111 lines
3.0 KiB
Go
111 lines
3.0 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package mchain
|
|
|
|
import "testing"
|
|
|
|
func TestProtocol_String(t *testing.T) {
|
|
cases := []struct {
|
|
p Protocol
|
|
want string
|
|
}{
|
|
{ProtocolDefault, ""},
|
|
{ProtocolCGGMP21, "cggmp21"},
|
|
{ProtocolFROST, "frost"},
|
|
{ProtocolBLS, "bls"},
|
|
{ProtocolDoerner, "doerner"},
|
|
{ProtocolPulsarM65, "pulsar-m-65"},
|
|
{ProtocolPulsarM87, "pulsar-m-87"},
|
|
{ProtocolCorona, "corona"},
|
|
{ProtocolMagnetar, "magnetar"},
|
|
}
|
|
for _, c := range cases {
|
|
if got := string(c.p); got != c.want {
|
|
t.Errorf("Protocol(%q).String() = %q, want %q", c.p, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProtocol_IsPostQuantum(t *testing.T) {
|
|
classical := []Protocol{
|
|
ProtocolDefault,
|
|
ProtocolCGGMP21,
|
|
ProtocolFROST,
|
|
ProtocolBLS,
|
|
ProtocolDoerner,
|
|
}
|
|
pq := []Protocol{
|
|
ProtocolPulsarM65,
|
|
ProtocolPulsarM87,
|
|
ProtocolCorona,
|
|
ProtocolMagnetar,
|
|
}
|
|
for _, p := range classical {
|
|
if p.IsPostQuantum() {
|
|
t.Errorf("Protocol(%q).IsPostQuantum() = true, want false", p)
|
|
}
|
|
}
|
|
for _, p := range pq {
|
|
if !p.IsPostQuantum() {
|
|
t.Errorf("Protocol(%q).IsPostQuantum() = false, want true", p)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProtocol_CompatibleCurve(t *testing.T) {
|
|
// Every Pulsar / Corona / Doerner / CGGMP21 produces secp256k1
|
|
// signatures. Only FROST produces Ed25519.
|
|
secp := []Protocol{
|
|
ProtocolDefault,
|
|
ProtocolCGGMP21,
|
|
ProtocolDoerner,
|
|
ProtocolPulsarM65,
|
|
ProtocolPulsarM87,
|
|
ProtocolCorona,
|
|
// BLS and Magnetar conservatively map to secp256k1 by default —
|
|
// callers using them MUST take the dedicated code path (not
|
|
// through Curve), so this default is just "don't blow up".
|
|
ProtocolBLS,
|
|
ProtocolMagnetar,
|
|
}
|
|
for _, p := range secp {
|
|
if got := p.CompatibleCurve(); got != CurveSecp256k1 {
|
|
t.Errorf("Protocol(%q).CompatibleCurve() = %q, want secp256k1", p, got)
|
|
}
|
|
}
|
|
if got := ProtocolFROST.CompatibleCurve(); got != CurveEd25519 {
|
|
t.Errorf("Protocol(frost).CompatibleCurve() = %q, want ed25519", got)
|
|
}
|
|
}
|
|
|
|
func TestProtocolFor(t *testing.T) {
|
|
cases := []struct {
|
|
c Curve
|
|
want Protocol
|
|
}{
|
|
{CurveSecp256k1, ProtocolCGGMP21},
|
|
{CurveEd25519, ProtocolFROST},
|
|
{Curve("sr25519"), ProtocolDefault}, // unknown → daemon default
|
|
{Curve(""), ProtocolDefault},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := ProtocolFor(tc.c); got != tc.want {
|
|
t.Errorf("ProtocolFor(%q) = %q, want %q", tc.c, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// PQ vs classical wire-shape regression: the empty Protocol string is
|
|
// the documented "send no protocol field" sentinel. If anyone ever
|
|
// renames it to e.g. "default" the daemon would receive a literal
|
|
// "default" string and refuse the request. Lock the wire form.
|
|
func TestProtocol_DefaultIsEmptyWireString(t *testing.T) {
|
|
if string(ProtocolDefault) != "" {
|
|
t.Fatalf("ProtocolDefault must serialise to empty string; got %q. "+
|
|
"Changing this breaks the 'protocol omitted from wire' "+
|
|
"contract — every daemon would receive a literal value and "+
|
|
"likely reject it.", string(ProtocolDefault))
|
|
}
|
|
}
|