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.
117 lines
3.6 KiB
Go
117 lines
3.6 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// envelope_negative_test.go — transcript-binding tests over the Message
|
|
// fields the digest D commits to. Under the ZAP fork the signed subject is
|
|
// D = keccak256("LUX-WARP-ZAP-CORE-v1" ‖ zap_c14n(Message)); folding the
|
|
// PQ lineage into the message means every lane (BLS Beam included) binds it.
|
|
// For each transcript field we mutate the message and assert:
|
|
//
|
|
// 1. D changes.
|
|
// 2. An honest verifier (D-equality model) rejects the mutated message.
|
|
// 3. No two single-field mutations collide on D (orthogonality).
|
|
|
|
package warp
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/luxfi/ids"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func negMessageFixture(t *testing.T) *Message {
|
|
t.Helper()
|
|
return &Message{
|
|
NetworkID: 1,
|
|
SourceChainID: ids.ID{0xA1, 0xA2, 0xA3, 0xA4},
|
|
SourceNebulaRoot: [32]byte{0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE},
|
|
SourceKeyEraID: 7,
|
|
SourceGeneration: 11,
|
|
HashSuiteID: MessageHashProfileTag,
|
|
Payload: []byte("envelope-negative-test-payload"),
|
|
}
|
|
}
|
|
|
|
// honestVerifier models a real lane verifier under an unchanged key: it
|
|
// accepts iff the candidate message's D equals the baseline's.
|
|
func honestVerifier(baseline *Message) func(c *Message) bool {
|
|
d := baseline.ID()
|
|
return func(c *Message) bool { return c.ID() == d }
|
|
}
|
|
|
|
func negMutateField(t *testing.T, base *Message, field string) *Message {
|
|
t.Helper()
|
|
c := *base
|
|
switch field {
|
|
case "network_id":
|
|
c.NetworkID = base.NetworkID + 1000
|
|
case "source_chain_id":
|
|
c.SourceChainID = ids.ID{0xCA, 0xFE, 0xCA, 0xFE}
|
|
case "nebula_root":
|
|
c.SourceNebulaRoot = [32]byte{0x99, 0x88, 0x77, 0x66}
|
|
case "key_era_id":
|
|
c.SourceKeyEraID = base.SourceKeyEraID + 1000
|
|
case "generation":
|
|
c.SourceGeneration = base.SourceGeneration + 1000
|
|
case "hash_suite_id":
|
|
c.HashSuiteID = "Pulsar-BLAKE3"
|
|
case "payload":
|
|
c.Payload = append([]byte("X"), base.Payload...)
|
|
default:
|
|
t.Fatalf("unknown message field: %q", field)
|
|
}
|
|
return &c
|
|
}
|
|
|
|
var negFields = []string{
|
|
"network_id",
|
|
"source_chain_id",
|
|
"nebula_root",
|
|
"key_era_id",
|
|
"generation",
|
|
"hash_suite_id",
|
|
"payload",
|
|
}
|
|
|
|
// TestMessageTranscriptMutationsRejected mutates each field and asserts D
|
|
// changes and the honest verifier rejects.
|
|
func TestMessageTranscriptMutationsRejected(t *testing.T) {
|
|
base := negMessageFixture(t)
|
|
baseID := base.ID()
|
|
verify := honestVerifier(base)
|
|
require.True(t, verify(base), "baseline must verify")
|
|
|
|
for _, f := range negFields {
|
|
t.Run(f, func(t *testing.T) {
|
|
mutated := negMutateField(t, base, f)
|
|
require.NotEqual(t, baseID, mutated.ID(), "mutation of %q did not change D", f)
|
|
require.False(t, verify(mutated), "honest verifier accepted mutated %q", f)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestMessageTranscriptMutationsDistinct is the orthogonality check: no two
|
|
// single-field mutations collide on D.
|
|
func TestMessageTranscriptMutationsDistinct(t *testing.T) {
|
|
base := negMessageFixture(t)
|
|
seen := make(map[ids.ID]string, len(negFields))
|
|
for _, f := range negFields {
|
|
id := negMutateField(t, base, f).ID()
|
|
if prev, ok := seen[id]; ok {
|
|
t.Fatalf("D collision: %q and %q produce the same digest", prev, f)
|
|
}
|
|
seen[id] = f
|
|
}
|
|
}
|
|
|
|
// TestHashSuiteExplicitVsEmptyBindDistinctly proves an explicit non-default
|
|
// suite produces a different D from the resolved-default message — the suite
|
|
// is bound verbatim, no sign-time defaulting.
|
|
func TestHashSuiteExplicitVsEmptyBindDistinctly(t *testing.T) {
|
|
base := negMessageFixture(t)
|
|
cp := *base
|
|
cp.HashSuiteID = "Pulsar-BLAKE3"
|
|
require.NotEqual(t, base.ID(), cp.ID())
|
|
}
|