mirror of
https://github.com/luxfi/bridge.git
synced 2026-09-24 00:26:46 +00:00
Adds BridgeProfile + LuxStrictPQBridgeProfile (Pulsar-M-65 on both sides,
STARK_FRI_SHA3_PQ proofs, ML-DSA-87 admin, ML-DSA-65 multisig pause).
Forbids ECDSA admin, BLS aggregate, KZG commitments, Groth16 wrappers,
and pairing precompiles in strict mode.
post_quantum_end_to_end label exposed via RPC + block-explorer metadata.
Classical-compat bridges remain available under BridgeClassicalCompat
profile but are explicitly labeled non-E2E-PQ.
Integration sites:
- bridge/profile.go is the bridge-package audit gate (mirrors consensus
type, owns the bridge_classical_compat_total counter).
- opnet-watcher/signer.go: SignRaw, SignTransaction, signEthMessage now
consult profile.RefuseClassicalAdmin before any ecrecover / EIP-191 /
secp256k1 sign — strict-PQ refuses, classical-compat increments
the metric.
- cmd/bridge/api.go: exposes bridge_getProfile JSON-RPC, GET
/v1/bridge/profile, and /metrics (Prometheus, including
bridge_classical_compat_total{primitive=...}).
- --profile flag (BRIDGE_PROFILE env) selects strict-pq | classical-compat
on both cmd/bridge and opnet-watcher.
Patch-bump.
286 lines
9.1 KiB
Go
286 lines
9.1 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package bridge
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
// TestBridgeProfileID_String pins the wire-name table.
|
|
func TestBridgeProfileID_String(t *testing.T) {
|
|
cases := []struct {
|
|
id BridgeProfileID
|
|
want string
|
|
}{
|
|
{BridgeProfileIDNone, "none"},
|
|
{BridgeProfileIDLuxStrictPQ, "lux-strict-pq-bridge"},
|
|
{BridgeProfileIDClassicalCompat, "bridge-classical-compat-unsafe"},
|
|
}
|
|
for _, c := range cases {
|
|
if got := c.id.String(); got != c.want {
|
|
t.Errorf("BridgeProfileID(0x%x).String() = %q, want %q", uint32(c.id), got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestLuxStrictPQBridgeProfile_Fields — every field of the canonical
|
|
// strict-PQ profile is pinned.
|
|
func TestLuxStrictPQBridgeProfile_Fields(t *testing.T) {
|
|
p := LuxStrictPQBridgeProfile
|
|
if p.ProfileID != 0x01 {
|
|
t.Errorf("ProfileID = 0x%x, want 0x01", p.ProfileID)
|
|
}
|
|
if p.Name != "LUX_STRICT_PQ_BRIDGE" {
|
|
t.Errorf("Name = %q, want LUX_STRICT_PQ_BRIDGE", p.Name)
|
|
}
|
|
if p.SourceFinalityScheme != SchemePulsarM65 {
|
|
t.Errorf("SourceFinalityScheme = %s, want pulsar-m-65", p.SourceFinalityScheme)
|
|
}
|
|
if p.DestFinalityScheme != SchemePulsarM65 {
|
|
t.Errorf("DestFinalityScheme = %s, want pulsar-m-65", p.DestFinalityScheme)
|
|
}
|
|
if p.ProofPolicyID != PolicySTARKFRISHA3PQ {
|
|
t.Errorf("ProofPolicyID = %s, want stark-fri-sha3-pq", p.ProofPolicyID)
|
|
}
|
|
if p.BridgeAdminScheme != AuthMLDSA87 {
|
|
t.Errorf("BridgeAdminScheme = %s, want ml-dsa-87", p.BridgeAdminScheme)
|
|
}
|
|
if p.BridgePauseScheme != AuthMultisigMLDSA {
|
|
t.Errorf("BridgePauseScheme = %s, want multisig-ml-dsa", p.BridgePauseScheme)
|
|
}
|
|
if !p.PostQuantumEndToEnd {
|
|
t.Errorf("PostQuantumEndToEnd = false, want true")
|
|
}
|
|
}
|
|
|
|
// TestBridgeProfile_LuxStrictPQ_AllForbidBitsTrue — every classical-allow
|
|
// gate is false on the canonical strict-PQ profile.
|
|
func TestBridgeProfile_LuxStrictPQ_AllForbidBitsTrue(t *testing.T) {
|
|
p := LuxStrictPQBridgeProfile
|
|
if p.AllowsClassicalAdmin {
|
|
t.Errorf("AllowsClassicalAdmin = true, want false on strict-PQ")
|
|
}
|
|
if p.AllowsBLSAggregate {
|
|
t.Errorf("AllowsBLSAggregate = true, want false on strict-PQ")
|
|
}
|
|
if p.AllowsKZGCommitment {
|
|
t.Errorf("AllowsKZGCommitment = true, want false on strict-PQ")
|
|
}
|
|
if p.AllowsGroth16Wrap {
|
|
t.Errorf("AllowsGroth16Wrap = true, want false on strict-PQ")
|
|
}
|
|
if p.AllowsPairingPrecompile {
|
|
t.Errorf("AllowsPairingPrecompile = true, want false on strict-PQ")
|
|
}
|
|
}
|
|
|
|
// TestBridgeClassicalCompat_Fields — classical-compat is explicitly
|
|
// labelled non-E2E-PQ and every classical gate is open.
|
|
func TestBridgeClassicalCompat_Fields(t *testing.T) {
|
|
p := BridgeClassicalCompat
|
|
if p.ProfileID != 0x90 {
|
|
t.Errorf("ProfileID = 0x%x, want 0x90", p.ProfileID)
|
|
}
|
|
if p.PostQuantumEndToEnd {
|
|
t.Errorf("PostQuantumEndToEnd = true, want false")
|
|
}
|
|
if !p.AllowsClassicalAdmin {
|
|
t.Errorf("AllowsClassicalAdmin = false, want true")
|
|
}
|
|
if !p.AllowsBLSAggregate {
|
|
t.Errorf("AllowsBLSAggregate = false, want true")
|
|
}
|
|
}
|
|
|
|
// TestBridgeWithdraw_RefusesECDSAUnderStrictPQ — the spec test name:
|
|
// a withdraw handler under strict-PQ MUST refuse ECDSA admin.
|
|
func TestBridgeWithdraw_RefusesECDSAUnderStrictPQ(t *testing.T) {
|
|
p := LuxStrictPQBridgeProfile
|
|
if err := p.RefuseClassicalAdmin(); err == nil {
|
|
t.Fatalf("withdraw under strict-PQ did not refuse ECDSA")
|
|
} else if !errors.Is(err, ErrBridgeProfileForbidden) {
|
|
t.Errorf("err = %v, want ErrBridgeProfileForbidden", err)
|
|
}
|
|
|
|
compat := BridgeClassicalCompat
|
|
if err := compat.RefuseClassicalAdmin(); err != nil {
|
|
t.Fatalf("withdraw under classical-compat refused ECDSA: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestBridgeProfile_RefuseGates_StrictPQRefusesEachPrimitive — each
|
|
// Refuse* gate refuses under strict-PQ; each allows under classical.
|
|
func TestBridgeProfile_RefuseGates_StrictPQRefusesEachPrimitive(t *testing.T) {
|
|
strict := LuxStrictPQBridgeProfile
|
|
compat := BridgeClassicalCompat
|
|
|
|
for name, fn := range map[string]struct {
|
|
strict func() error
|
|
compat func() error
|
|
}{
|
|
"RefuseClassicalAdmin": {strict.RefuseClassicalAdmin, compat.RefuseClassicalAdmin},
|
|
"RefuseBLSAggregate": {strict.RefuseBLSAggregate, compat.RefuseBLSAggregate},
|
|
"RefuseKZGCommitment": {strict.RefuseKZGCommitment, compat.RefuseKZGCommitment},
|
|
"RefuseGroth16Wrap": {strict.RefuseGroth16Wrap, compat.RefuseGroth16Wrap},
|
|
"RefusePairingPrecompile": {strict.RefusePairingPrecompile, compat.RefusePairingPrecompile},
|
|
} {
|
|
if err := fn.strict(); err == nil {
|
|
t.Errorf("strict-PQ %s = nil, want refusal", name)
|
|
} else if !errors.Is(err, ErrBridgeProfileForbidden) {
|
|
t.Errorf("strict-PQ %s err = %v, want ErrBridgeProfileForbidden", name, err)
|
|
}
|
|
|
|
if err := fn.compat(); err != nil {
|
|
t.Errorf("classical-compat %s = %v, want nil", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestBridgeProfile_ClassicalCompatTotal_Increments — every Refuse*
|
|
// traversal under classical-compat increments the matching counter.
|
|
func TestBridgeProfile_ClassicalCompatTotal_Increments(t *testing.T) {
|
|
before := ClassicalCompatTotal()
|
|
|
|
compat := BridgeClassicalCompat
|
|
_ = compat.RefuseClassicalAdmin()
|
|
_ = compat.RefuseBLSAggregate()
|
|
_ = compat.RefuseKZGCommitment()
|
|
_ = compat.RefuseGroth16Wrap()
|
|
_ = compat.RefusePairingPrecompile()
|
|
|
|
after := ClassicalCompatTotal()
|
|
|
|
if after["admin"] != before["admin"]+1 {
|
|
t.Errorf("admin counter = %d, want %d", after["admin"], before["admin"]+1)
|
|
}
|
|
if after["bls_aggregate"] != before["bls_aggregate"]+1 {
|
|
t.Errorf("bls_aggregate counter = %d, want %d", after["bls_aggregate"], before["bls_aggregate"]+1)
|
|
}
|
|
if after["kzg"] != before["kzg"]+1 {
|
|
t.Errorf("kzg counter = %d, want %d", after["kzg"], before["kzg"]+1)
|
|
}
|
|
if after["groth16"] != before["groth16"]+1 {
|
|
t.Errorf("groth16 counter = %d, want %d", after["groth16"], before["groth16"]+1)
|
|
}
|
|
if after["pairing"] != before["pairing"]+1 {
|
|
t.Errorf("pairing counter = %d, want %d", after["pairing"], before["pairing"]+1)
|
|
}
|
|
}
|
|
|
|
// TestBridgeProfile_NilReceiver — gates on a nil profile must refuse,
|
|
// not panic.
|
|
func TestBridgeProfile_NilReceiver(t *testing.T) {
|
|
var p *BridgeProfile
|
|
for name, fn := range map[string]func() error{
|
|
"RefuseClassicalAdmin": p.RefuseClassicalAdmin,
|
|
"RefuseBLSAggregate": p.RefuseBLSAggregate,
|
|
"RefuseKZGCommitment": p.RefuseKZGCommitment,
|
|
"RefuseGroth16Wrap": p.RefuseGroth16Wrap,
|
|
"RefusePairingPrecompile": p.RefusePairingPrecompile,
|
|
} {
|
|
if err := fn(); err == nil {
|
|
t.Errorf("nil-receiver %s = nil, want ErrBridgeProfileNil", name)
|
|
} else if !errors.Is(err, ErrBridgeProfileNil) {
|
|
t.Errorf("nil-receiver %s err = %v, want ErrBridgeProfileNil", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestBridgeProfile_Metadata_JSON pins the JSON contract for the
|
|
// bridge_getProfile RPC. The fields and the snake-case wire names are
|
|
// the public API; auditors filter on these.
|
|
func TestBridgeProfile_Metadata_JSON(t *testing.T) {
|
|
p := LuxStrictPQBridgeProfile
|
|
md := p.Metadata()
|
|
|
|
b, err := json.Marshal(md)
|
|
if err != nil {
|
|
t.Fatalf("Marshal: %v", err)
|
|
}
|
|
|
|
got := string(b)
|
|
wantSubstrings := []string{
|
|
`"profile_id":"0x01"`,
|
|
`"name":"LUX_STRICT_PQ_BRIDGE"`,
|
|
`"post_quantum_end_to_end":true`,
|
|
`"source_finality_scheme":"PULSAR_M_65"`,
|
|
`"dest_finality_scheme":"PULSAR_M_65"`,
|
|
`"proof_policy_id":"stark-fri-sha3-pq"`,
|
|
`"bridge_admin_scheme":"ml-dsa-87"`,
|
|
`"bridge_pause_scheme":"multisig-ml-dsa"`,
|
|
}
|
|
for _, s := range wantSubstrings {
|
|
if !contains(got, s) {
|
|
t.Errorf("Metadata JSON missing %q\nfull: %s", s, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestBridgeClassicalCompat_Metadata_JSON pins the classical-compat
|
|
// label is visible to RPC + explorer consumers.
|
|
func TestBridgeClassicalCompat_Metadata_JSON(t *testing.T) {
|
|
p := BridgeClassicalCompat
|
|
md := p.Metadata()
|
|
|
|
b, err := json.Marshal(md)
|
|
if err != nil {
|
|
t.Fatalf("Marshal: %v", err)
|
|
}
|
|
|
|
got := string(b)
|
|
wantSubstrings := []string{
|
|
`"profile_id":"0x90"`,
|
|
`"name":"BRIDGE_CLASSICAL_COMPAT_UNSAFE"`,
|
|
`"post_quantum_end_to_end":false`,
|
|
`"source_finality_scheme":"BLS_12_381"`,
|
|
`"dest_finality_scheme":"PULSAR_M_65"`,
|
|
}
|
|
for _, s := range wantSubstrings {
|
|
if !contains(got, s) {
|
|
t.Errorf("Metadata JSON missing %q\nfull: %s", s, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestBridgeProfile_SchemeWireName covers the upper-snake mapping.
|
|
func TestBridgeProfile_SchemeWireName(t *testing.T) {
|
|
cases := []struct {
|
|
in, want string
|
|
}{
|
|
{SchemePulsarM65, "PULSAR_M_65"},
|
|
{SchemePulsarM87, "PULSAR_M_87"},
|
|
{SchemeMLDSA65, "ML_DSA_65"},
|
|
{SchemeMLDSA87, "ML_DSA_87"},
|
|
{SchemeBLS12381, "BLS_12_381"},
|
|
{"unknown-scheme", "unknown-scheme"},
|
|
}
|
|
for _, c := range cases {
|
|
if got := schemeWireName(c.in); got != c.want {
|
|
t.Errorf("schemeWireName(%q) = %q, want %q", c.in, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestBridgeProfile_IsPostQuantumEndToEnd_Accessor pins the public
|
|
// accessor.
|
|
func TestBridgeProfile_IsPostQuantumEndToEnd_Accessor(t *testing.T) {
|
|
if !LuxStrictPQBridgeProfile.IsPostQuantumEndToEnd() {
|
|
t.Errorf("strict-PQ IsPostQuantumEndToEnd() = false, want true")
|
|
}
|
|
if BridgeClassicalCompat.IsPostQuantumEndToEnd() {
|
|
t.Errorf("classical-compat IsPostQuantumEndToEnd() = true, want false")
|
|
}
|
|
}
|
|
|
|
func contains(s, substr string) bool {
|
|
for i := 0; i+len(substr) <= len(s); i++ {
|
|
if s[i:i+len(substr)] == substr {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|