mirror of
https://github.com/luxfi/warp.git
synced 2026-10-30 15:13:04 +00:00
Decomplect the PQ finality lanes into a typed, impossible-to-confuse model where
KIND chooses the verifier and SUITE only chooses parameterization (fail-closed on
mismatch). Kills the prior conflation where the corona-backed lattice lane was
mislabeled "Pulse/Pulsar-SHA3".
Four strict evidence kinds — each its own verifier + suite + key registry:
beam-bls BLS12-381 aggregate (fast classical)
pulsar-threshold-mldsa threshold ML-DSA, verified as a STANDARD FIPS-204
ML-DSA signature under a group public key; the TALUS
dealerless machinery is entirely offline (VerifyPulsar
imports only mldsa65 — enforced by an import-hygiene test)
corona-ringtail Ringtail/Module-LWE lattice threshold (the corona kernel)
p3q-mldsa-rollup succinct rollup of independent ML-DSA-65 sigs; strict
ONLY when its proof system is PQ (classical SNARK gated)
Plus the raw mldsa-cert-set as a NON-strict availability/audit artifact.
Subject-agnostic verifiers take (subject, evidence, keyEra): one dispatch serves
the warp cross-chain digest D and the consensus finality subject
M = keccak256("QUASAR_FINALITY_V1" || chainID || height || round || blockID ||
stateRoot || signerSetID || keyEraID || generation || pChainHeight || policyID)
folding generation + pChainHeight + policyID so a signature binds its era, its
validator snapshot, and its tier (tier-downgrade replay fails closed).
Policy tiers (BLS_FAST / HYBRID_PQ_CHECKPOINT / STRICT_QUASAR / RECOVERY) select
which lanes are required — orthogonal to verification and to keys. PulsarKeyEra
and CoronaKeyEra are symmetric but DISTINCT registry types; P3Q/cert-set resolve a
per-validator SignerSetAuthority — no resolver can alias another's key material.
Offline signers (pulsard, coronad, P3Q prover) are interfaces only, never imported
by the verify path.
Wire unchanged: D, the Message c14n, messageDST, the ZAP magic and kind bytes are
byte-identical (forge Go<->Solidity equality holds; golden envelope SHA unchanged).
The corona lane DST is renamed pulseDST->coronaDST (no deployed corona sigs;
envelope KAT regenerated).
22 tests green: non-confusable dispatch, suite/kind fail-closed, Pulsar import
hygiene, subject-identity across lanes, policyID downgrade, key-era distinctness,
P3Q-classical-root strict gate.
114 lines
3.0 KiB
Go
114 lines
3.0 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Warp envelope fuzz harness.
|
|
//
|
|
// Properties under fuzzing:
|
|
//
|
|
// 1. ParseEnvelope on arbitrary bytes never panics — malformed input
|
|
// produces a clean error, never a runtime panic.
|
|
// 2. A successfully parsed envelope passes Verify and re-encodes
|
|
// byte-equally (canonical round-trip).
|
|
// 3. Legacy RLP / v2 lead bytes are rejected at the magic.
|
|
|
|
package warp
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"github.com/luxfi/crypto/bls"
|
|
"github.com/luxfi/ids"
|
|
)
|
|
|
|
func FuzzEnvelope(f *testing.F) {
|
|
f.Add(makeFuzzSeed(7, 11, true, true))
|
|
f.Add(makeFuzzSeed(0, 0, false, false))
|
|
f.Add([]byte{})
|
|
f.Add(wireMagic[:])
|
|
f.Add(append(wireMagic[:], kindEnvelope))
|
|
f.Add([]byte{0x05, 0x00, 0x00})
|
|
f.Add(append(wireMagic[:], 0xFF))
|
|
|
|
f.Fuzz(func(t *testing.T, raw []byte) {
|
|
// Property 1: never panics.
|
|
env, err := ParseEnvelope(raw)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// Property 2: structurally valid + canonical round-trip.
|
|
if err := env.Verify(); err != nil {
|
|
t.Fatalf("parsed envelope fails Verify: %v", err)
|
|
}
|
|
out, err := env.Bytes()
|
|
if err != nil {
|
|
t.Fatalf("re-encode failed: %v", err)
|
|
}
|
|
// A canonical parse of arbitrary bytes must re-encode to those
|
|
// exact bytes (the decoder accepts only canonical form).
|
|
if !bytes.Equal(raw, out) {
|
|
t.Fatalf("canonical round-trip not byte-equal:\n in =%x\n out=%x", raw, out)
|
|
}
|
|
env2, err := ParseEnvelope(out)
|
|
if err != nil {
|
|
t.Fatalf("re-parse failed: %v", err)
|
|
}
|
|
if !env.Equal(env2) {
|
|
t.Fatalf("round-trip envelopes not equal")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestFuzzCorpus_EnvelopeReplay re-runs the seed corpus deterministically.
|
|
func TestFuzzCorpus_EnvelopeReplay(t *testing.T) {
|
|
for i, s := range [][]byte{
|
|
makeFuzzSeed(7, 11, true, true),
|
|
makeFuzzSeed(0, 0, false, false),
|
|
} {
|
|
env, err := ParseEnvelope(s)
|
|
if err != nil {
|
|
t.Fatalf("seed %d: parse failed: %v", i, err)
|
|
}
|
|
if err := env.Verify(); err != nil {
|
|
t.Fatalf("seed %d: verify failed: %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// makeFuzzSeed builds a valid envelope wire stream with the given lineage
|
|
// and optional PQ lanes.
|
|
func makeFuzzSeed(eraID, generation uint64, withPulse, withCertSet bool) []byte {
|
|
message := &Message{
|
|
NetworkID: 1,
|
|
SourceChainID: ids.ID{0xA1, 0xA2},
|
|
SourceNebulaRoot: [32]byte{0xDE, 0xAD},
|
|
SourceKeyEraID: eraID,
|
|
SourceGeneration: generation,
|
|
HashSuiteID: MessageHashProfileTag,
|
|
Payload: []byte("envelope-fuzz-payload"),
|
|
}
|
|
signers := NewBitSet()
|
|
signers.Add(0)
|
|
signers.Add(2)
|
|
var sig [bls.SignatureLen]byte
|
|
copy(sig[:], bytes.Repeat([]byte{0xAB}, bls.SignatureLen))
|
|
|
|
var pulse []byte
|
|
if withPulse {
|
|
pulse = bytes.Repeat([]byte{0x42}, 64)
|
|
}
|
|
var cert []byte
|
|
if withCertSet {
|
|
cert = bytes.Repeat([]byte{0xC3}, 192)
|
|
}
|
|
env, err := NewEnvelope(message, NewBitSetSignature(signers, sig), pulse, cert)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
out, err := env.Bytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return out
|
|
}
|