Files
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

88 lines
3.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package warp
import (
"errors"
"github.com/luxfi/ids"
"golang.org/x/crypto/sha3"
)
// codec.go — the Warp ZAP profile: the domain-specific constants and
// signing-domain construction layered on top of the generic canonical
// TLV mechanism in zap.go. There is exactly ONE codec (ZAP) and exactly
// ONE signing digest (D). No RLP, no codec version, no type registry.
// Kind discriminators. zapKindMessage is the first byte of a Message
// c14n stream; kindEnvelope is the envelope kind byte that follows the
// wire magic. They are distinct so a Message can never be mistaken for
// a full envelope and vice-versa.
const (
zapKindMessage byte = 0x01
kindEnvelope byte = 0x02
)
// Size limits. MaxMessageSize bounds a Message's canonical encoding;
// MaxEnvelopeSize bounds the full envelope (message + Beam + the two PQ
// lanes). Both are hard ceilings checked at the decode boundary.
const (
// MaxMessageSize is the maximum canonical Message size.
MaxMessageSize = 256 * KiB
// MaxEnvelopeSize is the maximum Envelope wire size. Bounded at
// 4×MaxMessageSize to leave room for the Pulse (~33 KB) and the
// ML-DSA cert set alongside the message and Beam.
MaxEnvelopeSize = 4 * MaxMessageSize
)
// Cross-cutting errors. Envelope-shape errors live in envelope.go.
var (
ErrInvalidSignature = errors.New("invalid signature")
ErrInvalidMessage = errors.New("invalid message")
ErrUnknownValidator = errors.New("unknown validator")
ErrInsufficientWeight = errors.New("insufficient weight")
)
// Domain-separation tags. D = keccak256(messageDST ‖ zap_c14n(Message))
// is the single signed digest (the "Prism" transcript): message ID,
// replay key, and on-chain messageHash all at once. Each lane signs the
// SAME D under its OWN tag, so a signature in one lane can never be
// replayed into another (BLS objects vs lattice objects are already
// non-interchangeable; the distinct tags close the door regardless).
const (
messageDST = "LUX-WARP-ZAP-CORE-v1"
beamDST = "LUX-WARP-ZAP-BEAM-v1"
coronaDST = "LUX-WARP-ZAP-CORONA-v1"
mldsaDST = "LUX-WARP-ZAP-MLDSA-v1"
)
// keccak256 is Ethereum's keccak256 — golang.org/x/crypto/sha3
// NewLegacyKeccak256 (Keccak padding 0x01), NOT NIST SHA3 (pad 0x06) and
// NOT crypto/sha256. The on-chain keccak256 opcode computes exactly this,
// so the digest D matches byte-for-byte between Go and Solidity.
func keccak256(parts ...[]byte) [32]byte {
h := sha3.NewLegacyKeccak256()
for _, p := range parts {
_, _ = h.Write(p)
}
var out [32]byte
copy(out[:], h.Sum(nil))
return out
}
// BeamSigningBytes returns the exact bytes the BLS Beam lane signs and
// verifies: beamDST ‖ D. The Beam now authenticates the full Message
// (including PQ lineage) rather than only the unsigned message body.
func BeamSigningBytes(d ids.ID) []byte { return append([]byte(beamDST), d[:]...) }
// CoronaSigningBytes returns the exact bytes the Corona Ringtail lattice
// threshold lane signs and verifies: coronaDST ‖ D. (This lane was formerly
// mislabeled "Pulse"; it is the corona kernel's Module-LWE signature.)
func CoronaSigningBytes(d ids.ID) []byte { return append([]byte(coronaDST), d[:]...) }
// MLDSASigningBytes returns the exact bytes the ML-DSA cert-set lane signs
// and verifies: mldsaDST ‖ D.
func MLDSASigningBytes(d ids.ID) []byte { return append([]byte(mldsaDST), d[:]...) }