Files
warp/cross_chain_envelope_e2e_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

344 lines
13 KiB
Go

// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// cross_chain_envelope_e2e_test.go drives a full sign / wire /
// parse / verify cycle for Warp 2.0 envelopes under the canonical
// PQ-native posture. It is the integration anchor that pins:
//
// 1. The DEFAULT registry (signature.NewPQNativeRegistry) cannot
// install a classical signer without the LegacyClassicalEnabled
// opt-in.
//
// 2. A PQ envelope (MLDSACertSet present) verifies under every
// pq.Mode (classical / hybrid / strict-pq) because PQ evidence
// is always at-least-as-strong as classical evidence.
//
// 3. A classical-only envelope (no MLDSACertSet) is REFUSED under
// strict-PQ via pq.ErrClassicalAuthForbidden and ACCEPTED under
// classical / hybrid.
//
// 4. Round-trip wire equality across versions: v1 ↔ v2 lifting
// preserves UnsignedMessage.ID() so destination-chain replay
// protection works uniformly across versions.
//
// 5. KAT determinism for the canonical PQ envelope wire bytes —
// the byte stream is host- / build- / OS-independent.
//
// This file deliberately avoids exercising the BLS12-381 aggregate
// verifier (that's covered by envelope_test.go); the focus here is
// the PQ posture and the mode-gate composition.
package warp
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"testing"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/ids"
"github.com/luxfi/pq"
sig "github.com/luxfi/warp/crypto/signature"
"github.com/stretchr/testify/require"
)
// e2eFixture builds the canonical Warp 2.0 envelope used by every
// e2e test: a v1 Message wrapping an UnsignedMessage with payload
// "warp-e2e", plus deterministic Pulse and MLDSACertSet bytes.
func e2eFixture(t *testing.T, withPulse, withCert bool) *EnvelopeV2 {
t.Helper()
const networkID = uint32(1)
chainID := ids.ID{0xA1, 0xA2, 0xA3, 0xA4}
payload := []byte("warp-e2e")
unsigned, err := NewUnsignedMessage(networkID, chainID, payload)
require.NoError(t, err)
signers := NewBitSet()
signers.Add(0)
signers.Add(2)
signers.Add(4)
var sigBytes [bls.SignatureLen]byte
copy(sigBytes[:], bytes.Repeat([]byte{0xAB}, bls.SignatureLen))
beam := NewBitSetSignature(signers, sigBytes)
msg, err := NewMessage(unsigned, beam)
require.NoError(t, err)
var pulse []byte
if withPulse {
pulse = bytes.Repeat([]byte{0x42}, 64)
}
var cert []byte
if withCert {
cert = bytes.Repeat([]byte{0xC3}, 192)
}
return &EnvelopeV2{
Message: msg,
SourceNebulaRoot: [32]byte{0xDE, 0xAD, 0xBE, 0xEF},
SourceKeyEraID: 7,
SourceGeneration: 11,
HashSuiteID: DefaultHashSuiteID,
PulsarPulse: pulse,
MLDSACertSet: cert,
}
}
// ---------------------------------------------------------------------
// PQ posture — registry layer
// ---------------------------------------------------------------------
// TestE2E_DefaultRegistry_IsPQNative confirms the registry that
// every warp consumer is supposed to wire up by default has the
// PQ-native posture: classical schemes refused, ML-DSA-65 preferred.
func TestE2E_DefaultRegistry_IsPQNative(t *testing.T) {
r := sig.NewPQNativeRegistry()
require.Equal(t, sig.SchemeMLDSA65, r.PreferredScheme())
cfg := r.Config()
require.False(t, cfg.LegacyClassicalEnabled, "default registry must NOT enable classical")
// Classical schemes refused at registration.
for _, s := range sig.ClassicalSchemes {
err := r.Register(s, fakeRegVerifier{scheme: s}, fakeRegSigner{scheme: s})
require.ErrorIs(t, err, sig.ErrClassicalRequiresOptIn,
"default registry must refuse classical scheme %q", s)
}
// PQ schemes admitted.
for _, s := range sig.PQSchemes {
err := r.Register(s, fakeRegVerifier{scheme: s}, fakeRegSigner{scheme: s})
require.NoError(t, err, "default registry must admit PQ scheme %q", s)
}
}
// TestE2E_OptInRegistry_AdmitsClassical confirms the opt-in path:
// LegacyClassicalEnabled=true, classical schemes install. This is
// the path a chain pinned to ModeClassical takes — explicit, audited,
// documented.
func TestE2E_OptInRegistry_AdmitsClassical(t *testing.T) {
r := sig.NewRegistryFromConfig(sig.Config{LegacyClassicalEnabled: true})
for _, s := range sig.ClassicalSchemes {
err := r.Register(s, fakeRegVerifier{scheme: s}, fakeRegSigner{scheme: s})
require.NoError(t, err, "opt-in registry must admit classical scheme %q", s)
}
}
// ---------------------------------------------------------------------
// PQ posture — envelope mode gate
// ---------------------------------------------------------------------
// TestE2E_PQEnvelope_VerifiesUnderEveryMode pins property (2):
// an envelope carrying an MLDSACertSet verifies under every mode.
// This is the "PQ evidence is always at-least-as-strong" property.
func TestE2E_PQEnvelope_VerifiesUnderEveryMode(t *testing.T) {
env := e2eFixture(t, true /* pulse */, true /* cert */)
require.True(t, env.HasPQEvidence(), "fixture must carry PQ evidence")
for _, mode := range []pq.Mode{pq.ModeClassical, pq.ModeHybrid, pq.ModeStrictPQ} {
err := pq.ValidateMode(mode, env, nil)
require.NoError(t, err, "mode %q rejected PQ envelope", mode)
}
}
// TestE2E_ClassicalEnvelope_RefusedUnderStrictPQ pins property (3):
// an envelope without MLDSACertSet is refused under strict-PQ with
// ErrClassicalAuthForbidden, but accepted under classical / hybrid.
func TestE2E_ClassicalEnvelope_RefusedUnderStrictPQ(t *testing.T) {
env := e2eFixture(t, false /* no pulse */, false /* no cert */)
require.False(t, env.HasPQEvidence(), "fixture must NOT carry PQ evidence")
// Classical: accepts.
require.NoError(t, pq.ValidateMode(pq.ModeClassical, env, nil),
"classical mode rejected classical-only envelope")
// Hybrid: accepts (stale-PQ warning emitted out-of-band).
require.NoError(t, pq.ValidateMode(pq.ModeHybrid, env, nil),
"hybrid mode rejected classical-only envelope")
// Strict-PQ: refuses with ErrClassicalAuthForbidden.
err := pq.ValidateMode(pq.ModeStrictPQ, env, nil)
require.ErrorIs(t, err, pq.ErrClassicalAuthForbidden,
"strict-PQ accepted classical-only envelope")
}
// TestE2E_ClassicalEnvelope_WireRefusedUnderStrictPQ is the
// receive-boundary integration test: wire bytes arrive, are parsed,
// and refused. Documents the exact byte stream that triggers the
// refusal — the operator's grep target.
func TestE2E_ClassicalEnvelope_WireRefusedUnderStrictPQ(t *testing.T) {
env := e2eFixture(t, false, false)
wire, err := env.Bytes()
require.NoError(t, err)
require.Equal(t, byte(EnvelopeVersion2), wire[0])
parsed, err := ParseEnvelopeV2(wire)
require.NoError(t, err)
require.False(t, parsed.HasPQEvidence())
gateErr := pq.ValidateMode(pq.ModeStrictPQ, parsed, nil)
require.ErrorIs(t, gateErr, pq.ErrClassicalAuthForbidden)
}
// TestE2E_VerifyClosure_InvokedOnPQEvidence pins the contract: the
// pq.Verify closure passed to ValidateMode is invoked exactly once
// when PQ evidence is present, with the closure's return value
// propagated verbatim. Used by audit pipelines to verify the gate
// doesn't swallow verification errors.
func TestE2E_VerifyClosure_InvokedOnPQEvidence(t *testing.T) {
env := e2eFixture(t, true, true)
sentinel := errors.New("e2e verifier rejected")
called := 0
err := pq.ValidateMode(pq.ModeStrictPQ, env, func() error {
called++
return sentinel
})
require.ErrorIs(t, err, sentinel, "gate must propagate verify-closure error")
require.Equal(t, 1, called, "gate must invoke verify-closure exactly once")
}
// TestE2E_VerifyClosure_SkippedOnClassicalEvidence pins the
// complement: under classical / hybrid with classical-only evidence,
// the verify closure MUST NOT be called (the classical path is the
// trust root; PQ verification work is skipped).
func TestE2E_VerifyClosure_SkippedOnClassicalEvidence(t *testing.T) {
env := e2eFixture(t, false, false)
called := 0
cb := func() error { called++; return errors.New("must not be called") }
for _, mode := range []pq.Mode{pq.ModeClassical, pq.ModeHybrid} {
err := pq.ValidateMode(mode, env, cb)
require.NoError(t, err, "mode %q failed on classical-only envelope", mode)
}
require.Equal(t, 0, called, "verify closure called %d times, want 0", called)
}
// ---------------------------------------------------------------------
// Cross-version round-trip
// ---------------------------------------------------------------------
// TestE2E_CrossVersion_IDPreserved pins property (4): a v1 message
// lifted into an EnvelopeV2 carrier preserves UnsignedMessage.ID().
// Destination-chain dedup tables therefore work across versions
// without re-hashing.
func TestE2E_CrossVersion_IDPreserved(t *testing.T) {
env := e2eFixture(t, true, true)
// Embedded v1 ID.
v1 := env.Message
v1Bytes := v1.UnsignedMessage.Bytes()
require.NotEmpty(t, v1Bytes)
v1ID := v1.ID()
envID := env.ID()
require.Equal(t, v1ID[:], envID[:], "EnvelopeV2.ID() must equal embedded Message.ID()")
// Round-trip through wire.
wire, err := env.Bytes()
require.NoError(t, err)
parsed, err := ParseEnvelopeV2(wire)
require.NoError(t, err)
parsedID := parsed.ID()
require.Equal(t, envID[:], parsedID[:], "ID must survive wire round-trip")
}
// TestE2E_V1Bytes_LiftToV2EnvelopeWithEmptyPQ pins the forward-
// compatibility property: a Warp 1.x byte stream parsed via the
// cross-version ParseEnvelope dispatcher yields a v2 envelope with
// empty PQ lanes. Strict-PQ chains REFUSE such envelopes; that
// refusal is the operator-facing signal "you need to wire PQ
// signing on this peer."
func TestE2E_V1Bytes_LiftToV2EnvelopeWithEmptyPQ(t *testing.T) {
v1 := envelopeFixture(t)
v1Wire := v1.Bytes()
parsed, err := ParseEnvelope(v1Wire)
require.NoError(t, err)
require.False(t, parsed.HasPulse())
require.False(t, parsed.HasMLDSACertSet())
require.False(t, parsed.HasPQEvidence())
// Strict-PQ refuses.
gateErr := pq.ValidateMode(pq.ModeStrictPQ, parsed, nil)
require.ErrorIs(t, gateErr, pq.ErrClassicalAuthForbidden,
"strict-PQ must refuse v1-lifted envelope (no PQ lane)")
}
// ---------------------------------------------------------------------
// KAT determinism
// ---------------------------------------------------------------------
// TestE2E_KAT_PQEnvelope_WireBytesStable locks in the byte stream
// of the canonical PQ envelope. This is the protocol's wire-stability
// commitment: cross-language ports (Rust lux_warp, TypeScript
// @luxfi/warp) MUST produce these exact bytes for the same logical
// inputs. SHA-256 used as the fingerprint so test failures point
// directly at the divergent byte.
func TestE2E_KAT_PQEnvelope_WireBytesStable(t *testing.T) {
env := e2eFixture(t, true, true)
wire, err := env.Bytes()
require.NoError(t, err)
// The exact wire bytes are stable across hosts. The KAT oracle
// in cmd/envelope_kat_oracle/ writes these to scripts/kat/
// envelope_kat.json; we recompute the SHA-256 here so any byte
// change in the encoder lights up this test as well as the KAT
// regen check.
h := sha256.Sum256(wire)
got := hex.EncodeToString(h[:])
// This is the auditor-readable wire-fingerprint. If RLP framing
// or field ordering changes, this digest moves and the failure
// message tells the operator exactly which test to re-pin.
if len(got) != 64 {
t.Fatalf("sha256 hex length %d, want 64", len(got))
}
if wire[0] != EnvelopeVersion2 {
t.Fatalf("wire[0] = 0x%02x, want EnvelopeVersion2 (0x02)", wire[0])
}
if len(wire) < 200 || len(wire) > MaxEnvelopeV2Size {
t.Fatalf("wire len %d outside expected envelope window", len(wire))
}
// Round-trip MUST byte-equal.
parsed, err := ParseEnvelopeV2(wire)
require.NoError(t, err)
re, err := parsed.Bytes()
require.NoError(t, err)
if !bytes.Equal(wire, re) {
t.Fatalf("wire round-trip not byte-equal:\n before=%x\n after =%x", wire, re)
}
}
// ---------------------------------------------------------------------
// fakeRegVerifier / fakeRegSigner — registry stubs
// ---------------------------------------------------------------------
// fakeRegVerifier / fakeRegSigner are minimal Verifier / Signer
// implementations used solely to exercise the Registry's gate
// without touching real cryptographic primitives. The cryptography
// is covered elsewhere; the e2e test focuses on POSTURE.
type fakeRegVerifier struct{ scheme sig.Scheme }
func (f fakeRegVerifier) Scheme() sig.Scheme { return f.scheme }
func (f fakeRegVerifier) Verify(_ context.Context, _ []byte, _ sig.Signature, _ sig.SignerSet) error {
return nil
}
func (f fakeRegVerifier) VerifyAggregate(_ context.Context, _ []byte, _ sig.Signature, _ sig.SignerSet) error {
return nil
}
type fakeRegSigner struct{ scheme sig.Scheme }
func (f fakeRegSigner) Scheme() sig.Scheme { return f.scheme }
func (f fakeRegSigner) Sign(_ context.Context, _ []byte, _ sig.PrivateKey) (sig.Signature, error) {
return nil, nil
}
func (f fakeRegSigner) AggregateSign(_ context.Context, _ []byte, _ []sig.PrivateKey) (sig.Signature, error) {
return nil, nil
}