Files
warp/evidence_test.go
zeekay 954cd81ecf fix(warp): close red-review findings on the typed evidence model (R1/R2/R3/R4)
R1 (MEDIUM): bind P3Q ProvingSystem to the verified proof. VerifyP3QRollup now
returns the proof system it ACTUALLY verified; the dispatcher rejects
(ErrP3QProvingSystemMismatch) if it differs from the claimed ProvingSystem the
strict-PQ gate keys on — a classical proof relabeled "stark-rescue" can no longer
pass strict finality. New TestP3QProvingSystemMismatchRejected.

R2 (MEDIUM): unexport the bytes dispatcher (VerifyFinalityEvidence ->
verifyFinalityEvidence). It is NOT a finality verdict (returns nil for the
non-strict cert-set / an ungated classical P3Q lane); unexporting forces
consumers through AcceptQuasarCert (consensus M) / VerifyWithOptions (cross-chain
D) — the only finality entry points — removing the footgun before the API freezes.

R3: document the verify-path SECURITY PRECONDITIONS on LaneVerifierSet — resolver
authenticity (keys MUST come from the authenticated on-chain key lineage) and
per-lane key independence (no shared Pulsar/Corona seed, else dual-PQ collapses to
single-PQ).

R4 (LOW): the Pulsar import-hygiene guard now t.Fatal's on go-list failure instead
of silently skipping a load-bearing check.

Full warp suite green (23 tests).
2026-06-27 14:08:56 -07:00

658 lines
25 KiB
Go

// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// evidence_test.go — the typed finality-evidence dispatch + policy tests.
// These pin the non-confusability invariants (KIND chooses the verifier, SUITE
// only parameterizes, a lane can NEVER route to the wrong verifier), the REAL
// Pulsar ML-DSA verify, the P3Q strict-root guardrail, the cert-set
// inadmissibility, the policy tiers, the canonical M subject, and the
// distinctness of the per-lane key registries.
//
// The spy verifiers (stubCoronaVerifier / stubCertSetVerifier) live in
// envelope_test.go and record whether they were called, so a "does not
// dispatch to X" test proves it by asserting X's spy was never invoked.
package warp
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"errors"
"testing"
"github.com/luxfi/crypto/bls"
mldsa65 "github.com/luxfi/crypto/pq/mldsa/mldsa65"
"github.com/luxfi/ids"
"github.com/stretchr/testify/require"
)
// ---------------------------------------------------------------------
// Test fakes for the subject-agnostic lanes.
// ---------------------------------------------------------------------
type fakeBeamVerifier struct {
called bool
err error
}
func (f *fakeBeamVerifier) VerifyBeam(_ []byte, _ BitSetSignature) error {
f.called = true
return f.err
}
type fakePulsarEraResolver struct {
era PulsarKeyEra
err error
}
func (f *fakePulsarEraResolver) ResolvePulsarKeyEra(_ ids.ID, _ uint64, _ uint64) (PulsarKeyEra, error) {
return f.era, f.err
}
type fakeP3QVerifier struct {
called bool
err error
proven string // proof system the fake "verified"; empty → echoes root.ProvingSystem
}
func (f *fakeP3QVerifier) VerifyP3QRollup(_ []byte, root P3QRoot, _ SignerSetAuthority) (string, error) {
f.called = true
if f.err != nil {
return "", f.err
}
if f.proven != "" {
return f.proven, nil
}
return root.ProvingSystem, nil
}
type fakeSignerSetAuthority struct{}
func (fakeSignerSetAuthority) ResolveSignerSet(_ ids.ID, _ uint64) ([]ValidatorMLDSAKey, uint64, error) {
return nil, 0, nil
}
// fullEvidenceEnvelope builds a Quasar envelope carrying all THREE wire lanes
// (Beam + Corona + MLDSACertSet) with deterministic opaque PQ-lane bytes.
func fullEvidenceEnvelope(t *testing.T) *Envelope {
t.Helper()
env := envelopeFixture(t) // Beam populated, message tag = MessageHashProfileTag
env.CoronaSig = bytes.Repeat([]byte{0x42}, 64)
env.MLDSACertSet = bytes.Repeat([]byte{0xC3}, 192)
require.NoError(t, env.Verify())
return env
}
// envSubject returns the warp subject D for an envelope.
func envSubject(e *Envelope) []byte {
id := e.Message.ID()
return id[:]
}
// ---------------------------------------------------------------------
// v1 invariants: non-confusable dispatch.
// ---------------------------------------------------------------------
// TestPulsarThresholdMLDSADoesNotDispatchToCorona proves Pulsar evidence
// routes ONLY to the (non-injectable) VerifyPulsar path — never to the corona
// verifier. With no Pulsar key-era resolver wired it fails closed, and the
// corona/cert-set spies are never touched.
func TestPulsarThresholdMLDSADoesNotDispatchToCorona(t *testing.T) {
corona := &stubCoronaVerifier{err: errors.New("corona MUST NOT be called for Pulsar evidence")}
certset := &stubCertSetVerifier{err: errors.New("cert-set MUST NOT be called for Pulsar evidence")}
ev := FinalityEvidence{
Kind: EvidencePulsarThresholdMLDSA,
Suite: SuitePulsarThresholdMLDSA65,
Pulsar: &PulsarEvidence{SuiteID: SuitePulsarThresholdMLDSA65, Signature: []byte{0x01}},
}
// No PulsarEra resolver → fail closed; corona/cert-set verifiers present but
// must never be reached.
err := verifyFinalityEvidence(ev, make([]byte, 32), LaneVerifierSet{Corona: corona, CertSet: certset})
require.ErrorIs(t, err, ErrNoVerifierForKind,
"Pulsar evidence must fail closed without a key-era resolver")
require.False(t, corona.called, "Pulsar evidence dispatched to the corona verifier")
require.False(t, certset.called, "Pulsar evidence dispatched to the cert-set verifier")
}
// TestPulsarThresholdMLDSADoesNotDispatchToCertSet is the cert-set-facing half
// of the same invariant.
func TestPulsarThresholdMLDSADoesNotDispatchToCertSet(t *testing.T) {
certset := &stubCertSetVerifier{err: errors.New("cert-set MUST NOT be called for Pulsar evidence")}
ev := FinalityEvidence{
Kind: EvidencePulsarThresholdMLDSA,
Suite: SuitePulsarThresholdMLDSA65,
Pulsar: &PulsarEvidence{SuiteID: SuitePulsarThresholdMLDSA65, Signature: []byte{0x01}},
}
err := verifyFinalityEvidence(ev, make([]byte, 32), LaneVerifierSet{CertSet: certset})
require.ErrorIs(t, err, ErrNoVerifierForKind)
require.False(t, certset.called, "Pulsar evidence dispatched to the cert-set verifier")
}
// TestCoronaRingtailDoesNotDispatchToPulsar proves Corona evidence routes to
// the Ringtail verifier and never returns a Pulsar-domain error.
func TestCoronaRingtailDoesNotDispatchToPulsar(t *testing.T) {
sentinel := errors.New("corona-verifier-reached")
corona := &stubCoronaVerifier{err: sentinel}
ev := FinalityEvidence{
Kind: EvidenceCoronaRingtail,
Suite: SuiteCoronaRingtailSHA3,
Corona: &CoronaEvidence{Sig: bytes.Repeat([]byte{0x42}, 64)},
}
err := verifyFinalityEvidence(ev, make([]byte, 32), LaneVerifierSet{Corona: corona})
require.True(t, corona.called, "corona evidence did not reach the corona verifier")
require.ErrorIs(t, err, sentinel, "corona evidence must route to the corona verifier")
require.NotErrorIs(t, err, ErrWrongEra, "corona evidence must NEVER produce a Pulsar-domain error")
require.NotErrorIs(t, err, ErrBadSignature)
}
// TestMLDSACertSetDoesNotDispatchToPulsar proves cert-set evidence routes to
// the independent-cert verifier, never to the Pulsar path.
func TestMLDSACertSetDoesNotDispatchToPulsar(t *testing.T) {
sentinel := errors.New("certset-verifier-reached")
certset := &stubCertSetVerifier{err: sentinel}
ev := FinalityEvidence{
Kind: EvidenceMLDSACertSet,
Suite: SuiteMLDSA65CertSetSHA3,
CertSet: &CertSetEvidence{CertSet: bytes.Repeat([]byte{0xC3}, 192)},
}
err := verifyFinalityEvidence(ev, make([]byte, 32), LaneVerifierSet{CertSet: certset})
require.True(t, certset.called, "cert-set evidence did not reach the cert-set verifier")
require.ErrorIs(t, err, sentinel)
require.NotErrorIs(t, err, ErrWrongEra,
"cert-set evidence must NEVER produce a Pulsar-domain error")
}
// TestQuasarEnvelopeCarriesTypedEvidenceKinds pins Envelope.Evidence(): every
// carried lane is enumerated with the correct (kind, suite) pairing, and the
// reserved Pulsar/P3Q consensus kinds are NOT carried on the warp wire.
func TestQuasarEnvelopeCarriesTypedEvidenceKinds(t *testing.T) {
env := fullEvidenceEnvelope(t)
evs := env.Evidence()
suiteByKind := make(map[FinalityEvidenceKind]SuiteID, len(evs))
for _, ev := range evs {
_, dup := suiteByKind[ev.Kind]
require.False(t, dup, "duplicate evidence kind %q", ev.Kind)
suiteByKind[ev.Kind] = ev.Suite
}
require.Equal(t, SuiteBeamBLS12381, suiteByKind[EvidenceBeamBLS])
require.Equal(t, SuiteCoronaRingtailSHA3, suiteByKind[EvidenceCoronaRingtail])
require.Equal(t, SuiteMLDSA65CertSetSHA3, suiteByKind[EvidenceMLDSACertSet])
require.Len(t, evs, 3, "Beam + Corona + MLDSACertSet expected")
require.NotContains(t, suiteByKind, EvidencePulsarThresholdMLDSA,
"Pulsar is a consensus lane with NO warp wire field; it must not be enumerated")
require.NotContains(t, suiteByKind, EvidenceP3QMLDSARollup,
"P3Q is a consensus lane with NO warp wire field; it must not be enumerated")
// The carried lanes hold their routing context, not an envelope back-ref.
for _, ev := range evs {
switch ev.Kind {
case EvidenceBeamBLS:
require.NotNil(t, ev.Beam)
case EvidenceCoronaRingtail:
require.NotNil(t, ev.Corona)
require.Equal(t, env.Message.SourceKeyEraID, ev.Corona.KeyEraID)
require.Equal(t, env.CoronaSig, ev.Corona.Sig)
case EvidenceMLDSACertSet:
require.NotNil(t, ev.CertSet)
require.Equal(t, env.MLDSACertSet, ev.CertSet.CertSet)
}
}
// A Beam-only envelope carries exactly one lane.
beamOnly := envelopeFixture(t)
beamEvs := beamOnly.Evidence()
require.Len(t, beamEvs, 1)
require.Equal(t, EvidenceBeamBLS, beamEvs[0].Kind)
}
// TestDefaultPulsarSuiteIsThresholdMLDSA pins the typed default — the NEW TALUS
// suite value.
func TestDefaultPulsarSuiteIsThresholdMLDSA(t *testing.T) {
require.Equal(t, SuitePulsarThresholdMLDSA65, DefaultPulsarSuiteID)
require.Equal(t, SuiteID("Lux-Pulsar-TALUS-MLDSA65"), DefaultPulsarSuiteID)
// The Pulsar suite is distinct from every other lane suite.
require.NotEqual(t, SuiteID(DefaultCoronaSuiteID), SuiteID(DefaultPulsarSuiteID))
require.NotEqual(t, SuiteBeamBLS12381, DefaultPulsarSuiteID)
require.NotEqual(t, SuiteMLDSA65CertSetSHA3, DefaultPulsarSuiteID)
require.NotEqual(t, SuiteP3QMLDSARollup, DefaultPulsarSuiteID)
}
// TestDefaultCoronaSuiteIsRingtail pins the typed default and that it is NOT
// the legacy "Pulsar-SHA3" message tag — the decoupling that kills the alias.
func TestDefaultCoronaSuiteIsRingtail(t *testing.T) {
require.Equal(t, SuiteCoronaRingtailSHA3, DefaultCoronaSuiteID)
require.Equal(t, SuiteID("Corona-Ringtail-SHA3"), DefaultCoronaSuiteID)
require.NotEqual(t, SuiteID(MessageHashProfileTag), SuiteID(DefaultCoronaSuiteID),
"the corona lane suite must NOT be the message-level Pulsar-SHA3 tag")
}
// TestSuiteKindMismatchRejects proves requireSuite fails closed when a suite
// does not match its kind — and that a verifier is NOT reached when the suite
// guard fires (suite never selects a verifier; it only validates one).
func TestSuiteKindMismatchRejects(t *testing.T) {
corona := &stubCoronaVerifier{}
// Corona kind carrying the Beam suite: structurally inadmissible.
ev := FinalityEvidence{
Kind: EvidenceCoronaRingtail,
Suite: SuiteBeamBLS12381,
Corona: &CoronaEvidence{Sig: bytes.Repeat([]byte{0x42}, 64)},
}
err := verifyFinalityEvidence(ev, make([]byte, 32), LaneVerifierSet{Corona: corona})
require.ErrorIs(t, err, ErrSuiteKindMismatch)
require.False(t, corona.called, "the suite guard must fire BEFORE the verifier is reached")
// Pulsar kind carrying the corona suite: rejected before any verify.
evP := FinalityEvidence{
Kind: EvidencePulsarThresholdMLDSA,
Suite: SuiteCoronaRingtailSHA3,
Pulsar: &PulsarEvidence{},
}
errP := verifyFinalityEvidence(evP, make([]byte, 32), LaneVerifierSet{})
require.ErrorIs(t, errP, ErrSuiteKindMismatch)
require.NotErrorIs(t, errP, ErrNoVerifierForKind)
// An unknown kind fails closed too.
evU := FinalityEvidence{Kind: "bogus-kind", Suite: SuiteBeamBLS12381}
require.ErrorIs(t, verifyFinalityEvidence(evU, make([]byte, 32), LaneVerifierSet{}), ErrUnknownFinalityEvidence)
}
// goldenQuasarTypedEvidenceWireSHA256 is the SHA-256 of the canonical wire
// bytes of the golden full-evidence envelope below. The envelope wire is
// UNCHANGED by the typed-evidence refactor (the renames are Go-level; the
// message c14n / D / magic / kind bytes are byte-stable), so this fingerprint
// pins that stability.
const goldenQuasarTypedEvidenceWireSHA256 = "3747d25a12703f2cd20f1ebb370e582017b7a33a1f68afd7e72a48200542380b"
// TestGoldenQuasarTypedEvidenceRoundTrip pins a golden full-evidence envelope:
// its canonical wire bytes round-trip byte-equally, its typed evidence
// enumeration is stable, and the wire SHA-256 matches the golden fingerprint.
func TestGoldenQuasarTypedEvidenceRoundTrip(t *testing.T) {
message := &Message{
NetworkID: 1,
SourceChainID: ids.ID{0xA1, 0xA2, 0xA3, 0xA4},
SourceNebulaRoot: [32]byte{0xDE, 0xAD, 0xBE, 0xEF},
SourceKeyEraID: 7,
SourceGeneration: 11,
HashSuiteID: MessageHashProfileTag,
Payload: []byte("quasar-typed-evidence-golden"),
}
signers := NewBitSet()
signers.Add(0)
signers.Add(2)
signers.Add(4)
var sig [bls.SignatureLen]byte
copy(sig[:], bytes.Repeat([]byte{0xAB}, bls.SignatureLen))
env, err := NewEnvelope(
message,
NewBitSetSignature(signers, sig),
bytes.Repeat([]byte{0x42}, 64), // Corona lane
bytes.Repeat([]byte{0xC3}, 192), // MLDSACertSet lane
)
require.NoError(t, err)
wire, err := env.Bytes()
require.NoError(t, err)
parsed, err := ParseEnvelope(wire)
require.NoError(t, err)
re, err := parsed.Bytes()
require.NoError(t, err)
require.Equal(t, wire, re, "envelope wire must round-trip byte-equal")
require.Equal(t, env.ID(), parsed.ID(), "D must survive the round-trip")
want := []struct {
kind FinalityEvidenceKind
suite SuiteID
}{
{EvidenceBeamBLS, SuiteBeamBLS12381},
{EvidenceCoronaRingtail, SuiteCoronaRingtailSHA3},
{EvidenceMLDSACertSet, SuiteMLDSA65CertSetSHA3},
}
evs := parsed.Evidence()
require.Len(t, evs, len(want))
for i, w := range want {
require.Equal(t, w.kind, evs[i].Kind)
require.Equal(t, w.suite, evs[i].Suite)
}
gotSum := sha256.Sum256(wire)
require.Equal(t, goldenQuasarTypedEvidenceWireSHA256, hex.EncodeToString(gotSum[:]),
"golden Quasar envelope wire bytes changed")
}
// ---------------------------------------------------------------------
// Pulsar: REAL FIPS-204 ML-DSA-65 verify under a group public key.
// ---------------------------------------------------------------------
const (
testPulsarKeyEraID = uint64(5)
testPulsarGeneration = uint64(2)
)
var testPulsarSignerSet = ids.ID{0x5A, 0x5A, 0x5A, 0x5A}
// signPulsar produces a real ML-DSA-65 quorum signature over subject under a
// fresh group key, plus the matching (era, evidence). It models exactly what
// the chain sees: an ordinary ML-DSA public key + an ordinary ML-DSA signature.
func signPulsar(t *testing.T, subject []byte) (PulsarEvidence, PulsarKeyEra, *mldsa65.PublicKey) {
t.Helper()
pk, sk, err := mldsa65.GenerateKey(rand.Reader)
require.NoError(t, err)
sig, err := mldsa65.Sign(sk, subject, pulsarLaneContext, false)
require.NoError(t, err)
ev := PulsarEvidence{
SignerSetID: testPulsarSignerSet,
KeyEraID: testPulsarKeyEraID,
Generation: testPulsarGeneration,
SuiteID: SuitePulsarThresholdMLDSA65,
Signature: sig,
}
era := PulsarKeyEra{
ChainID: ids.ID{0xC0},
SignerSetID: testPulsarSignerSet,
KeyEraID: testPulsarKeyEraID,
Generation: testPulsarGeneration,
MLDSAPubKey: pk.Bytes(),
SchemeID: SuitePulsarThresholdMLDSA65,
KeygenMode: "talus-mpc",
}
return ev, era, pk
}
// TestPulsarVerifierAcceptsStandardMLDSAUnderGroupKey proves the Pulsar lane is
// a plain FIPS-204 ML-DSA verify: a real ML-DSA-65 signature over the subject,
// under the era's group public key, verifies — with NO threshold machinery in
// the path.
func TestPulsarVerifierAcceptsStandardMLDSAUnderGroupKey(t *testing.T) {
subject := make([]byte, 32)
copy(subject, []byte("pulsar-real-mldsa-subject"))
ev, era, _ := signPulsar(t, subject)
require.NoError(t, VerifyPulsar(ev, subject, era))
// And through the dispatcher, with the era supplied by a resolver.
fe := FinalityEvidence{Kind: EvidencePulsarThresholdMLDSA, Suite: SuitePulsarThresholdMLDSA65, Pulsar: &ev}
require.NoError(t, verifyFinalityEvidence(fe, subject, LaneVerifierSet{
PulsarEra: &fakePulsarEraResolver{era: era},
}))
}
// TestPulsarRejectsWrongEra proves the era-identifier check fires before the
// signature check: evidence whose (keyEraID, generation, signerSetID) do not
// match the resolved era is rejected with ErrWrongEra.
func TestPulsarRejectsWrongEra(t *testing.T) {
subject := make([]byte, 32)
copy(subject, []byte("pulsar-wrong-era"))
ev, era, _ := signPulsar(t, subject)
wrong := ev
wrong.KeyEraID = ev.KeyEraID + 1 // valid signature, wrong era handle
require.ErrorIs(t, VerifyPulsar(wrong, subject, era), ErrWrongEra)
wrongGen := ev
wrongGen.Generation = ev.Generation + 1
require.ErrorIs(t, VerifyPulsar(wrongGen, subject, era), ErrWrongEra)
wrongSet := ev
wrongSet.SignerSetID = ids.ID{0xFF}
require.ErrorIs(t, VerifyPulsar(wrongSet, subject, era), ErrWrongEra)
}
// TestPulsarRejectsWrongGroupKey proves a valid signature under one group key
// does NOT verify under a different group key: the era identifiers match, but
// the ML-DSA verify fails closed with ErrBadSignature.
func TestPulsarRejectsWrongGroupKey(t *testing.T) {
subject := make([]byte, 32)
copy(subject, []byte("pulsar-wrong-key"))
ev, era, _ := signPulsar(t, subject)
// Swap in a DIFFERENT group public key (same era identifiers).
otherPk, _, err := mldsa65.GenerateKey(rand.Reader)
require.NoError(t, err)
era.MLDSAPubKey = otherPk.Bytes()
require.ErrorIs(t, VerifyPulsar(ev, subject, era), ErrBadSignature)
// A wrong subject also fails closed under the right key.
ev2, era2, _ := signPulsar(t, subject)
other := make([]byte, 32)
copy(other, []byte("a-completely-different-subject!!!"))
require.ErrorIs(t, VerifyPulsar(ev2, other, era2), ErrBadSignature)
}
// ---------------------------------------------------------------------
// Policy: tiers, P3Q strict-root guardrail, cert-set inadmissibility.
// ---------------------------------------------------------------------
// quasarCertFixture builds a QuasarCert with a Beam plus a REAL Pulsar lane
// over M, returning the cert and the LaneVerifierSet that admits it (Beam and
// Corona verifiers accept; the Pulsar era resolves the real key).
func quasarCertFixture(t *testing.T) (*QuasarCert, LaneVerifierSet) {
t.Helper()
signers := NewBitSet()
signers.Add(0)
signers.Add(1)
var beamSig [bls.SignatureLen]byte
copy(beamSig[:], bytes.Repeat([]byte{0xAB}, bls.SignatureLen))
cert := &QuasarCert{
Subject: QuasarFinalityParams{
ChainID: ids.ID{0xC1, 0xC2},
Height: 4242,
Round: 7,
BlockID: ids.ID{0xB1, 0xB2},
StateRoot: [32]byte{0x57, 0xA7},
SignerSetID: testPulsarSignerSet,
KeyEraID: testPulsarKeyEraID,
PolicyID: uint64(TierStrictQuasar),
},
Beam: NewBitSetSignature(signers, beamSig),
}
m := cert.SubjectBytes()
ev, era, _ := signPulsar(t, m[:])
cert.Pulsar = &ev
cert.Corona = &CoronaEvidence{
ChainID: cert.Subject.ChainID,
KeyEraID: testPulsarKeyEraID,
Generation: testPulsarGeneration,
Sig: bytes.Repeat([]byte{0x42}, 64),
}
lanes := LaneVerifierSet{
Beam: &fakeBeamVerifier{},
Corona: &stubCoronaVerifier{}, // err nil → accepts
PulsarEra: &fakePulsarEraResolver{era: era},
}
return cert, lanes
}
// TestStrictQuasarRequiresBeamPulsarCorona proves the strict tier admits a cert
// carrying Beam ∧ Pulsar ∧ Corona, and fails closed (ErrMissingLane) when any
// required lane is dropped.
func TestStrictQuasarRequiresBeamPulsarCorona(t *testing.T) {
require.Equal(t, [][]FinalityEvidenceKind{
{EvidenceBeamBLS},
{EvidencePulsarThresholdMLDSA},
{EvidenceCoronaRingtail},
}, RequiredKinds(TierStrictQuasar))
cert, lanes := quasarCertFixture(t)
require.NoError(t, AcceptQuasarCert(TierStrictQuasar, cert, lanes))
// Drop Corona → strict tier fails closed.
noCorona := *cert
noCorona.Corona = nil
require.ErrorIs(t, AcceptQuasarCert(TierStrictQuasar, &noCorona, lanes), ErrMissingLane)
// Drop Pulsar → strict tier fails closed.
noPulsar := *cert
noPulsar.Pulsar = nil
require.ErrorIs(t, AcceptQuasarCert(TierStrictQuasar, &noPulsar, lanes), ErrMissingLane)
}
// TestRecoveryTierAcceptsP3Q proves the recovery tier admits Beam ∧ P3Q when
// the P3Q rollup's proof system is post-quantum.
func TestRecoveryTierAcceptsP3Q(t *testing.T) {
cert, lanes := quasarCertFixture(t)
cert.Pulsar = nil
cert.Corona = nil
cert.P3QRoot = &P3QRoot{
SignerSetID: testPulsarSignerSet,
ProvingSystem: "stark-rescue", // PQ root of trust
SuiteID: SuiteP3QMLDSARollup,
Root: bytes.Repeat([]byte{0x01}, 32),
Proof: bytes.Repeat([]byte{0x02}, 64),
}
lanes.P3Q = &fakeP3QVerifier{}
lanes.SignerSet = fakeSignerSetAuthority{}
cert.Subject.PolicyID = PolicyIDForTier(TierRecovery)
require.NoError(t, AcceptQuasarCert(TierRecovery, cert, lanes))
require.True(t, lanes.P3Q.(*fakeP3QVerifier).called, "recovery tier must reach the P3Q verifier")
}
// TestP3QClassicalRootRejectedAsStrictRoot proves a P3Q root backed by a
// CLASSICAL proof system (Groth16) is refused as a strict-PQ finality root —
// the bytes verifier is never even reached — unless policy explicitly opts in.
func TestP3QClassicalRootRejectedAsStrictRoot(t *testing.T) {
cert, lanes := quasarCertFixture(t)
cert.Pulsar = nil
cert.Corona = nil
p3qVerifier := &fakeP3QVerifier{}
cert.P3QRoot = &P3QRoot{
SignerSetID: testPulsarSignerSet,
ProvingSystem: "groth16", // classical — NOT a PQ root of trust
SuiteID: SuiteP3QMLDSARollup,
Root: bytes.Repeat([]byte{0x01}, 32),
}
lanes.P3Q = p3qVerifier
lanes.SignerSet = fakeSignerSetAuthority{}
cert.Subject.PolicyID = PolicyIDForTier(TierRecovery)
err := AcceptQuasarCert(TierRecovery, cert, lanes)
require.ErrorIs(t, err, ErrP3QClassicalRoot,
"a Groth16 P3Q root must be refused as a strict-PQ finality root")
require.False(t, p3qVerifier.called,
"the strict-root guardrail must fire BEFORE the P3Q bytes verifier")
// The deliberate, auditable opt-in admits it (and now reaches the verifier).
require.NoError(t, AcceptQuasarCert(TierRecovery, cert, lanes, WithAllowClassicalP3QRoot()))
require.True(t, p3qVerifier.called)
// Direct gate check.
require.ErrorIs(t, P3QStrictRootOK(*cert.P3QRoot), ErrP3QClassicalRoot)
require.NoError(t, P3QStrictRootOK(P3QRoot{ProvingSystem: "lattice-zk"}))
}
// TestRawCertSetInadmissibleAsStrictFinality proves a raw ML-DSA cert-set is
// recognized but NEVER admissible as a strict finality root: it is in no tier's
// required kinds, so a cert carrying only Beam + CertSet fails every PQ tier.
func TestRawCertSetInadmissibleAsStrictFinality(t *testing.T) {
require.False(t, IsStrictFinalityKind(EvidenceMLDSACertSet),
"raw cert-set must never be a strict finality kind")
require.True(t, IsStrictFinalityKind(EvidenceBeamBLS))
require.True(t, IsStrictFinalityKind(EvidencePulsarThresholdMLDSA))
require.True(t, IsStrictFinalityKind(EvidenceCoronaRingtail))
require.True(t, IsStrictFinalityKind(EvidenceP3QMLDSARollup))
// No tier's required kinds include the cert-set.
for _, tier := range []FinalityTier{TierBLSFast, TierHybridPQCheckpoint, TierStrictQuasar, TierRecovery} {
for _, group := range RequiredKinds(tier) {
for _, k := range group {
require.NotEqual(t, EvidenceMLDSACertSet, k,
"tier %s must not require the cert-set as a strict lane", tier)
}
}
}
cert, lanes := quasarCertFixture(t)
cert.Pulsar = nil
cert.Corona = nil
cert.CertSet = &CertSetEvidence{ChainID: cert.Subject.ChainID, CertSet: bytes.Repeat([]byte{0xC3}, 192)}
lanes.CertSet = &stubCertSetVerifier{} // would accept the bytes if ever asked
// Even though the cert-set verifies for AVAILABILITY, it cannot satisfy any
// strict/PQ finality tier. Bind the cert's PolicyID to each tier so the
// tier-check passes and the failure is specifically ErrMissingLane (the
// strict lane is absent), not a policy mismatch.
for _, tier := range []FinalityTier{TierStrictQuasar, TierRecovery, TierHybridPQCheckpoint} {
cert.Subject.PolicyID = PolicyIDForTier(tier)
require.ErrorIs(t, AcceptQuasarCert(tier, cert, lanes), ErrMissingLane, "tier %s", tier)
}
}
// ---------------------------------------------------------------------
// The consensus subject M.
// ---------------------------------------------------------------------
// TestQuasarFinalitySubjectIsCanonical pins M's transcript: fixed length,
// lossless round-trip, deterministic digest, and total-order binding (changing
// ANY field changes M).
func TestQuasarFinalitySubjectIsCanonical(t *testing.T) {
p := QuasarFinalityParams{
ChainID: ids.ID{0x01, 0x02, 0x03},
Height: 99,
Round: 3,
BlockID: ids.ID{0x0B, 0x0C},
StateRoot: [32]byte{0x57, 0xA7, 0xE0},
SignerSetID: ids.ID{0x55},
KeyEraID: 8,
PolicyID: uint64(TierStrictQuasar),
}
transcript := p.MarshalTranscript()
require.Len(t, transcript, quasarFinalityTranscriptLen)
// Lossless round-trip; re-marshal is byte-equal.
parsed, err := ParseQuasarFinalityTranscript(transcript)
require.NoError(t, err)
require.Equal(t, p, parsed)
require.Equal(t, transcript, parsed.MarshalTranscript())
// Deterministic digest.
require.Equal(t, QuasarFinalitySubject(p), QuasarFinalitySubject(p))
// Non-canonical lengths are rejected.
_, err = ParseQuasarFinalityTranscript(transcript[:len(transcript)-1])
require.Error(t, err)
_, err = ParseQuasarFinalityTranscript(append(transcript, 0x00))
require.Error(t, err)
// Total-order binding: every field changes M.
base := QuasarFinalitySubject(p)
mutators := []func(*QuasarFinalityParams){
func(q *QuasarFinalityParams) { q.ChainID = ids.ID{0xFF} },
func(q *QuasarFinalityParams) { q.Height++ },
func(q *QuasarFinalityParams) { q.Round++ },
func(q *QuasarFinalityParams) { q.BlockID = ids.ID{0xFF} },
func(q *QuasarFinalityParams) { q.StateRoot = [32]byte{0xFF} },
func(q *QuasarFinalityParams) { q.SignerSetID = ids.ID{0xFF} },
func(q *QuasarFinalityParams) { q.KeyEraID++ },
func(q *QuasarFinalityParams) { q.Generation++ },
func(q *QuasarFinalityParams) { q.PChainHeight++ },
func(q *QuasarFinalityParams) { q.PolicyID++ },
}
for i, mut := range mutators {
q := p
mut(&q)
require.NotEqual(t, base, QuasarFinalitySubject(q), "field %d does not bind into M", i)
}
// M is domain-separated from the warp message digest D: an all-zero
// transcript still differs from a raw keccak over the same zero bytes.
require.NotEqual(t, keccak256(p.MarshalTranscript()), QuasarFinalitySubject(p))
}