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.
120 lines
4.3 KiB
Go
120 lines
4.3 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package pulsar — message-tag policy + D-binding tests for Quasar envelope
|
|
// verification. NOTE: the message-level HashSuiteID tag (here "Pulsar-SHA3")
|
|
// is the GENERIC c14n tag, decoupled from the Corona lane suite (the resolver
|
|
// returns warp.DefaultCoronaSuiteID).
|
|
//
|
|
// 1. A corona signature over a "Pulsar-SHA3"-tagged message verifies when
|
|
// the receiver's message-tag policy also expects "Pulsar-SHA3".
|
|
// 2. The same envelope, verified while the receiver's message-tag policy
|
|
// expects "Pulsar-BLAKE3", is rejected with ErrEnvelopeBadSuiteID.
|
|
// 3. A mutated HashSuiteID (tampered after signing, corona sig byte-identical)
|
|
// MUST reject — D folds HashSuiteID, so the corona sig over the original D
|
|
// no longer verifies under the mutated D.
|
|
package pulsar
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/luxfi/warp"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestVerifyAcceptsMatchingPulsarSHA3(t *testing.T) {
|
|
env := envFixture(t, 7, 11)
|
|
env.Message.HashSuiteID = warp.MessageHashProfileTag
|
|
signing := warp.CoronaSigningBytes(env.Message.ID())
|
|
|
|
sig, gk := runPulsarCeremony(t, 3, 2, string(signing))
|
|
pulse, err := SerializeCoronaSig(sig)
|
|
require.NoError(t, err)
|
|
env.CoronaSig = pulse
|
|
|
|
verifier := NewRingtailVerifier(&stubResolver{gk: gk, suiteID: string(warp.DefaultCoronaSuiteID)})
|
|
opts := warp.VerifyOptions{
|
|
SkipBeam: true,
|
|
Corona: verifier,
|
|
RequireCorona: true,
|
|
HashSuiteID: warp.MessageHashProfileTag,
|
|
}
|
|
require.NoError(t, warp.VerifyWithOptions(env, opts))
|
|
}
|
|
|
|
func TestVerifyRejectsExpectedSuiteMismatch(t *testing.T) {
|
|
env := envFixture(t, 7, 11)
|
|
env.Message.HashSuiteID = warp.MessageHashProfileTag
|
|
signing := warp.CoronaSigningBytes(env.Message.ID())
|
|
|
|
sig, gk := runPulsarCeremony(t, 3, 2, string(signing))
|
|
pulse, err := SerializeCoronaSig(sig)
|
|
require.NoError(t, err)
|
|
env.CoronaSig = pulse
|
|
|
|
verifier := NewRingtailVerifier(&stubResolver{gk: gk, suiteID: string(warp.DefaultCoronaSuiteID)})
|
|
opts := warp.VerifyOptions{
|
|
SkipBeam: true,
|
|
Corona: verifier,
|
|
RequireCorona: true,
|
|
HashSuiteID: "Pulsar-BLAKE3", // receiver policy disagrees with the honest envelope
|
|
}
|
|
require.ErrorIs(t, warp.VerifyWithOptions(env, opts), warp.ErrEnvelopeBadSuiteID)
|
|
}
|
|
|
|
func TestVerifyRejectsMutatedHashSuiteIDPostSign(t *testing.T) {
|
|
env := envFixture(t, 7, 11)
|
|
env.Message.HashSuiteID = warp.MessageHashProfileTag
|
|
signing := warp.CoronaSigningBytes(env.Message.ID())
|
|
|
|
sig, gk := runPulsarCeremony(t, 3, 2, string(signing))
|
|
pulse, err := SerializeCoronaSig(sig)
|
|
require.NoError(t, err)
|
|
env.CoronaSig = pulse
|
|
|
|
// Round-trip through the wire, then mutate HashSuiteID and re-serialise
|
|
// so the Pulse bytes are unchanged but the declared profile is swapped.
|
|
wire, err := env.Bytes()
|
|
require.NoError(t, err)
|
|
parsed, err := warp.ParseEnvelope(wire)
|
|
require.NoError(t, err)
|
|
|
|
parsed.Message.HashSuiteID = "Pulsar-BLAKE3"
|
|
mutatedWire, err := parsed.Bytes()
|
|
require.NoError(t, err)
|
|
mutated, err := warp.ParseEnvelope(mutatedWire)
|
|
require.NoError(t, err)
|
|
|
|
// Receiver's message-tag policy expects "Pulsar-BLAKE3" (matching the
|
|
// mutated envelope tag) and the corona resolver returns the Corona
|
|
// suite, so BOTH the message-tag and corona-suite checks pass. The
|
|
// kernel still rejects because D bound the original "Pulsar-SHA3" tag.
|
|
verifier := NewRingtailVerifier(&stubResolver{gk: gk, suiteID: string(warp.DefaultCoronaSuiteID)})
|
|
opts := warp.VerifyOptions{
|
|
SkipBeam: true,
|
|
Corona: verifier,
|
|
RequireCorona: true,
|
|
HashSuiteID: "Pulsar-BLAKE3",
|
|
}
|
|
err = warp.VerifyWithOptions(mutated, opts)
|
|
require.Error(t, err)
|
|
require.ErrorIs(t, err, ErrCoronaVerifyFailed)
|
|
}
|
|
|
|
func TestRingtailVerifierRejectsHashSuiteFieldMutation(t *testing.T) {
|
|
env := envFixture(t, 7, 11)
|
|
env.Message.HashSuiteID = warp.MessageHashProfileTag
|
|
signing := warp.CoronaSigningBytes(env.Message.ID())
|
|
|
|
sig, gk := runPulsarCeremony(t, 3, 2, string(signing))
|
|
pulse, err := SerializeCoronaSig(sig)
|
|
require.NoError(t, err)
|
|
env.CoronaSig = pulse
|
|
|
|
// Mutate HashSuiteID after signing; Pulse stays unchanged.
|
|
env.Message.HashSuiteID = "Pulsar-BLAKE3"
|
|
|
|
verifier := NewRingtailVerifier(&stubResolver{gk: gk, suiteID: string(warp.DefaultCoronaSuiteID)})
|
|
require.ErrorIs(t, verifier.VerifyRingtailThreshold(coronaInputs(env)), ErrCoronaVerifyFailed)
|
|
}
|