Files
warp/strict_pq_test.go
zeekay 3e1633748d tests: PQ-default e2e + strict-PQ pairings + signature-leg fuzz
Three new test files that lock in the Tier A posture's
operational invariants at the warp root package layer.

cross_chain_envelope_e2e_test.go (10 tests):
- TestE2E_DefaultRegistry_IsPQNative
- TestE2E_OptInRegistry_AdmitsClassical
- TestE2E_PQEnvelope_VerifiesUnderEveryMode
- TestE2E_ClassicalEnvelope_RefusedUnderStrictPQ
- TestE2E_ClassicalEnvelope_WireRefusedUnderStrictPQ
- TestE2E_VerifyClosure_InvokedOnPQEvidence
- TestE2E_VerifyClosure_SkippedOnClassicalEvidence
- TestE2E_CrossVersion_IDPreserved
- TestE2E_V1Bytes_LiftToV2EnvelopeWithEmptyPQ
- TestE2E_KAT_PQEnvelope_WireBytesStable

strict_pq_test.go (7 tests + 8 sub-cases):
- TestStrictPQ_ClassicalEnvelope_Refused
- TestStrictPQ_PQEnvelope_Accepted
- TestStrictPQ_DefaultRegistry_ClassicalOptInRequired
- TestStrictPQ_OptInRegistryStillRefusedAtModeGate
- TestStrictPQ_RecommendedPairings (8 mode×legacy×hasPQ cases)
- TestStrictPQ_LegacyClassicalFlag_Documented
- TestStrictPQ_DefaultIsPQAware

The decomplection invariant — registry posture independent of
chain posture — is the key property pinned by
TestStrictPQ_OptInRegistryStillRefusedAtModeGate: a registry
that successfully installed BLS (LegacyClassicalEnabled=true)
still has its classical envelopes refused at chain verification
when the chain is strict-PQ.

signature_scheme_fuzz_test.go (2 harnesses):
- FuzzSignatureSchemeLegParser  exercises ParseEnvelope + pq.ValidateMode
                                under arbitrary byte input
                                (≥1.87M iterations clean in 15s @ ~125k/s)
- FuzzCorruptedMLDSACertSet     tampers with MLDSACertSet length
                                prefix to catch buffer-overrun /
                                off-by-one length parsing
                                (≥74k iterations clean in 6s)

Properties pinned by the fuzz harnesses:
A. ParseEnvelope NEVER panics on any byte sequence.
B. env.HasPQEvidence() is consistent with len(MLDSACertSet) > 0.
C. classical-only ⇒ strict-PQ refuses with ErrClassicalAuthForbidden.
D. PQ-evidenced ⇒ strict-PQ accepts (verify closure invoked).
E. Wire-byte round-trip is byte-stable.

Test counts (root warp + crypto/signature):
- 71 PASS / 0 FAIL under -race
- 5 fuzz harnesses (FuzzWarpEnvelopeV2, FuzzBLSAggregateCert,
  FuzzWarpV1Envelope, FuzzSignatureSchemeLegParser,
  FuzzCorruptedMLDSACertSet) all PASS on seed corpus

Pre-existing failures NOT introduced by this push:
- pulsar/TestLP073ContainsGroth16DisclaimerSection11 (external
  lps/LP-073-pulsar.md missing §11) — Gate-3 in CRYPTOGRAPHER-SIGN-OFF.md
- precompile/ (transitive geth/trie/utils/verkle.go interface
  mismatch in luxfi/[email protected]) — out of scope for warp
2026-05-19 08:36:51 -07:00

211 lines
8.4 KiB
Go

// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// strict_pq_test.go is the strict-PQ posture's contract test. It
// extends security_profile_test.go (which pins the gate predicates)
// with cross-layer integration: how the chain's pq.Mode interacts
// with the signature registry's Config.LegacyClassicalEnabled flag
// to deliver "PQ default, classical opt-in" end to end.
//
// The five strict-PQ invariants pinned here:
//
// 1. ModeStrictPQ + classical-only envelope = ErrClassicalAuthForbidden.
// 2. ModeStrictPQ + PQ envelope = accept (PQ evidence is the trust root).
// 3. The default signature registry (NewPQNativeRegistry) cannot
// install a classical scheme without an explicit opt-in.
// 4. A registry with LegacyClassicalEnabled=true CAN install a
// classical scheme but the chain-mode gate still refuses the
// resulting classical envelope under strict-PQ — registry
// posture is independent of chain posture (decomplected).
// 5. The mode<->registry pairing recommended for each profile.
//
// Together these test that "classical is opt-in" holds at BOTH the
// registry layer (Config.LegacyClassicalEnabled) and the chain
// layer (pq.Mode). Either gate alone would be insufficient.
package warp
import (
"context"
"testing"
"github.com/luxfi/pq"
sig "github.com/luxfi/warp/crypto/signature"
"github.com/stretchr/testify/require"
)
// strictPQEnv builds an envelope WITHOUT MLDSACertSet — the
// canonical "classical-only" envelope the strict-PQ gate refuses.
func strictPQEnvClassicalOnly(t *testing.T) *EnvelopeV2 {
t.Helper()
return e2eFixture(t, false, false)
}
// strictPQEnvWithPQ builds an envelope WITH MLDSACertSet — the
// canonical "PQ-evidenced" envelope that verifies under every mode.
func strictPQEnvWithPQ(t *testing.T) *EnvelopeV2 {
t.Helper()
return e2eFixture(t, true, true)
}
// TestStrictPQ_ClassicalEnvelope_Refused pins invariant (1).
func TestStrictPQ_ClassicalEnvelope_Refused(t *testing.T) {
env := strictPQEnvClassicalOnly(t)
err := pq.ValidateMode(pq.ModeStrictPQ, env, nil)
require.ErrorIs(t, err, pq.ErrClassicalAuthForbidden,
"strict-PQ accepted classical-only envelope")
}
// TestStrictPQ_PQEnvelope_Accepted pins invariant (2).
func TestStrictPQ_PQEnvelope_Accepted(t *testing.T) {
env := strictPQEnvWithPQ(t)
err := pq.ValidateMode(pq.ModeStrictPQ, env, nil)
require.NoError(t, err, "strict-PQ refused PQ envelope")
}
// TestStrictPQ_DefaultRegistry_ClassicalOptInRequired pins
// invariant (3): the registry's PQ-native default refuses to
// install a classical scheme.
func TestStrictPQ_DefaultRegistry_ClassicalOptInRequired(t *testing.T) {
r := sig.NewPQNativeRegistry()
verifier := stubVerifier{scheme: sig.SchemeBLS}
signer := stubSigner{scheme: sig.SchemeBLS}
err := r.Register(sig.SchemeBLS, verifier, signer)
require.ErrorIs(t, err, sig.ErrClassicalRequiresOptIn,
"PQ-native registry admitted BLS without LegacyClassicalEnabled")
}
// TestStrictPQ_OptInRegistryStillRefusedAtModeGate pins invariant (4):
// even when the registry has classical opt-in enabled and successfully
// installs BLS, the chain-mode gate STILL refuses the resulting
// classical envelope under strict-PQ. Posture is decomplected:
// registry posture (which schemes are installable) is orthogonal to
// chain posture (which schemes the chain trusts).
func TestStrictPQ_OptInRegistryStillRefusedAtModeGate(t *testing.T) {
// Registry has classical opt-in.
r := sig.NewRegistryFromConfig(sig.Config{LegacyClassicalEnabled: true})
err := r.Register(sig.SchemeBLS, stubVerifier{scheme: sig.SchemeBLS}, stubSigner{scheme: sig.SchemeBLS})
require.NoError(t, err, "opt-in registry must admit BLS")
// Chain is strict-PQ. The envelope arrives with no MLDSACertSet.
env := strictPQEnvClassicalOnly(t)
gateErr := pq.ValidateMode(pq.ModeStrictPQ, env, nil)
require.ErrorIs(t, gateErr, pq.ErrClassicalAuthForbidden,
"strict-PQ chain accepted classical envelope even though registry has opt-in")
}
// TestStrictPQ_RecommendedPairings pins invariant (5): the
// canonical pairings of pq.Mode and Config.LegacyClassicalEnabled.
//
// mode | LegacyClassicalEnabled | notes
// -------------|------------------------|-----------------------------
// classical | true | legacy chain; PQ unused
// hybrid | true | migration middle; both lanes
// strict-pq | false | canonical Liquid default
//
// strict-pq with LegacyClassicalEnabled=true is REDUNDANT but not
// incorrect — the registry still installs BLS, but the mode gate
// refuses classical envelopes anyway. The test asserts the gate
// outcome for each pairing on the same envelope set.
func TestStrictPQ_RecommendedPairings(t *testing.T) {
type pairing struct {
mode pq.Mode
legacy bool
envHasPQ bool
want error // pq.ErrClassicalAuthForbidden, or nil
}
cases := []pairing{
// Canonical Liquid strict-PQ default.
{pq.ModeStrictPQ, false, true, nil},
{pq.ModeStrictPQ, false, false, pq.ErrClassicalAuthForbidden},
// Strict-PQ with redundant opt-in.
{pq.ModeStrictPQ, true, true, nil},
{pq.ModeStrictPQ, true, false, pq.ErrClassicalAuthForbidden},
// Hybrid: accepts both.
{pq.ModeHybrid, true, true, nil},
{pq.ModeHybrid, true, false, nil},
// Classical: ignores PQ evidence (still accepts both at the gate).
{pq.ModeClassical, true, true, nil},
{pq.ModeClassical, true, false, nil},
}
for _, tc := range cases {
t.Run(tc.mode.String(), func(t *testing.T) {
r := sig.NewRegistryFromConfig(sig.Config{LegacyClassicalEnabled: tc.legacy})
// The registry's gate is independent of the mode's gate;
// we just confirm it accepts/refuses BLS install per the flag.
err := r.Register(sig.SchemeBLS,
stubVerifier{scheme: sig.SchemeBLS},
stubSigner{scheme: sig.SchemeBLS})
if tc.legacy {
require.NoError(t, err, "legacy=true must admit BLS install")
} else {
require.ErrorIs(t, err, sig.ErrClassicalRequiresOptIn,
"legacy=false must refuse BLS install")
}
env := e2eFixture(t, tc.envHasPQ, tc.envHasPQ)
gateErr := pq.ValidateMode(tc.mode, env, nil)
if tc.want == nil {
require.NoError(t, gateErr,
"mode=%s legacy=%t hasPQ=%t: unexpected gate error",
tc.mode, tc.legacy, tc.envHasPQ)
} else {
require.ErrorIs(t, gateErr, tc.want,
"mode=%s legacy=%t hasPQ=%t: gate error mismatch",
tc.mode, tc.legacy, tc.envHasPQ)
}
})
}
}
// TestStrictPQ_LegacyClassicalFlag_Documented confirms the wire-stable
// names of the opt-in flag. Renaming Config.LegacyClassicalEnabled
// would break every operator-facing chain config.
func TestStrictPQ_LegacyClassicalFlag_Documented(t *testing.T) {
cfg := sig.Config{LegacyClassicalEnabled: true}
require.True(t, cfg.LegacyClassicalEnabled,
"field name 'LegacyClassicalEnabled' must remain stable")
}
// TestStrictPQ_DefaultIsPQAware confirms the runtime check used by
// audit pipelines: a chain pinned strict-PQ reports IsPostQuantum=true
// AND IsPQAware=true. Other modes split these.
func TestStrictPQ_DefaultIsPQAware(t *testing.T) {
require.True(t, pq.ModeStrictPQ.IsPostQuantum(), "strict-PQ must be IsPostQuantum")
require.True(t, pq.ModeStrictPQ.IsPQAware(), "strict-PQ must be IsPQAware")
require.False(t, pq.ModeHybrid.IsPostQuantum(), "hybrid must NOT be IsPostQuantum")
require.True(t, pq.ModeHybrid.IsPQAware(), "hybrid must be IsPQAware")
require.False(t, pq.ModeClassical.IsPostQuantum(), "classical must NOT be IsPostQuantum")
require.False(t, pq.ModeClassical.IsPQAware(), "classical must NOT be IsPQAware")
}
// stubVerifier / stubSigner are the minimal Verifier / Signer types
// used by the strict-PQ tests. Mirrors fakeRegVerifier in
// cross_chain_envelope_e2e_test.go but kept local so that file is
// the single source-of-truth for posture e2e plumbing.
type stubVerifier struct{ scheme sig.Scheme }
func (s stubVerifier) Scheme() sig.Scheme { return s.scheme }
func (s stubVerifier) Verify(_ context.Context, _ []byte, _ sig.Signature, _ sig.SignerSet) error {
return nil
}
func (s stubVerifier) VerifyAggregate(_ context.Context, _ []byte, _ sig.Signature, _ sig.SignerSet) error {
return nil
}
type stubSigner struct{ scheme sig.Scheme }
func (s stubSigner) Scheme() sig.Scheme { return s.scheme }
func (s stubSigner) Sign(_ context.Context, _ []byte, _ sig.PrivateKey) (sig.Signature, error) {
return nil, nil
}
func (s stubSigner) AggregateSign(_ context.Context, _ []byte, _ []sig.PrivateKey) (sig.Signature, error) {
return nil, nil
}