Red proved H3's VerifyUnderPolicy was INERT: the live wire-level verifier
(QuantumPolicy.VerifyWithKeys -> QuasarCert.VerifyWithRealKeys) used the
implied-policy path (BLS-permissive), and the policy-driven path had ZERO
live callers. consensus/engine/pq (PostQuantum.SetCertPolicy) was dead —
nothing imported it.
Task 3 — make the LIVE path policy-driven from the SINGLE source of truth:
- config: ChainSecurityProfile.CertPolicy() — the one mapping from the
chain's security profile to its cert posture. strict-PQ/FIPS -> Strict
variant (BLS NOT a required leg) + Mode=PQ-strict (RequiredLegs =
{Pulsar, Corona}); other profiles -> Hybrid (BLS retained alongside PQ).
- pkg/wire: QuantumPolicy carries that CertPolicy. New VerifyCertUnderPolicy
is THE live verifier — routes through QuasarCert.VerifyUnderPolicy with
the chain's policy + full CertKeys. VerifyWithKeys is now a thin adapter
over it. NewQuantumPolicyForProfile derives the policy from the profile.
Leg presence is policy-driven (VerifyUnderPolicy Gate 1), never
cert-byte-driven: a CRQC that forges BLS and strips the PQ legs is
rejected.
- Deleted consensus/engine/pq (10 files, zero importers — the whole package
was dead, not just SetCertPolicy). The live verify is now the wire path.
Test (d): on a strict-PQ chain, the LIVE verifier rejects a BLS-only
(PQ-legs-stripped) cert even with a valid BLS key supplied; the policy is
Strict-variant (BLS not required, Corona required); the permissive default
is Hybrid (BLS required) — proving the switch is profile-driven, not
hardcoded. (The per-leg crypto half is covered in quasar/
cert_policy_verify_test.go.)
The QuasarCert verifier made BLS mandatory and PQ legs optional/
cert-byte-driven: a CRQC that broke BLS-12-381 could ship a BLS-only
cert (omit every PQ leg) and verify true, while a legitimate pure-PQ
(no-BLS) cert was wrongly rejected. config.CertPolicy.RequiredLegs()
(the canonical leg-set authority) had zero production callers.
Wire it in:
- New cert_policy_verify.go: VerifyUnderPolicy(message, CertPolicy, CertKeys)
is the canonical verifier. For every leg in RequiredLegs() it rejects
unless the leg is BOTH present in the cert AND backed by a supplied key,
BEFORE any signature math — leg presence is policy-driven, not
attacker-controlled cert bytes. BLS is required iff the policy includes
it (Variant=Hybrid); a Strict-variant policy yields a pure-PQ required
set so a no-BLS cert verifies. Identity rollup is required under any PQ
policy (downgrade-resistant), present-implies-verify otherwise.
- Decomplect leg crypto into verify{BLS,Corona,Magnetar,MLDSARollup}Leg
helpers; VerifyWithRealKeys / VerifyWithRealKeysPolaris reimplemented on
them with the BLS-mandatory bug fixed (BLS required iff key supplied;
pure-PQ allowed; each PQ leg required iff its key supplied — an adversary
cannot strip a leg the verifier was configured to check).
- Migrate callers to policy intent: engine/pq gains SetCertPolicy +
policy-driven verify path; pkg/wire QuantumPolicy enforces requireRT
(Corona key mandatory) when set.
Tests (real crypto, no stubs): leg-removal negatives for each required
leg reject; missing-key-for-required-leg rejects; pure-PQ cert verifies
under Strict-variant policy AND via VerifyWithRealKeysPolaris(nil BLS);
Red PoC (BLS-only cert + PQ keys configured) now rejected. Existing
ComposeAllThreeSchemes / cert roundtrip / CertBundle suites unchanged.
Pre-existing unrelated failure TestVerifyBLSAggregate_C1_ActualDeserialization
(witness_test.go) fails identically on origin — out of scope.
Rename the Z-Chain parallel-witness rollup proof system from
classical Groth16/bn254 (pairing-based, quantum-breakable) to the
post-quantum STARK/FRI path (P3Q: Plonky3 fork, cSHAKE256 Merkle over
Goldilocks, FRI; no KZG, no pairings, no trusted setup).
This is a comment/identifier rename only: QuasarCert.MLDSARollup is
opaque bytes and the consensus module has no proof-system dependency,
so the cert composition + ZWitnessProducer interface are unchanged.
The verifier and prover live in chains/zkvm.
- witness_producer.go: ZWitnessProducer doc + MLDSAGroth16->MLDSAStark;
DisabledZWitnessProducer doc (STARK prover, AIR constraint count).
- types.go / polaris.go: MLDSARollup leg doc 'STARK/Groth16'->
'strict-PQ STARK/FRI (P3Q)'; wire-layout comment.
- engine/pq/consensus.go: FinalityEvent.MLDSARollup doc.
- pkg/wire/candidate.go: PolicyQuantum/PolicyPZ/WitnessZ docs.
QuasarCert composition gate test (TestQuasarCert_ComposesAllThreeSchemes)
still passes. No wire-format change.
Replaces every direct import of luxfi/corona/* and luxfi/pulsar/ref/go
in consensus with the canonical luxfi/threshold/protocols/{corona,pulsar}
alias surface. Pure import swap + go.mod bump; no behaviour change.
Files touched (16):
bench/pq_modes_bench_test.go
engine/pq/crypto.go
engine/pq/engine.go
pkg/wire/policies.go
protocol/quasar/cert_size_compare_test.go
protocol/quasar/corona_gob.go
protocol/quasar/dual_threshold_test.go
protocol/quasar/epoch.go
protocol/quasar/epoch_test.go
protocol/quasar/gpu_accel.go
protocol/quasar/grouped_threshold.go
protocol/quasar/quasar.go
protocol/quasar/types.go
protocol/quasar/wave_signer.go
protocol/quasar/wave_signer_test.go
protocol/quasar/witness.go
protocol/quasar/witness_pulsar_test.go
Import path swaps:
luxfi/corona/threshold -> luxfi/threshold/protocols/corona
luxfi/corona/keyera -> luxfi/threshold/protocols/corona/keyera
luxfi/corona/primitives -> luxfi/threshold/protocols/corona/primitives
luxfi/corona/reshare -> luxfi/threshold/protocols/corona/reshare
luxfi/corona/sign -> luxfi/threshold/protocols/corona/sign
luxfi/corona/gpu -> luxfi/threshold/protocols/corona/gpu
luxfi/pulsar/ref/go/pkg/pulsar -> luxfi/threshold/protocols/pulsar
The aliased package name "coronaThreshold" used by quasar / engine/pq /
pkg/wire identifiers is preserved at the call sites (it now refers to
the threshold/protocols/corona alias package). engine/pq/crypto.go is
the only file where the package was bare-imported as `threshold`; that
identifier was renamed to `corona` to match the new alias path.
go.mod:
- luxfi/threshold bumped to a branch pseudo-version that carries the
new alias subpackages; will pin to a stable tag once threshold's
companion commit lands and a release is cut.
- luxfi/corona and luxfi/pulsar drop to indirect (consensus now
reaches them only via threshold/protocols).
- luxfi/math v1.4.0 -> v1.4.1 and luxfi/metric v1.5.0 -> v1.5.7 are
transitive minimum-version bumps pulled by the new threshold
requirement; they are no-op for consensus call sites.
Comment-only references to "github.com/luxfi/corona/..." in bls.go,
round_digest.go, reshare_epoch.go, comprehensive_test.go,
engine_test.go, quasar_bench_test.go and config/pq_mode.go are
documentation pointers to where the underlying implementation lives;
they are accurate either way and are left unchanged (out of refactor
scope).
Layering shape after:
consensus -> threshold/protocols/{corona,pulsar} (stable alias)
|
v
luxfi/corona, luxfi/pulsar (primitive implementations)
Closes the 22-file layering violation flagged by the 2026-05-31
integration audit. Tests pass: ./engine/pq/..., ./pkg/wire/...,
./protocol/quasar/... (all key cases — TestEpochManager*,
TestRoundSigner*, TestVerifyPQFinality*, TestBLSThreshold*,
TestDualSigningFlow, TestCoronaCertSize* — green; the pre-existing
TestGroupedEpochManager_ParallelKeygen perf-bound flake on slow
machines was reproduced on unmodified main, so it is not a
regression).
Consumer-side rename to follow the luxfi/pulsar package drop of the
-M suffix. All 25 touched files migrate the import path
github.com/luxfi/pulsar/ref/go/pkg/pulsarm → .../pulsar and the
in-file symbol references pulsarm.* → pulsar.* (plus the local
witness_pulsarm_test.go filename → witness_pulsar_test.go).
Build: go build ./... PASS.
Test: full ./... sweep — 28 packages PASS (protocol/quasar 15.9s,
engine 10.7s, config 0.9s, pkg/wire 1.5s).
Followup to the QuasarCert field rename (commit d1c8a2cc). This sweeps
the remaining Corona identifiers and prose out of the consensus repo
entirely:
- Identifiers (Go code):
coronaThreshold → coronaThreshold (import alias)
coronaGroupKey → coronaGroupKey
coronaSigners → coronaSigners
coronaShares → coronaShares
coronaGobEncode → coronaGobEncode
coronaGobDecode → coronaGobDecode
decodeCoronaSig → decodeCoronaSig
EncodeCoronaSig → EncodeCoronaSig
CoronaRound1{,Data,State} → CoronaRound1{,Data,State}
CoronaGroupKey/Pub/Shares → Corona*
- File rename:
protocol/quasar/corona_gob.go → corona_gob.go
- PQMode constants:
PQModeCorona → PQModeNasua
SigSchemeCoronaAcademic → SigSchemeNasua
"corona" / "corona-academic" string forms → "nasua"
- Prose & comments throughout the repo: "Corona" → "Corona" when
naming the production Ring-LWE library, "Nasua" when naming the
historical academic upstream (luxfi/nasua, formerly luxfi/corona).
Canonical Pulsar family taxonomy after this rename:
Nasua — academic R-LWE 2-round threshold sig (Boschini et al,
ePrint 2024/1113); trusted-dealer DKG; kept as historical
reference fork only. NOT for an open public chain.
Corona — production Ring-LWE threshold ML-DSA (Pedersen DKG over R_q
+ proactive resharing); `github.com/luxfi/corona`. The
Ring-LWE leg of QuasarCert.
Pulsar — production Module-LWE threshold ML-DSA (FIPS 204
byte-equal); `github.com/luxfi/pulsar`. The Module-LWE
leg of QuasarCert. Pure-PQ pair = Corona + Pulsar.
Wire-format unchanged. Tests: 28/28 packages green (-short suite,
quasar 29s, integration 58s, no regressions).
The Quasar cert layers are the right cardinality of "Double Lattice +
Identity Rollup + optional Classical Fast-path", not "Triple Quantum":
BLS-12-381 classical fast-path (pairing) — empty in pure-PQ
Corona Ring-LWE threshold ML-DSA (luxfi/corona; production R-LWE)
Pulsar Module-LWE threshold ML-DSA (luxfi/pulsar; Class N1 byte-equal
to FIPS 204)
MLDSARollup per-validator ML-DSA-65 rolled into one STARK / Groth16 proof
(Z-Chain → P3Q)
"Triple Quantum" misled because BLS is classical — at most one of three
legs was quantum in the historic BLS+Corona+ZK shape. The honest PQ
cardinality is **Double Lattice** (Ring + Module). Pure-PQ profiles
drop BLS entirely and rely on Corona + Pulsar.
Corona is the academic R-LWE construction with trusted-dealer DKG;
it stays out of any open public-chain path. Corona is the production
Ring-LWE library (Pedersen DKG over R_q + proactive resharing).
Surface (in protocol/quasar/types.go):
QuasarCert.BLS → unchanged (classical fast-path, optional)
QuasarCert.Corona → QuasarCert.Corona (Ring-LWE threshold sig)
QuasarCert.MLDSAProof → QuasarCert.MLDSARollup (succinct identity rollup)
QuasarCert now also has a Pulsar field (Module-LWE threshold sig,
populated when wired)
QuasarSig.Corona → QuasarSig.Corona (also added Pulsar []byte)
FinalityEvent.Corona / MLDSAProof → Corona / Pulsar / MLDSARollup
New predicates:
QuasarCert.IsDoubleLattice() — Corona ∧ Pulsar (honest PQ count)
QuasarCert.HasClassicalFastPath() — len(BLS) > 0
QuasarCert.HasIdentityRollup() — len(MLDSARollup) > 0
The wire byte layout is unchanged (length-prefixed slots). Callers that
read the bytes don't notice. Source-level references that compiled
before the rename now compile against the new identifiers; bulk
rename swept the consensus repo (22 files, 219 insertions, 151
deletions) plus the engine/pq facing FinalityEvent.
Tests: GOWORK=off go test -short -count=1 ./... → all 28 packages green
(quasar suite 32s; integration 60s; no regressions).
PQ mode is binary. The profile registry is collapsed to three
canonical bundles:
0x01 ProfileStrictPQ ("STRICT") — production PQ chains
0x02 ProfilePermissive ("PERMISSIVE") — testnet / devnet
0x03 ProfileFIPS ("FIPS") — FIPS-aligned superset
Removed pseudo-profiles: HybridProfile, ClassicalProfile, BLSProfile,
ZooStrictPQProfile, HanzoStrictPQProfile, QuasarStrictPQProfile,
AnnulusStrictPQProfile. Chains that need classical primitives pin
ForkClassicalCompatUnsafeProfile (0x80) at the byte layer and own
the audit consequence.
(*ChainSecurityProfile).IsPQ() is the single canonical predicate.
Profile string surface tracks the byte registry exactly: "strict",
"permissive", "fips".
Cross-brand scrub: dropped "Hanzo mesh" doc-comments in pq_mode.go
+ pq_mode_test.go + witness_floor_test.go in favor of "Lux mesh".
Dropped Lux/Zoo/Hanzo enumeration in account_id.go. account_id_test.go
uses fork byte (0x80) instead of zoo/hanzo profile IDs. qblock.go
references LP-0079 instead of cross-org HIP path.