mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
The compact group-key era was named for a place (Pulsar) when it is a generic value serving every threshold lane, scheme-qualified. Drop the Pulsar prefix and generalise the verifier so ONE type + ONE entry point covers both the Pulsar (FIPS-204 ML-DSA) and Corona (Ring-LWE Ringtail) lanes. Pure decomplect: same behaviour, value-not-place naming. - Type: PulsarKeyEra -> KeyEra (file pulsar_key_era.go -> key_era.go) - Field: MLDSAPubKey []byte -> GroupPubKey []byte (scheme-interpreted via SchemeID; not ML-DSA-specific). The unrelated Validator.MLDSAPubKey (*mldsa.PublicKey, a per-validator identity key) is left untouched. - Registry: PulsarKeyEraRegistry -> KeyEraRegistry (lane-agnostic; holds Pulsar and Corona eras alike), NewPulsarKeyEraRegistry -> NewKeyEraRegistry. Generalised verifier dispatch: - VerifyThresholdLeg(ev, M, era) is the ONE compact-threshold verify entry point. It resolves the era's lane through the suite registry (era.eraSuite, Pulsar|Corona only) and routes the group-signature check to that lane's boring verifier: pulsarwire.VerifyBytes for Pulsar, coronaThreshold.Verify for Corona (via new verifyCoronaGroupKeyLeg, which decodes the era's wire-byte group key and reuses verifyCoronaLeg). Both the era scheme and the evidence suite are resolved through the registry and must name the SAME lane + param set, so no suite string can reach the wrong verifier (reuses LookupSuite/resolveSuiteForLane lane safety). - VerifyPulsar is retained as a thin Pulsar-suite wrapper that refuses any non-Pulsar era at the door, then defers to VerifyThresholdLeg. Tests: existing boring-verifier + suite-dispatch-safety (VerifyPulsar) and dualpq tests hold unchanged; KeyEra registry test now proves lane-agnostic acceptance (corona era registers, non-threshold Beam refused); new TestCompact_VerifyThresholdLeg_CoronaLane proves a corona-suite era verifies via the corona path through the same KeyEra/dispatch with a real multi-node Ring-LWE signature, and that a pulsar suite can never reach the corona verifier. GOWORK=off go build ./... green; go test ./protocol/quasar/ green.
701 lines
30 KiB
Go
701 lines
30 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// compact_cert_test.go — the COMPACT-CERTIFICATE evidence layer proof corpus.
|
|
//
|
|
// What this file proves, with REAL crypto (no fakes):
|
|
//
|
|
// 1. A STRICT_QUASAR cert (Beam ∧ Pulsar ∧ Corona) over ONE canonical M
|
|
// verifies — real BLS aggregate, real FIPS-204 ML-DSA, real multi-node
|
|
// Ring-LWE threshold.
|
|
// 2. Each named posture enforces its required legs / permitted modes:
|
|
// BLS_FAST = Beam only; HYBRID accepts Pulsar OR P3Q; RECOVERY accepts P3Q
|
|
// and REFUSES the Pulsar threshold mode; STRICT refuses the P3Q mode.
|
|
// 3. COMPACTNESS: the Beam/Pulsar/Corona legs are O(1) in committee size — a
|
|
// cert for 1000 validators is the SAME size as for 4, and orders of
|
|
// magnitude smaller than the O(N) naive "1000 raw ML-DSA certs" object.
|
|
// 4. Dispatch safety + boring VerifyPulsar: wrong-era, suite-mismatch,
|
|
// cross-lane suite, unknown suite and a corrupt signature all reject with
|
|
// distinct typed errors; no suite string reaches the wrong verifier.
|
|
// 5. The P3Q fallback path verifies a rollup root (Direct) over a REAL
|
|
// validator-set-bound MLDSACertSet (a WeightedQuorumCert), and rejects a
|
|
// tampered root, a corrupt cert set, a sub-threshold floor, an un-opted-in
|
|
// classical proof, and an unaudited succinct backend.
|
|
package quasar
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/luxfi/crypto/bls"
|
|
)
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Shared compact-cert builders.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
const compactThreshold = uint64(100)
|
|
|
|
// compactHeader builds a fixed cert header for a posture with the given signer
|
|
// root, and returns the header + validator-set root + the canonical message M
|
|
// every compact leg signs.
|
|
func compactHeader(policy *QuasarEvidencePolicy, signerRoot [32]byte) (*ConsensusCert, [48]byte, []byte) {
|
|
var vsetRoot [48]byte
|
|
for i := range vsetRoot {
|
|
vsetRoot[i] = 0x33
|
|
}
|
|
var bh [32]byte
|
|
for i := range bh {
|
|
bh[i] = 0xC0
|
|
}
|
|
cert := &ConsensusCert{
|
|
Version: consensusCertVersion,
|
|
Profile: 1,
|
|
ChainID: 9,
|
|
Epoch: 9,
|
|
Height: 9,
|
|
Round: 9,
|
|
BlockHash: bh,
|
|
ValidatorSetRoot: vsetRoot,
|
|
PolicyID: policy.EvidencePolicyID(),
|
|
RequiredLegsRoot: HashRequiredLegs(policy.RequiredLegs()),
|
|
SignerRoot: signerRoot,
|
|
KeyEraID: 7,
|
|
}
|
|
return cert, vsetRoot, consensusCertMessage(cert, cert.RequiredLegsRoot)
|
|
}
|
|
|
|
// fixedSignerRoot is the threshold-leg signer root (the threshold accountability
|
|
// echoes it; it is bound into M).
|
|
func fixedSignerRoot() [32]byte {
|
|
var s [32]byte
|
|
for i := range s {
|
|
s[i] = 0x5A
|
|
}
|
|
return s
|
|
}
|
|
|
|
// beamEvidence builds a Beam (BLS aggregate) LegEvidence.
|
|
func beamEvidence(aggSig []byte) LegEvidence {
|
|
return LegEvidence{
|
|
Leg: LegSpec{Kind: LegClassical, ParamSetID: blsParam},
|
|
Mode: EvidenceClassicalAggregate,
|
|
Payload: encodeClassicalAggregatePayload(&ClassicalAggregatePayload{
|
|
Scheme: ClassicalSchemeBLS12381, Payload: aggSig,
|
|
}),
|
|
}
|
|
}
|
|
|
|
// pulsarThresholdEvidence builds a compact Pulsar (FIPS-204 ML-DSA threshold)
|
|
// LegEvidence with accountability bound to signerRoot.
|
|
func pulsarThresholdEvidence(pulsarSig []byte, signerRoot [32]byte) LegEvidence {
|
|
return LegEvidence{
|
|
Leg: LegSpec{Kind: LegPulsarMLDSA, ParamSetID: pqParam},
|
|
Mode: EvidenceThresholdSig,
|
|
Payload: encodeThresholdSigPayload(&ThresholdSigPayload{
|
|
Signature: pulsarSig,
|
|
Accountability: &ThresholdAccountability{SignerRoot: signerRoot, AggregateWeight: 150},
|
|
}),
|
|
}
|
|
}
|
|
|
|
// coronaThresholdEvidence builds a compact Corona (Ring-LWE threshold)
|
|
// LegEvidence.
|
|
func coronaThresholdEvidence(coronaSigBytes []byte, signerRoot [32]byte) LegEvidence {
|
|
return LegEvidence{
|
|
Leg: LegSpec{Kind: LegCoronaLattice, ParamSetID: pqParam},
|
|
Mode: EvidenceThresholdSig,
|
|
Payload: encodeThresholdSigPayload(&ThresholdSigPayload{
|
|
Signature: coronaSigBytes,
|
|
Accountability: &ThresholdAccountability{SignerRoot: signerRoot, AggregateWeight: 150},
|
|
}),
|
|
}
|
|
}
|
|
|
|
// aggN builds a real BLS aggregate over n signers of msg, returning the
|
|
// aggregate pubkey (compressed) and aggregate signature bytes. Aggregate SIZE
|
|
// is independent of n — the basis of the Beam leg's O(1) compactness.
|
|
func aggN(t *testing.T, msg []byte, n int) (aggPub, aggSig []byte) {
|
|
t.Helper()
|
|
pubs := make([]*bls.PublicKey, 0, n)
|
|
sigs := make([]*bls.Signature, 0, n)
|
|
for i := 0; i < n; i++ {
|
|
sk, err := bls.NewSecretKey()
|
|
if err != nil {
|
|
t.Fatalf("bls keygen: %v", err)
|
|
}
|
|
sig, err := sk.Sign(msg)
|
|
if err != nil {
|
|
t.Fatalf("bls sign: %v", err)
|
|
}
|
|
pubs = append(pubs, sk.PublicKey())
|
|
sigs = append(sigs, sig)
|
|
}
|
|
apk, err := bls.AggregatePublicKeys(pubs)
|
|
if err != nil {
|
|
t.Fatalf("aggregate pubkeys: %v", err)
|
|
}
|
|
asig, err := bls.AggregateSignatures(sigs)
|
|
if err != nil {
|
|
t.Fatalf("aggregate sigs: %v", err)
|
|
}
|
|
return bls.PublicKeyToCompressedBytes(apk), bls.SignatureToBytes(asig)
|
|
}
|
|
|
|
// p3qSuiteDirect is the production P3Q ML-DSA-65 Direct suite id.
|
|
const p3qSuiteDirect = "Lux-P3Q-MLDSA65-Direct-v1"
|
|
|
|
// p3qDirectCert builds a {Beam, P3Q-Direct} cert for the given posture. The P3Q
|
|
// leg wraps a REAL validator-set-bound WeightedQuorumCert (fourMLDSA, Σweight
|
|
// 100, threshold 100) — the MLDSACertSet — in a rollup-root commitment. Returns
|
|
// the store, validators (carrying the weighted config/envelope the inner verify
|
|
// needs), the cert, M, and the inner WeightedQuorumCert bytes.
|
|
func p3qDirectCert(t *testing.T, policy *QuasarEvidencePolicy) (*envStore, *envValidators, *ConsensusCert, []byte, []byte) {
|
|
t.Helper()
|
|
sc := fourMLDSA(t)
|
|
innerWire, err := sc.cert.MarshalBinary()
|
|
if err != nil {
|
|
t.Fatalf("marshal inner weighted cert: %v", err)
|
|
}
|
|
cert := &ConsensusCert{
|
|
Version: consensusCertVersion,
|
|
Profile: 1,
|
|
ChainID: sc.cert.ChainID,
|
|
Epoch: sc.cert.Epoch,
|
|
Height: sc.cert.Height,
|
|
Round: sc.cert.Round,
|
|
BlockHash: sc.cert.ValueHash,
|
|
ValidatorSetRoot: sc.cert.ValidatorSetRoot,
|
|
PolicyID: policy.EvidencePolicyID(),
|
|
RequiredLegsRoot: HashRequiredLegs(policy.RequiredLegs()),
|
|
SignerRoot: fixedSignerRoot(),
|
|
KeyEraID: 7,
|
|
AggregateWeight: sc.cert.AggregateWeight,
|
|
}
|
|
msg := consensusCertMessage(cert, cert.RequiredLegsRoot)
|
|
aggPub, aggSig := aggN(t, msg, 3)
|
|
|
|
p3q := LegEvidence{
|
|
Leg: LegSpec{Kind: LegPulsarMLDSA, ParamSetID: pqParam},
|
|
Mode: EvidenceP3QRollup,
|
|
Payload: encodeP3QRollupPayload(&P3QRollupPayload{
|
|
SuiteID: p3qSuiteDirect,
|
|
RollupRoot: P3QRollupRoot(p3qSuiteDirect, innerWire),
|
|
CertSet: innerWire,
|
|
}),
|
|
}
|
|
cert.Evidence = []LegEvidence{beamEvidence(aggSig), p3q}
|
|
|
|
store := &envStore{policy: policy}
|
|
validators := &envValidators{
|
|
root: sc.cert.ValidatorSetRoot, epoch: sc.env.Epoch, cfg: sc.cfg, env: sc.env,
|
|
classKeys: map[ClassicalScheme][]byte{ClassicalSchemeBLS12381: aggPub},
|
|
}
|
|
return store, validators, cert, msg, innerWire
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 1. STRICT_QUASAR — Beam ∧ Pulsar ∧ Corona over one M.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
func TestCompact_StrictQuasar_AllThreeLegsVerify(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping STRICT_QUASAR Corona-DKG integration under -short")
|
|
}
|
|
policy := NewQuasarEvidencePolicy(PolicyStrictQuasar, pqParam, compactThreshold)
|
|
store := &envStore{policy: policy}
|
|
cert, vsetRoot, msg := compactHeader(policy, fixedSignerRoot())
|
|
|
|
aggPub, aggSig := aggN(t, msg, 4)
|
|
pulsarSig, pulsarGK := signPulsarLegFIPS204(t, msg)
|
|
coronaSig, coronaGK, _ := signCoronaLegMultiNode(t, dualPQThreshold, dualPQThreshold, dualPQN, msg)
|
|
|
|
cert.Evidence = []LegEvidence{
|
|
beamEvidence(aggSig),
|
|
pulsarThresholdEvidence(pulsarSig, cert.SignerRoot),
|
|
coronaThresholdEvidence(EncodeCoronaSig(coronaSig), cert.SignerRoot),
|
|
}
|
|
validators := &envValidators{
|
|
root: vsetRoot, epoch: 9,
|
|
classKeys: map[ClassicalScheme][]byte{ClassicalSchemeBLS12381: aggPub},
|
|
pulsarKey: pulsarGK, coronaKey: coronaGK,
|
|
}
|
|
if err := VerifyConsensusCert(store, validators, cert); err != nil {
|
|
t.Fatalf("STRICT_QUASAR (Beam ∧ Pulsar ∧ Corona) cert rejected: %v", err)
|
|
}
|
|
|
|
// Missing Corona ⇒ rejected (AND-mode).
|
|
noCorona := *cert
|
|
noCorona.Evidence = cert.Evidence[:2]
|
|
if err := VerifyConsensusCert(store, validators, &noCorona); err == nil {
|
|
t.Fatal("STRICT_QUASAR accepted a cert missing the Corona leg")
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 2. Policy-mode required-legs enforcement.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
func TestCompact_PolicyModes_RequiredLegsEnforced(t *testing.T) {
|
|
// --- BLS_FAST: Beam-only verifies; nothing else required. ---
|
|
t.Run("BLS_FAST_BeamOnly", func(t *testing.T) {
|
|
policy := NewQuasarEvidencePolicy(PolicyBLSFast, pqParam, compactThreshold)
|
|
store := &envStore{policy: policy}
|
|
cert, vsetRoot, msg := compactHeader(policy, fixedSignerRoot())
|
|
aggPub, aggSig := aggN(t, msg, 3)
|
|
cert.Evidence = []LegEvidence{beamEvidence(aggSig)}
|
|
validators := &envValidators{root: vsetRoot, epoch: 9, classKeys: map[ClassicalScheme][]byte{ClassicalSchemeBLS12381: aggPub}}
|
|
if err := VerifyConsensusCert(store, validators, cert); err != nil {
|
|
t.Fatalf("BLS_FAST Beam-only rejected: %v", err)
|
|
}
|
|
})
|
|
|
|
// --- HYBRID accepts the Pulsar threshold mode. ---
|
|
t.Run("HYBRID_AcceptsPulsar", func(t *testing.T) {
|
|
policy := NewQuasarEvidencePolicy(PolicyHybridPQCheckpoint, pqParam, compactThreshold)
|
|
store := &envStore{policy: policy}
|
|
cert, vsetRoot, msg := compactHeader(policy, fixedSignerRoot())
|
|
aggPub, aggSig := aggN(t, msg, 3)
|
|
pulsarSig, pulsarGK := signPulsarLegFIPS204(t, msg)
|
|
cert.Evidence = []LegEvidence{beamEvidence(aggSig), pulsarThresholdEvidence(pulsarSig, cert.SignerRoot)}
|
|
validators := &envValidators{root: vsetRoot, epoch: 9, classKeys: map[ClassicalScheme][]byte{ClassicalSchemeBLS12381: aggPub}, pulsarKey: pulsarGK}
|
|
if err := VerifyConsensusCert(store, validators, cert); err != nil {
|
|
t.Fatalf("HYBRID Beam+Pulsar rejected: %v", err)
|
|
}
|
|
})
|
|
|
|
// --- HYBRID accepts the P3Q rollup mode for the SAME PQ leg (the OR). ---
|
|
t.Run("HYBRID_AcceptsP3Q", func(t *testing.T) {
|
|
policy := NewQuasarEvidencePolicy(PolicyHybridPQCheckpoint, pqParam, compactThreshold)
|
|
store, validators, cert, _, _ := p3qDirectCert(t, policy)
|
|
if err := VerifyConsensusCert(store, validators, cert); err != nil {
|
|
t.Fatalf("HYBRID Beam+P3Q rejected: %v", err)
|
|
}
|
|
})
|
|
|
|
// --- RECOVERY accepts P3Q but REFUSES the Pulsar threshold mode. ---
|
|
t.Run("RECOVERY_RefusesPulsarMode", func(t *testing.T) {
|
|
policy := NewQuasarEvidencePolicy(PolicyRecoveryMode, pqParam, compactThreshold)
|
|
store := &envStore{policy: policy}
|
|
cert, vsetRoot, msg := compactHeader(policy, fixedSignerRoot())
|
|
aggPub, aggSig := aggN(t, msg, 3)
|
|
pulsarSig, pulsarGK := signPulsarLegFIPS204(t, msg)
|
|
// Present a Pulsar threshold sig where the posture allows ONLY P3Q.
|
|
cert.Evidence = []LegEvidence{beamEvidence(aggSig), pulsarThresholdEvidence(pulsarSig, cert.SignerRoot)}
|
|
validators := &envValidators{root: vsetRoot, epoch: 9, classKeys: map[ClassicalScheme][]byte{ClassicalSchemeBLS12381: aggPub}, pulsarKey: pulsarGK}
|
|
if err := VerifyConsensusCert(store, validators, cert); !errors.Is(err, ErrDisallowedEvidenceMode) {
|
|
t.Fatalf("RECOVERY accepted a Pulsar threshold leg: err = %v, want ErrDisallowedEvidenceMode", err)
|
|
}
|
|
})
|
|
|
|
// --- STRICT REFUSES the P3Q mode for the PQ leg (wants the compact sig). ---
|
|
t.Run("STRICT_RefusesP3QMode", func(t *testing.T) {
|
|
policy := NewQuasarEvidencePolicy(PolicyStrictQuasar, pqParam, compactThreshold)
|
|
store, validators, cert, _, _ := p3qDirectCert(t, policy)
|
|
// p3qDirectCert gives {Beam, P3Q}; STRICT forbids the P3Q mode AND
|
|
// additionally requires Corona — either way it must reject.
|
|
if err := VerifyConsensusCert(store, validators, cert); err == nil {
|
|
t.Fatal("STRICT accepted a P3Q PQ leg")
|
|
}
|
|
})
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 3. COMPACTNESS — O(1) in committee size, not O(N).
|
|
// ----------------------------------------------------------------------------
|
|
|
|
func TestCompact_O1NotON_For1000Validators(t *testing.T) {
|
|
msg := []byte("compact-size canonical finality message for the O(1) proof")
|
|
|
|
// Beam aggregate size is INVARIANT under committee growth: aggregating 4 vs
|
|
// 64 signers yields the SAME aggregate-signature byte length. This is the
|
|
// structural basis of the Beam leg's O(1) compactness.
|
|
_, agg4 := aggN(t, msg, 4)
|
|
_, agg64 := aggN(t, msg, 64)
|
|
if len(agg4) != len(agg64) {
|
|
t.Fatalf("Beam aggregate not O(1): |agg(4)|=%d |agg(64)|=%d", len(agg4), len(agg64))
|
|
}
|
|
|
|
// One compact Pulsar (ML-DSA-65 threshold) signature — the on-chain object
|
|
// for the WHOLE committee, regardless of size.
|
|
pulsarSig, _ := signPulsarLegFIPS204(t, msg)
|
|
oneSig := len(pulsarSig)
|
|
if oneSig == 0 {
|
|
t.Fatal("empty pulsar signature")
|
|
}
|
|
|
|
// The compact checkpoint footprint (Beam + Pulsar evidence payloads) does
|
|
// NOT reference committee size — build it once; it is identical "for" any N.
|
|
beam := beamEvidence(agg4)
|
|
pulsar := pulsarThresholdEvidence(pulsarSig, fixedSignerRoot())
|
|
compact := len(beam.Payload) + len(pulsar.Payload)
|
|
|
|
// The NAIVE anti-pattern (what we MUST NOT store): 1000 raw per-validator
|
|
// ML-DSA certs. Grows linearly in committee size.
|
|
const committee = 1000
|
|
naive := committee * oneSig
|
|
|
|
if naive/compact < 100 {
|
|
t.Fatalf("compact cert is not >=100x smaller than the naive O(N) object: compact=%d naive=%d ratio=%d",
|
|
compact, naive, naive/compact)
|
|
}
|
|
t.Logf("compact checkpoint footprint (Beam+Pulsar) = %d bytes (O(1) in committee size)", compact)
|
|
t.Logf("naive 1000-raw-ML-DSA-cert object = %d bytes (O(N))", naive)
|
|
t.Logf("compression ratio at N=1000 = %dx", naive/compact)
|
|
|
|
// Explicit O(1): the compact footprint computed "for" N=4 and "for" N=1000
|
|
// is byte-identical, because no compact leg carries per-validator data.
|
|
footprintFor := func(_ int) int { return len(beam.Payload) + len(pulsar.Payload) }
|
|
if footprintFor(4) != footprintFor(committee) {
|
|
t.Fatal("compact footprint depends on committee size — not O(1)")
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 4. The deliberately boring VerifyPulsar + dispatch safety.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
func TestCompact_VerifyPulsar_BoringAndDispatchSafe(t *testing.T) {
|
|
var signerSet [48]byte
|
|
for i := range signerSet {
|
|
signerSet[i] = 0x21
|
|
}
|
|
M := QuasarFinalityMessage(QuasarFinalityParams{
|
|
ChainID: 9, Epoch: 9, Height: 9, Round: 9, KeyEraID: 7, EvidencePolicyID: 1,
|
|
SignerSetID: signerSet, Profile: 1,
|
|
})
|
|
pulsarSig, pulsarGK := signPulsarLegFIPS204(t, M)
|
|
|
|
era := KeyEra{
|
|
ChainID: 9, SignerSetID: signerSet, KeyEraID: 7, Generation: 1,
|
|
PChainHeight: 1000, GroupPubKey: pulsarGK, Threshold: 100,
|
|
SchemeID: "Lux-Pulsar-TALUS-MLDSA65", KeygenMode: "talus-mpc",
|
|
}
|
|
good := PulsarEvidence{SuiteID: "Lux-Pulsar-TALUS-MLDSA65", KeyEraID: 7, Generation: 1, SignerSetID: signerSet, Signature: pulsarSig}
|
|
|
|
if err := VerifyPulsar(good, M, era); err != nil {
|
|
t.Fatalf("boring VerifyPulsar rejected a valid era signature: %v", err)
|
|
}
|
|
|
|
// reject is a one-line negative assertion: mutate a fresh copy of `good`,
|
|
// expect a specific typed error.
|
|
reject := func(name string, mutate func(*PulsarEvidence), want error) {
|
|
t.Helper()
|
|
bad := good
|
|
mutate(&bad)
|
|
if err := VerifyPulsar(bad, M, era); !errors.Is(err, want) {
|
|
t.Fatalf("%s: err = %v, want %v", name, err, want)
|
|
}
|
|
}
|
|
|
|
reject("wrong KeyEraID", func(e *PulsarEvidence) { e.KeyEraID = 8 }, ErrWrongEra)
|
|
reject("wrong Generation", func(e *PulsarEvidence) { e.Generation = 2 }, ErrWrongEra)
|
|
reject("wrong SignerSetID", func(e *PulsarEvidence) { e.SignerSetID[0] ^= 0xFF }, ErrWrongEra)
|
|
// suite mismatch: a valid Pulsar suite, but not the era's scheme.
|
|
reject("suite != era scheme", func(e *PulsarEvidence) { e.SuiteID = "Lux-Pulsar-TALUS-MLDSA87" }, ErrSuiteMismatch)
|
|
// DISPATCH SAFETY: a Corona suite id can NEVER reach the Pulsar verifier.
|
|
reject("corona suite at pulsar verifier", func(e *PulsarEvidence) { e.SuiteID = "Lux-Corona-Ringtail-L3-v1" }, ErrSuiteLaneMismatch)
|
|
reject("unknown suite", func(e *PulsarEvidence) { e.SuiteID = "Lux-Nonsense-vX" }, ErrUnknownSuite)
|
|
reject("corrupt signature", func(e *PulsarEvidence) {
|
|
e.Signature = append([]byte(nil), pulsarSig...)
|
|
e.Signature[len(e.Signature)/2] ^= 0xFF
|
|
}, ErrBadSignature)
|
|
|
|
// wrong M (replay to a different finality position) — the sig no longer verifies.
|
|
otherM := QuasarFinalityMessage(QuasarFinalityParams{ChainID: 9, Epoch: 9, Height: 10, Round: 9, KeyEraID: 7, SignerSetID: signerSet, Profile: 1})
|
|
if !errors.Is(VerifyPulsar(good, otherM, era), ErrBadSignature) {
|
|
t.Fatal("a signature for one M verified under a different M (replay)")
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 4a. ONE KeyEra + ONE VerifyThresholdLeg entry point serves the Corona lane.
|
|
// A corona-suite era verifies through the SAME type + dispatch as Pulsar —
|
|
// value-not-place: the lane is selected by the era's scheme, not its type.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
func TestCompact_VerifyThresholdLeg_CoronaLane(t *testing.T) {
|
|
var signerSet [48]byte
|
|
for i := range signerSet {
|
|
signerSet[i] = 0x33
|
|
}
|
|
M := QuasarFinalityMessage(QuasarFinalityParams{
|
|
ChainID: 11, Epoch: 4, Height: 4, Round: 4, KeyEraID: 2, EvidencePolicyID: 1,
|
|
SignerSetID: signerSet, Profile: 1,
|
|
})
|
|
|
|
// A REAL multi-node Corona (Ring-LWE, t-of-n) threshold signature + the
|
|
// matching group key — the dual-PQ corona helper, same as the dualpq test.
|
|
coronaSig, coronaGK, _ := signCoronaLegMultiNode(t, dualPQThreshold, dualPQThreshold, dualPQN, M)
|
|
gkBytes, err := coronaGK.MarshalBinary()
|
|
if err != nil {
|
|
t.Fatalf("corona group key MarshalBinary: %v", err)
|
|
}
|
|
|
|
const coronaSuite = "Lux-Corona-Ringtail-L3-v1"
|
|
era := KeyEra{
|
|
ChainID: 11, SignerSetID: signerSet, KeyEraID: 2, Generation: 0,
|
|
PChainHeight: 500, GroupPubKey: gkBytes, Threshold: 100,
|
|
SchemeID: coronaSuite, KeygenMode: "talus-mpc",
|
|
}
|
|
good := PulsarEvidence{
|
|
SuiteID: coronaSuite, KeyEraID: 2, Generation: 0,
|
|
SignerSetID: signerSet, Signature: EncodeCoronaSig(coronaSig),
|
|
}
|
|
|
|
// THE PROOF: the SAME KeyEra type and the SAME VerifyThresholdLeg entry
|
|
// point route a Corona-suite era through the Corona (Ring-LWE) verifier —
|
|
// no CoronaKeyEra type, no second verify entry point.
|
|
if err := VerifyThresholdLeg(good, M, era); err != nil {
|
|
t.Fatalf("corona-lane era rejected via the unified dispatch: %v", err)
|
|
}
|
|
|
|
// The Pulsar wrapper must REFUSE a Corona era at the door (lane gate): a
|
|
// Corona scheme can never reach the Pulsar verify path.
|
|
if err := VerifyPulsar(good, M, era); !errors.Is(err, ErrSuiteLaneMismatch) {
|
|
t.Fatalf("VerifyPulsar accepted a corona era: err = %v, want ErrSuiteLaneMismatch", err)
|
|
}
|
|
|
|
// Dispatch safety + boring rejects, each a distinct typed error.
|
|
reject := func(name string, mutate func(*PulsarEvidence), want error) {
|
|
t.Helper()
|
|
bad := good
|
|
mutate(&bad)
|
|
if err := VerifyThresholdLeg(bad, M, era); !errors.Is(err, want) {
|
|
t.Fatalf("%s: err = %v, want %v", name, err, want)
|
|
}
|
|
}
|
|
// A Pulsar suite id can NEVER reach the Corona verifier.
|
|
reject("pulsar suite at corona era", func(e *PulsarEvidence) { e.SuiteID = "Lux-Pulsar-TALUS-MLDSA65" }, ErrSuiteLaneMismatch)
|
|
// A different (valid) Corona suite that is not the era's scheme.
|
|
reject("suite != era scheme", func(e *PulsarEvidence) { e.SuiteID = "Lux-Corona-Ringtail-L5-v1" }, ErrSuiteMismatch)
|
|
reject("unknown suite", func(e *PulsarEvidence) { e.SuiteID = "Lux-Nonsense-vX" }, ErrUnknownSuite)
|
|
reject("wrong KeyEraID", func(e *PulsarEvidence) { e.KeyEraID = 9 }, ErrWrongEra)
|
|
reject("wrong SignerSetID", func(e *PulsarEvidence) { e.SignerSetID[0] ^= 0xFF }, ErrWrongEra)
|
|
reject("corrupt corona signature", func(e *PulsarEvidence) {
|
|
e.Signature = append([]byte(nil), good.Signature...)
|
|
e.Signature[len(e.Signature)/2] ^= 0xFF
|
|
}, ErrBadSignature)
|
|
|
|
// Wrong M (replay to a different finality position) — the corona signature
|
|
// no longer verifies under the era group key.
|
|
otherM := QuasarFinalityMessage(QuasarFinalityParams{ChainID: 11, Epoch: 4, Height: 5, Round: 4, KeyEraID: 2, SignerSetID: signerSet, Profile: 1})
|
|
if !errors.Is(VerifyThresholdLeg(good, otherM, era), ErrBadSignature) {
|
|
t.Fatal("a corona signature for one M verified under a different M (replay)")
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 4b. Suite registry — exhaustive dispatch-safety invariant.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
func TestCompact_SuiteRegistry_DispatchSafety(t *testing.T) {
|
|
// Every registered suite resolves to EXACTLY its lane's (LegKind, Mode).
|
|
for _, s := range productionSuites {
|
|
got, ok := LookupSuite(s.ID)
|
|
if !ok {
|
|
t.Fatalf("suite %q not resolvable", s.ID)
|
|
}
|
|
lane, ok := laneByKind[got.Kind]
|
|
if !ok {
|
|
t.Fatalf("suite %q names unknown kind %q", s.ID, got.Kind)
|
|
}
|
|
if got.Leg != lane.leg || got.Mode != lane.mode {
|
|
t.Fatalf("suite %q maps to (%s,%s), lane is (%s,%s)", s.ID, got.Leg, got.Mode, lane.leg, lane.mode)
|
|
}
|
|
}
|
|
|
|
// Cross-lane resolution is refused for every (suite, foreign-lane) pair.
|
|
lanes := []EvidenceKind{EvidenceBeamBLS, EvidencePulsarThresholdMLDSA, EvidenceCoronaRingtail, EvidenceP3QMLDSARollup}
|
|
for _, s := range productionSuites {
|
|
for _, want := range lanes {
|
|
_, err := resolveSuiteForLane(s.ID, want, 0)
|
|
if want == s.Kind {
|
|
if err != nil {
|
|
t.Fatalf("suite %q refused for its OWN lane %q: %v", s.ID, want, err)
|
|
}
|
|
} else if !errors.Is(err, ErrSuiteLaneMismatch) {
|
|
t.Fatalf("suite %q (lane %q) NOT refused for foreign lane %q: %v", s.ID, s.Kind, want, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unknown suite and param mismatch reject.
|
|
if _, err := resolveSuiteForLane("does-not-exist", EvidencePulsarThresholdMLDSA, 0); !errors.Is(err, ErrUnknownSuite) {
|
|
t.Fatalf("unknown suite: %v", err)
|
|
}
|
|
if _, err := resolveSuiteForLane("Lux-Pulsar-TALUS-MLDSA44", EvidencePulsarThresholdMLDSA, uint8(QuorumSchemeMLDSA65)); !errors.Is(err, ErrSuiteParamMismatch) {
|
|
t.Fatalf("param mismatch not caught: %v", err)
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 5. P3Q rollup fallback path.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
func TestCompact_P3QRollup_DirectPath(t *testing.T) {
|
|
policy := NewQuasarEvidencePolicy(PolicyRecoveryMode, pqParam, compactThreshold)
|
|
store, validators, cert, msg, innerWire := p3qDirectCert(t, policy)
|
|
|
|
// Happy path: RECOVERY = Beam ∧ P3Q verifies end-to-end over a real
|
|
// validator-set-bound MLDSACertSet.
|
|
if err := VerifyConsensusCert(store, validators, cert); err != nil {
|
|
t.Fatalf("RECOVERY Beam+P3Q rejected: %v", err)
|
|
}
|
|
|
|
p3qLeg := cert.Evidence[1] // [Beam, P3Q]
|
|
if err := VerifyP3QRollupLeg(policy, validators, cert, msg, p3qLeg); err != nil {
|
|
t.Fatalf("valid P3Q rollup rejected: %v", err)
|
|
}
|
|
|
|
// Tampered rollup root — commitment binding fails before any inner verify.
|
|
badRootPayload := &P3QRollupPayload{SuiteID: p3qSuiteDirect, CertSet: innerWire}
|
|
badRootPayload.RollupRoot = P3QRollupRoot(p3qSuiteDirect, innerWire)
|
|
badRootPayload.RollupRoot[0] ^= 0xFF
|
|
badRoot := LegEvidence{Leg: p3qLeg.Leg, Mode: EvidenceP3QRollup, Payload: encodeP3QRollupPayload(badRootPayload)}
|
|
if err := VerifyP3QRollupLeg(policy, validators, cert, msg, badRoot); !errors.Is(err, ErrP3QRootMismatch) {
|
|
t.Fatalf("tampered root: err = %v, want ErrP3QRootMismatch", err)
|
|
}
|
|
|
|
// Corrupt the MLDSACertSet (a signature byte). The rollup root is recomputed
|
|
// over the corrupted bytes (so the commitment passes) — the inner
|
|
// validator-set-bound verify must then reject.
|
|
corruptWire := append([]byte(nil), innerWire...)
|
|
corruptWire[len(corruptWire)/2] ^= 0xFF
|
|
corrupt := LegEvidence{Leg: p3qLeg.Leg, Mode: EvidenceP3QRollup, Payload: encodeP3QRollupPayload(&P3QRollupPayload{
|
|
SuiteID: p3qSuiteDirect, RollupRoot: P3QRollupRoot(p3qSuiteDirect, corruptWire), CertSet: corruptWire,
|
|
})}
|
|
if err := VerifyP3QRollupLeg(policy, validators, cert, msg, corrupt); err == nil {
|
|
t.Fatal("corrupt MLDSACertSet accepted")
|
|
}
|
|
|
|
// Sub-threshold floor: a posture whose weight floor (200) exceeds the
|
|
// MLDSACertSet's Σweight (100). The inner weighted verify pins the floor and
|
|
// rejects — the threshold is enforced THROUGH the P3Q leg.
|
|
highPolicy := NewQuasarEvidencePolicy(PolicyRecoveryMode, pqParam, 200)
|
|
if err := VerifyP3QRollupLeg(highPolicy, validators, cert, msg, p3qLeg); err == nil {
|
|
t.Fatal("P3Q rollup accepted below the threshold floor")
|
|
}
|
|
}
|
|
|
|
func TestCompact_P3QRollup_SuccinctGated(t *testing.T) {
|
|
policy := NewQuasarEvidencePolicy(PolicyRecoveryMode, pqParam, compactThreshold)
|
|
cert, _, msg := compactHeader(policy, fixedSignerRoot())
|
|
validators := &envValidators{root: cert.ValidatorSetRoot, epoch: 9}
|
|
|
|
succinctLeg := func(suiteID string) LegEvidence {
|
|
return LegEvidence{
|
|
Leg: LegSpec{Kind: LegPulsarMLDSA, ParamSetID: pqParam},
|
|
Mode: EvidenceP3QRollup,
|
|
Payload: encodeP3QRollupPayload(&P3QRollupPayload{
|
|
SuiteID: suiteID,
|
|
RollupRoot: P3QRollupRoot(suiteID, []byte("committed-statement")),
|
|
Proof: []byte("succinct-proof-bytes"),
|
|
}),
|
|
}
|
|
}
|
|
|
|
// STARK (PQ-succinct): audit-gated, fails closed.
|
|
if err := VerifyP3QRollupLeg(policy, validators, cert, msg, succinctLeg("Lux-P3Q-MLDSA65-STARK-v1")); !errors.Is(err, ErrP3QBackendNotAuditGated) {
|
|
t.Fatalf("STARK P3Q: err = %v, want ErrP3QBackendNotAuditGated", err)
|
|
}
|
|
|
|
// Groth16 (classical succinct): refused unless the policy opts in.
|
|
groth := succinctLeg("Lux-P3Q-MLDSA65-Groth16-v1")
|
|
if err := VerifyP3QRollupLeg(policy, validators, cert, msg, groth); !errors.Is(err, ErrClassicalProofAssumptionRefused) {
|
|
t.Fatalf("Groth16 P3Q without opt-in: err = %v, want ErrClassicalProofAssumptionRefused", err)
|
|
}
|
|
// With explicit opt-in, the classical assumption is accepted but the backend
|
|
// is still unaudited ⇒ fail closed (PQ-safe raw data challengeable).
|
|
optIn := NewQuasarEvidencePolicy(PolicyRecoveryMode, pqParam, compactThreshold).WithClassicalProofAssumption()
|
|
if err := VerifyP3QRollupLeg(optIn, validators, cert, msg, groth); !errors.Is(err, ErrP3QBackendNotAuditGated) {
|
|
t.Fatalf("Groth16 P3Q with opt-in: err = %v, want ErrP3QBackendNotAuditGated", err)
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 6. KeyEra registry + the one-M property.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
func TestCompact_KeyEraRegistry(t *testing.T) {
|
|
reg := NewKeyEraRegistry()
|
|
var ss [48]byte
|
|
ss[0] = 0x09
|
|
M := []byte("era registry message")
|
|
_, gk := signPulsarLegFIPS204(t, M)
|
|
era := KeyEra{ChainID: 9, SignerSetID: ss, KeyEraID: 1, Generation: 0, GroupPubKey: gk, SchemeID: "Lux-Pulsar-TALUS-MLDSA65"}
|
|
|
|
if err := reg.Register(era); err != nil {
|
|
t.Fatalf("register: %v", err)
|
|
}
|
|
if err := reg.Register(era); err != nil {
|
|
t.Fatalf("idempotent re-register: %v", err)
|
|
}
|
|
got, err := reg.Lookup(9, ss, 1, 0)
|
|
if err != nil {
|
|
t.Fatalf("lookup: %v", err)
|
|
}
|
|
if got.SchemeID != era.SchemeID {
|
|
t.Fatal("lookup returned a different era")
|
|
}
|
|
// Conflicting key under same coordinates rejected.
|
|
conflict := era
|
|
conflict.GroupPubKey = append([]byte(nil), gk...)
|
|
conflict.GroupPubKey[0] ^= 0xFF
|
|
if err := reg.Register(conflict); !errors.Is(err, ErrEraConflict) {
|
|
t.Fatalf("conflict: err = %v, want ErrEraConflict", err)
|
|
}
|
|
// Not-found.
|
|
if _, err := reg.Lookup(9, ss, 2, 0); !errors.Is(err, ErrEraNotFound) {
|
|
t.Fatalf("not-found: err = %v, want ErrEraNotFound", err)
|
|
}
|
|
// The registry is LANE-AGNOSTIC: a Corona (Ring-LWE) era registers fine —
|
|
// KeyEra serves every threshold lane, not just Pulsar. Registration checks
|
|
// the lane + a non-empty key; the full Corona group-signature verify is
|
|
// proven in TestCompact_VerifyThresholdLeg_CoronaLane.
|
|
coronaEra := KeyEra{ChainID: 9, SignerSetID: ss, KeyEraID: 2, Generation: 0, GroupPubKey: []byte("corona-group-key"), SchemeID: "Lux-Corona-Ringtail-L3-v1"}
|
|
if err := reg.Register(coronaEra); err != nil {
|
|
t.Fatalf("corona era registration refused (registry must be lane-agnostic): %v", err)
|
|
}
|
|
// A NON-threshold lane (Beam classical aggregate) has no compact group-key
|
|
// era and is refused at registration.
|
|
bad := era
|
|
bad.SchemeID = "Lux-Beam-BLS12381-v1"
|
|
if err := reg.Register(bad); !errors.Is(err, ErrSuiteLaneMismatch) {
|
|
t.Fatalf("non-threshold era scheme: err = %v, want ErrSuiteLaneMismatch", err)
|
|
}
|
|
}
|
|
|
|
// TestCompact_OneCanonicalMessage proves QuasarFinalityMessage and the
|
|
// envelope's consensusCertMessage produce byte-identical M for the same tuple —
|
|
// all lanes provably sign the SAME M.
|
|
func TestCompact_OneCanonicalMessage(t *testing.T) {
|
|
policy := NewQuasarEvidencePolicy(PolicyStrictQuasar, pqParam, compactThreshold)
|
|
cert, vsetRoot, envMsg := compactHeader(policy, fixedSignerRoot())
|
|
|
|
typed := QuasarFinalityMessage(QuasarFinalityParams{
|
|
ChainID: cert.ChainID,
|
|
Epoch: cert.Epoch,
|
|
Height: cert.Height,
|
|
Round: cert.Round,
|
|
BlockID: cert.BlockHash,
|
|
StateRoot: cert.StateRoot,
|
|
SignerSetID: vsetRoot,
|
|
KeyEraID: cert.KeyEraID,
|
|
EvidencePolicyID: cert.PolicyID,
|
|
RequiredLegsRoot: cert.RequiredLegsRoot,
|
|
SignerRoot: cert.SignerRoot,
|
|
Profile: cert.Profile,
|
|
})
|
|
if string(typed) != string(envMsg) {
|
|
t.Fatal("QuasarFinalityMessage != consensusCertMessage for the same tuple — two finality messages")
|
|
}
|
|
|
|
// Changing the key era changes M (era binding).
|
|
cert2 := *cert
|
|
cert2.KeyEraID = 8
|
|
if string(consensusCertMessage(&cert2, cert2.RequiredLegsRoot)) == string(envMsg) {
|
|
t.Fatal("KeyEraID is not bound into M")
|
|
}
|
|
}
|