Files
warp/security_profile.go
zeekay 443aa53f81 feat(warp)!: typed finality-evidence model (Beam/Pulsar/Corona/P3Q) + Corona relabel
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.
2026-06-27 13:47:13 -07:00

86 lines
3.3 KiB
Go

// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// security_profile.go — strict-PQ adapter for the Warp envelope.
//
// A Envelope ships three signature lanes side-by-side:
//
// 1. Classical BLS Beam over BLS12-381 (pairing-based,
// Shor-vulnerable, the fast common-case verification path).
// 2. Corona Ringtail — Module-LWE lattice THRESHOLD signature over the
// same transcript (the corona kernel; formerly mislabeled "Pulse").
// 3. ML-DSA cert set — FIPS 204 ML-DSA-65 attestations from the
// signing validators (optional today, REQUIRED under strict-PQ).
//
// This file plugs Envelope into the canonical pq.Mode gate:
//
// profile, err := pq.ModeFromString(chainCfg.WarpProfile)
// if err := pq.ValidateMode(profile, env, verifyFn); err != nil {
// // strict-PQ refused a classical-only envelope, or the
// // PQ verification returned a non-nil error
// }
//
// Warp's post-quantum AUTHORITY is the Corona Ringtail (Module-LWE THRESHOLD)
// lane — one O(1) aggregate over the transcript that scales to any validator
// set. The ML-DSA cert-set is an O(n) fallback (a per-signer attestation set,
// ~3.3 KB each: feasible for tiny sets, infeasible on-chain past ~hundreds of
// vdrs). So strict-PQ is satisfied by the Corona threshold ALONE; the cert-set
// is belt-and-suspenders, never the mandate. When neither PQ lane is present,
// strict-PQ refuses with pq.ErrClassicalAuthForbidden and hybrid falls back to
// the classical BLS Beam.
//
// (The Pulsar threshold ML-DSA lane is a separate, reserved, fail-closed
// evidence kind — see evidence.go — not the corona lane below.)
package warp
import "github.com/luxfi/pq"
// HasPQEvidence implements pq.PQEvidencer. Either PQ lane counts — the Corona
// Ringtail threshold is primary and O(1); the ML-DSA cert-set is the O(n)
// fallback. Repointing this from HasMLDSACertSet to HasCorona is load-bearing:
// without it, strict-PQ wrongly mandated the non-scaling cert-set and refused a
// Corona-only (the common, scalable) envelope.
func (e *Envelope) HasPQEvidence() bool {
return e.HasCorona() || e.HasMLDSACertSet()
}
// VerificationLane reports which lane(s) the verifier MUST
// validate for a given mode + envelope. Bitwise mask:
//
// - LaneClassical: BLS Beam.
// - LanePQ: the PQ lane(s) — Corona Ringtail threshold primary,
// ML-DSA cert-set fallback.
//
// Routing rules:
//
// classical → LaneClassical
// hybrid + PQ evidence present → LanePQ
// hybrid + no PQ evidence → LaneClassical (stale-PQ warning)
// strict-pq → LanePQ (gate refuses without PQ evidence)
type VerificationLane int
const (
LaneClassical VerificationLane = 1 << iota
LanePQ
)
// LanesForMode returns the bitwise set of lanes the verifier MUST validate.
// Pass env.HasPQEvidence() as hasPQEvidence for the hybrid fall-through (true
// when EITHER the Pulse threshold or the ML-DSA cert-set is present).
func LanesForMode(mode pq.Mode, hasPQEvidence bool) VerificationLane {
switch mode {
case pq.ModeStrictPQ:
return LanePQ
case pq.ModeHybrid:
if hasPQEvidence {
return LanePQ
}
return LaneClassical
case pq.ModeClassical:
return LaneClassical
default:
return LaneClassical
}
}