AggregateBCCWithBlame consumed raw []Partial; PartyID was unauthenticated, so an
attacker could stamp a victim's slot on a forged partial and (via first-per-
PartyID + duplicate rules) get the honest victim BLAMED and front-run-EXCLUDED.
No forge/leak (capstone holds), but blame misattributed.
Fix: bind each partial to its producer's long-term identity key.
- Partial gains Author + AuthSig; Round2 signs (author ‖ slot ‖ H(PartyID,
session,nonce,ZShare,Proof)) via an injected IdentitySigner (SetIdentity).
- AggregateBCCWithBlame gains (quorum, epoch, AbortSignatureVerifier). When a
verifier is supplied it AUTHENTICATES each partial BEFORE attribution: accept
only if AuthSig verifies AND Author == quorum[PartyID]; drop forged/wrong-slot/
unsigned partials with NO blame against the slot's honest owner, BEFORE the
first-per-PartyID/duplicate logic (so a forgery cannot evict the victim).
- Blame is emitted ONLY for authenticated partials and NEVER off a raw PartyID
(auth==nil => no blame produced — attribution fails closed). Wraps
protocol_auth.SignedProtocolMessage (one identity layer). Networked transport
auth stays a flagged residual.
GATE B updated to authenticated partials (a deviator SIGNS its bad partial =>
attributable; a transport-tampered one breaks its sig => dropped, not framed).
New TestRED_PoC_MEDIUM_CannotFrameOrExcludeHonestVictim: forged victim-slot
partial is dropped (attacker can't sign as victim) -> victim NOT blamed, NOT
excluded, round still signs; contrast shows the verifier is load-bearing.
The dedup key was H(committeeID‖w1). The same joint nonce (same w1) used in two
committees that share a victim validator produced DISTINCT keys, so the victim's
ledger did not dedup across committees -> victim emits a partial in each -> the
attacker collects two partials on one nonce.
Fix: key on w1 alone. This is sound precisely BECAUSE the ledger is now
per-validator-share (resolved by shareIdentityKey): within the victim's one
ledger the same w1 collapses to ONE key regardless of committee. committeeID
stays on the NonceTicket/binding for audit and on TicketID; only the MaterialKey
drops it.
GATE: TestRED_LOW_CrossCommittee_SameNonce_Deduped — two distinct committees
sharing a victim, same w1 -> equal MaterialKey -> second reserve ErrNonceReused.
NewDistributedBCCSigner gave each instance its OWN empty in-memory ledger;
since a signer fixes (sid,ctx,msg) at construction, signing two messages needs
two signer objects -> two empty ledgers -> cross-message nonce reuse slipped
unless the integrator hand-threaded ONE ledger via SetNonceLedger. The
reference runBCCCeremony never did, so the shipped default was fail-open and
RED recovered the victim's full s1-share on the bare API.
Fix: resolve the single-use ledger from a process-global registry keyed by
SHARE IDENTITY (shareIdentityKey = H(mode,nodeID,evalPoint,secret-share)).
Every signer over the same key-share resolves to ONE ledger -> cross-instance
reuse refused by DEFAULT, no per-instance empty-ledger path. SetNonceLedger now
installs a persistent ledger into the same per-share slot (first-writer-wins);
the in-process default is safe NOW, persistence stays a flagged residual.
GATE A extended: TestRED_PoC_DefaultLedger_NonceReuse_Refused drives the REAL
API like runBCCCeremony (fresh signer per message, NO SetNonceLedger) and
asserts the second Round2 on the reused nonce + the relabel variant both return
ErrNonceReused -> RED's PoC flips, s1 is unrecoverable.
Replace the in-house v0.1 RECONSTRUCT DKG (dkg.go DKGSession.Round3 -> KeyFromSeed,
the centralized seam) with the dealerless Pedersen-VSS DKG via
github.com/luxfi/dkg/vss, over ring.MLDSA65().WithMatrices(ExpandA(rho)) for the
chain-genesis binding. DealerlessDKGViaVSS produces the (*AlgSetup, []*AlgShare)
shape the TALUS signer consumes; NO party ever forms the seed sigma, s1, or sk
(group key = aggregated PUBLIC Pedersen commit T = A*s1 + B*u).
Proven:
- shape + ExpandA(rho) binding + non-trivial sharing + threshold privacy
(TestDKG_NoReconstruct_ViaVSS);
- AST scan: no reconstruct/Lagrange/KeyFromSeed primitive defined or called in
the DKG path (TestDKG_NoReconstruct_SourceStructural);
- the HONEST obstruction (HANDOFF-PHASE3 §67-79), PROVEN not asserted: the vss
key's s2 = B*u is LARGE, so HighBits(A*s1) differs from group-key t1 in
1534/1536 coeffs -> the BCC FindHint cannot reach t1, no stock FIPS-204 sig is
producible against this key. The no-reconstruct DKG is the ROOT; stock-
verifiable signing keeps the trusted-dealer key (TestDKG_VSS_NotDirectlyBCCSignable).
ML-DSA-65 only (luxfi/dkg ships ring.MLDSA65(); MLDSA87 profile is upstream TODO).
CEFComputeW1 now drives the malicious-secure shared luxfi/dkg cscp package
instead of pulsar's in-house semi-honest cscpSecureHighBitsVec. Byte-faithful
coefficient bridge (KAT-proven domain), ML-DSA-87 via K=8 profile shim over the
shared GF(q) ring. Stock FIPS-204 verify round-trip green for both 65 and 87.
(cherry picked from commit 9c2ca7202ab037f0ace55f49ed714b454a9c1d1e)
CEFComputeW1 now drives the malicious-secure shared luxfi/dkg cscp package
instead of pulsar's in-house semi-honest cscpSecureHighBitsVec. Byte-faithful
coefficient bridge (KAT-proven domain), ML-DSA-87 via K=8 profile shim over the
shared GF(q) ring. Stock FIPS-204 verify round-trip green for both 65 and 87.
Add github.com/luxfi/dkg v0.2.0 dependency and a foundation KAT proving the
shared dkg ring substrate, bound to pulsar's ExpandA(rho) via
ring.MLDSA65().WithMatrices, reproduces pulsar's own FIPS-204 arithmetic:
1. A-matrix binding round-trips faithfully through the dkg ring's
NTT-Montgomery domain (INTT(IMForm(dkgA)) == ExpandA(rho) coeff form),
catching any spurious Montgomery-R scaling (pin 4).
2. dkg ring INTT-only ConvertVecFromNTT recomputes t = A·s1 + s2 ->
Power2Round == pulsar's km.t1 (byte-identical to cloudflare/circl).
A extracted convention-neutrally via unit-vector multiply through pulsar's own
circl-correct keygen pipeline (no Montgomery guesswork). Foundation for
replacing the in-house reconstruct DKG with dkg/vss no-reconstruct VSS.
CAPSTONE (TestMalicious_BadBehaviorNeverForgesOrLeaks): every single-member
deviation on the no-reconstruct sign path => ABORT or attributable BLAME, NEVER a
FIPS-valid forged signature. Subtests: nonce-reuse refused; invalid-sigma
blamed+abort; duplicate cannot inflate threshold; malformed no-panic+blamed;
valid-sigma wrong-z is a liveness fault (no forgery). Honest baseline asserts an
honest quorum DOES sign (so the negatives are meaningful).
SELECTIVE-ABORT (TestMalicious_SelectiveAbort_ReformProducesValidSig): the
aggregator's Lagrange is over the EXACT supplied evalPoints set, so selective
abort is orchestrator RE-FORMATION of a fresh exact-threshold quorum excluding the
blamed party -> the re-formed clean quorum produces a FIPS-valid signature.
ORDER-INDEPENDENCE (TestMalicious_AggregationOrderIndependent): identical
signature bytes regardless of partial submission order (CanonicalSignerSet
deterministic) -> no last-mover grinding of subset/output/challenge.
go vet clean; no stdlib log; full suite green (6.2s).
- NonceTicket (unique TicketID = H(committeeID||w1||binding) audit handle) +
ReserveNonceTicket: the ONE nonce-consume path. Dedup keys on MATERIAL
(committeeID||w1), NOT TicketID, so relabel/different-message reuse of the same
joint nonce is still ErrNonceReused. Round2 refactored to reserve via the
ticket. Lifecycle test: unique ids, single-use, domain-bound, abort-doesn't-leak.
- protocol_auth.go: canonical to-be-signed encoding for protocol messages bound
to (epoch,session,nonce,round); pluggable identity verifier (reuses the
AbortSignatureVerifier shape, DRY); DetectEquivocation -> two validly-signed
conflicting same-slot messages from one author yield ComplaintEquivocation
evidence. Networked transport (authenticated channels) is the consensus-layer
residual; this is the crypto primitive + detector.
Full suite green.
RED GATE-2 bypass: the structural gate greps only distributed_bcc.go for
KeyFromSeed(/func LargeCombine, so a reconstruct-then-sign wired via
deriveKeyMaterial+bccSign elsewhere PASSES.
GATE C (gate2_reachability_test.go): stdlib go/ast name call-graph over the
default-build files. Bans {KeyFromSeed, deriveKeyMaterial, bccSign,
shamirReconstruct*, LargeCombine, DealAlgShares} from being REACHABLE from the
committee sign entrypoints (AggregateBCC[WithBlame], NewDistributedBCCSigner,
DistributedBCCSigner.{Round1,Round2,Finalize,FinalizeWithBlame,SetNonceShare}).
Callee side over-approximated by name -> sound bias (over-report, never miss).
Two tests: (1) the REAL package is clean (8 entrypoints, none reach a banned
primitive); (2) the checker CATCHES RED's deriveKeyMaterial+bccSign bypass, both
as a synthetic graph AND via the real parse->graph pipeline on rogue source;
plus a clean-path negative control (no false positive).
Item 4 (ci_build_invariant_test.go): the production (default-tag) build never
carries legacy_trusted_dealer -- asserts the tag gates real reconstruct files,
they are absent from the default build, LargeCombine is quarantined, no
default file requires the tag, and DealAlgShares (trusted-dealer keygen) is
defined ONLY in _test.go.
Full suite green.
AggregateBCC silently dropped malformed/duplicate/invalid partials (anonymous
DoS) and CanonicalSignerSet does not dedupe (a duplicate PartyID could satisfy
the threshold with < t DISTINCT signers); a short ZShare panicked the aggregator.
FIX:
- AggregateBCCWithBlame (canonical) attributes every detectable deviation to its
PartyID (PartialBlame: malformed / duplicate / unknown-party / session-mismatch
/ invalid-proof); AggregateBCC is the back-compat wrapper (RoundSigner.Finalize
+ GATE-2 reflection + existing tests intact). FinalizeWithBlame maps PartyID ->
NodeID -> signed-complaint AbortEvidence (ComplaintBadPartial kind, never
self-accuses).
- Duplicate PartyID rejected: first occurrence authoritative, later ones blamed;
duplicates can NOT inflate a sub-threshold set.
- unpackPolyVecChecked: malformed/truncated ZShare -> ErrWireLengthMismatch, no
panic (closes a malformed-share DoS).
- shareCommitmentsFor: ONE pure fn of public inputs called by BOTH Round2 and
AggregateBCC => the partial-z proof binds the SAME per-party DKG/nonce
commitments on prove + verify (symmetric, no globals). nil today; the single
BDLOP plug point.
DESIGN+SCAFFOLD+FLAG (share_commit.go, Residual A): sound valid-sigma WRONG-z
blame needs a HIDING lattice commitment (BDLOP/Ajtai under a SEPARATE matrix) to
the dealt (y_i,s1_i) + an extended linear-sigma of opening. Homomorphic A*s1_i /
A*y_i is REJECTED (reopens W-LEAK / HINT-LEAK). Until then a valid-sigma wrong-z
is a LIVENESS fault (no FIPS-valid signature, no forgery/leak), marked
ErrIdentifiableAbortResidual.
GATE B (blame_gate_test.go): B.1 duplicate attributed + cannot inflate threshold;
B.2 invalid-sigma attributed to exact PartyID as AbortEvidence; B.3 malformed
ZShare no panic + BlameMalformed; B.4 honest residual — valid-sigma wrong-z
yields no forgery and is the flagged BDLOP residual.
GATE-1/GATE-2 still green; full suite green (7.5s).
THE HIGH (RED TestRED_NonceReuse_RecoversS1): same joint nonce + 2 messages =>
public (z_A - z_B) = (c_A - c_B)*s1 solves for the WHOLE expanded key. UNGUARDED
on v0.4.0.
FIX (nonce_ledger.go + Round2 wiring):
- NonceLedger single-use store keyed on the nonce COMMITMENT
H(committeeID || w1) -- NOT the nonceID label, so a relabeled joint nonce
cannot bypass. Reserve() is fail-closed: ErrNonceReused on any second use,
never rolled back (an aborted attempt leaves no reusable nonce state).
- DistributedBCCSigner reserves the nonce in Round2 BEFORE touching the secret.
Per-validator ledger injectable via SetNonceLedger (one ledger per share across
signer instances catches cross-instance reuse); domain binding via
SetNonceBinding (epoch/policy/message_kind/digest, audit + tripwire).
GATE A (nonce_reuse_gate_test.go): (1) actually RECOVERS the full 5-poly s1 by
solving (c_A-c_B)*s1 = z_A-z_B over Z_q (negacyclic Gauss-Jordan) == dealer
secret bit-for-bit -- the vuln is real; (2) the real signer API refuses the
second partial (ErrNonceReused), incl. the relabel bypass -- the system can
never be assembled via honest signers.
GATE-1/GATE-2 still green; full suite green (7.2s).
Residual (flagged): in-memory ledger does not survive restart -> production must
inject a PERSISTENT ledger; dealer-side NonceMPC 'Consumed' marking is
complementary defense-in-depth (leak-free NonceMPC residual).
Red-verified (commit a0bd97e): AggregateBCC no-reconstruct signer is the only
production committee signer; LargeCombine + DealAlgShares quarantined out of the
default build; GATE-1 (circl mldsa65.Verify accepts) + GATE-2 (no KeyFromSeed/sk
in sign-combine) green. HONEST SCOPE: no-reconstruct foundation, standard-verifier
compatible; NOT yet real-key-safe (nonce single-use guard + malicious-secure CSCP
are the next layer, red HIGH). v0.3 algebraic-broadcast path was removed; v0.4 =
BCC/CSCP.
Make the committee threshold-SIGN combine genuinely no-reconstruct and remove
the centralized-trust paths from the production build.
- Quarantine the GF(q) SEED-share reconstruct family behind
//go:build legacy_trusted_dealer: large_threshold.go (LargeCombine, the
KeyFromSeed-at-sign H-1 footgun), large_dkg.go, large_reshare.go,
large_types.go, largeshamir.go (+ their tests). The production committee
sign path is now ONLY the no-reconstruct AlgShare/AggregateBCC RoundSigner
(distributed_bcc.go): each party emits z_i = λ_i·y_i + c·λ_i·s1_i from its
own share+nonce; the combiner Lagrange-sums to z and recovers the hint from
public w' = A·z − c·t1·2^d — never forming s1, the seed, or sk.
- Rip out the trusted s1-share dealer DealAlgShares (+ zeroizeKeyMaterial)
from distributed_bcc.go into bootstrap_dealer_test.go (a _test.go file,
uncompilable into any production binary; available to tests/bootstrap only).
- Add the two hard gates (no_reconstruct_committee_test.go):
GATE 1 (standard-verifier): a (t,n)=(4,7) committee no-reconstruct signature
verifies under independent cloudflare/circl mldsa65.Verify; tamper rejected.
GATE 2 (no-reconstruct invariant): structural — the production build has no
KeyFromSeed call in any sign-combine file and no LargeCombine; behavioural —
Partial and AggregateBCC carry no share/sk/seed, and t-1 partials cannot sign.
Residual (flagged, NOT claimed fixed): a dealerless s1-share KEYGEN is the
research-blocked Part-2 problem (naive_additive_seta_obstruction.go computes the
obstruction; Mithril short-replicated shares is the adoption target). Keygen-side
KeyFromSeed in dkg.go (group-pubkey derivation) is a separate keygen residual.
Honest claim: no-reconstruct committee threshold signing whose output verifies
under the standard ML-DSA verifier — NOT FIPS-204 KeyGen distribution-equivalence,
NOT FIPS-validated.
The obstruction file over-claimed: "a genuinely dealerless DKG ... is NOT
achievable with known techniques." That is too strong and contradicted by
published work. Corrected to the NARROW, true statement and renamed the file to
say what it actually proves.
- distributed_bcc_dkg.go -> naive_additive_seta_obstruction.go (+ test). The file
computes the obstruction to the NAIVE additive (Pedersen/Gennaro) lift: a summed
joint secret has ‖s2‖∞ ≤ Nη, breaking the BCC boundary-clearance hypothesis and
ML-DSA's S_η-calibrated EUF-CMA. That computed barrier is correct and unchanged
(assessDealerlessFIPS, errors.Is sentinel — both intact, test green).
- Removed the general-impossibility framing. Short replicated-share constructions
(Mithril, ia.cr/2026/013, USENIX Sec 2026) achieve dealerless byte-FIPS-204
threshold ML-DSA at small N — a known, adoptable technique, not yet implemented
here; cited as the candidate path.
- Fixed a re-introduced mislabel: Corona is Module-LWE (Ringtail/Raccoon), not
Ring-LWE (2 comment sites).
What stays true and proven (separate concern): Pulsar's no-leak BCC/CEF threshold
SIGNING produces a STANDARD ML-DSA-verifiable signature (TestBCCSignRoundTripVerifiesCIRCL,
independent circl verifier). The keygen is trusted-dealer; genuinely-dealerless
byte-FIPS keygen is the open item the corrected file scopes.
Comments + file rename only; no logic, no symbol rename. Package builds, test green.
Builds the CarryCompare (CSCP) secure-comparison MPC circuit that realises the
ideal cefIdealSecureHighBits with an ACTUAL secure comparison, so no node (and no
process) ever forms the aggregate low sum A0 = Sum a0_i, the low part
w0 = LowBits(w), or the full commitment w — even transiently. This is the closing
move for PULSAR-V13-W-LEAK on the TALUS-MPC path.
talus_cscp.go — the real circuit, composing from the bgwMulShares / SharedRandomBit
substrate (talus_mpc.go):
- cscpSecureHighBitsVec: additive commitment shares -> additive->Shamir reshare to
<w> over GF(q) -> secure HighBits -> opened w1 only. Parallel over the 256*K
independent coefficients; race-clean.
- secure HighBits via the exact boundary-count identity
w1 = (Sum_{k=1..16}[w > (2k-1)*gamma2]) mod 16 (m=16 power-of-two buckets for
ML-DSA-65/87), proven coefficient-exact against FIPS Decompose over ALL q
residues.
- bit-decomposition by mask-open (carry-save adder + bitwise prefix less-than for
the mod-q fold = the "CSA + prefix comparison" the obstruction names);
bitLTPubShared / bitLTSharedPub / bitAdd / bitSubBetaQ / randomBitwise gadgets.
- CSCPParticipant: per-node state holds ONLY its own commitment share (no joint
w/w0/A0 field). CSCPMaliciousResidual / AssessCSCPMalicious: the scoped,
orthogonal malicious-secure / identifiable-abort layer (TALUS Phase B).
- enforces N >= 2T-1 (TALUS Thm 10.1).
talus_cef.go — CEFComputeW1 now DRIVES the real circuit (cscpSecureHighBitsVec)
instead of the ideal; cefIdealSecureHighBits retained as the test ORACLE.
Proven: gadgets bottom-up (bitwise LT exhaustive over all 64x64 pairs; bit-decompose
reconstructs; secure HighBits == FIPS Decompose on boundary + random coeffs);
multi-node leak-free ceremony (separate per-node state + bus) with w1 == ground-truth
HighBits(A*ybar), transcript opening ONLY {validity bit, uniform mask-open, w1}
(otherCt==0), source-structural guard (reconstruct only inside open(), sanctioned
tags only), reflection (no joint field), perfect masking; real CSCP == ideal
F_HighBits, ML-DSA-65 and -87; a wrong w1 is caught downstream by FindHint + the
mandatory stock-FIPS release gate (liveness fault, never forgery/leak). The headline
TestTalus_MPC_EndToEnd_StockVerify / _Mode87 now run the REAL CSCP and the aggregated
signature still verifies under unmodified cloudflare/circl mldsa{65,87}.Verify.
27 TALUS+CSCP tests green; full pkg suite green; race-clean.
Residual (honest): the circuit is semi-honest honest-majority; the malicious-secure
identifiable-abort layer is scoped (CSCPMaliciousResidual), not faked. KEYGEN stays
trusted-dealer (item 2 obstruction); permissionless safety rests on the dealerless
Corona leg of the AND-mode dual-PQ cert.
Genuinely-dealerless DKG producing byte-FIPS-204 ML-DSA keys is NOT achievable
with known techniques. This commit COMPUTES why (mirroring rangeproof.go's
derive-don't-assert pattern) rather than faking a key or falling back to a
dealer/TEE.
THE OBSTRUCTION (precise): FIPS-204 draws s1,s2 from S_eta (||.||inf <= eta) and
calibrates beta=tau*eta, omega, and the BCC 2*beta boundary margin to that. A
dealerless DKG forms the joint secret as a sum/Lagrange-combination of N>=2
contributions => ||s2||inf <= N*eta, so ||c*s2||inf <= N*beta > beta, violating
the BCC boundary-clearance hypothesis ||c*s2||inf <= beta. assessDealerlessFIPS
computes N*beta vs beta and shows byte-validity breaks at N=2 for both ML-DSA-65
and -87; the deeper break is that a sum-of-contributions secret is not the
S_eta distribution ML-DSA's EUF-CMA assumes. Forcing the joint into S_eta needs
either a dealer or a norm-reduction that changes (A,t) => not byte-compatible.
NOT the obstruction: t0. The joint t0 = Power2Round-low(joint t) is in
(-2^(d-1), 2^(d-1)] by definition for ANY N, so ||c*t0||inf <= tau*2^(d-1) < gamma2
always. Pinned by TestDealerless_T0IsNotTheObstruction (200704/245760 < 261888).
CORONA/RACCOON noise-flooding assessed: corona.BootstrapPedersen is dealerless
because Ring-LWE is noise-flooded (beta_j = A*(lambda_j*s_j)+e_j', sigma=kappa*sigmaE*sqrt(n))
with a Round_Xi absorption step; ML-DSA has fixed S_eta bounds and no absorbing
rounding, so any flooded variant is a Raccoon-family scheme (Threshold Raccoon,
EUROCRYPT'24) that does NOT verify under FIPS-204. This is exactly why the
Quasar cert is AND-mode dual-PQ: dealerless Corona leg || dealer/TEE-genesis
Pulsar leg. DealerlessMLDSADKG fails closed (ErrDealerlessByteFIPSUnreachable),
never fakes, never falls back to dealer/TEE. 5 tests pin the arithmetic.
Concrete poly-vector (s1) AlgShare + DistributedBCCSigner closing the
PULSAR-V12-PARALLEL-PQ forward-path item 1: a no-reconstruct, single-share
t-of-n ML-DSA signer in the current RoundSigner/Partial/FlatAggregateZ model.
- AlgShare: GF(q) poly-vector Shamir share of the EXPANDED signing component
s1 (s1 only — the BCC/CEF path never touches s2/t0, so sharing them would
reopen PULSAR-V13-HINT-LEAK; matches the DKGPublicOutput no-t0 invariant).
- DistributedBCCSigner (satisfies RoundSigner): Round1 binds the canonical
NonceCert and derives c; Round2 emits z_i = lambda_i*y_i + c*lambda_i*s1_i
with the SOUND linear-sigma partial-z proof (partial_proof.go); Finalize
aggregates z=sum z_i Lagrange-linearly (FlatAggregateZ), recovers the hint
from PUBLIC w' via FindHint, emits FIPS-204 sigEncode. The aggregator holds
NO share/sk/seed (AggregateBCC takes []Partial only).
- PULSAR-V13-PARTIAL-Z-PROOF: REAL — sound Maurer/Schnorr sigma wired and
proven (honest+forged partials), forgeries dropped with a clean blame path.
- DealAlgShares: Part-1 TRUSTED-DEALER keygen (no-reconstruct at SIGN time,
not dealerless at keygen — that is Part 2). DealNonceMPCDebug is the
NonceMPC stand-in (reveals w to the harness = PULSAR-V13-W-LEAK gate,
fail-closed for production; signing decomposition is independent of it).
Multi-node proof (7 tests, all green): t separate signers one share each,
ceremony over a message bus, signatures verify under UNMODIFIED FIPS-204
(VerifyBytes + stock circl Verify), ML-DSA-65 and -87, ctx-binding real,
sub-quorum refused, share carries no seed/s2/t0, ShareCount==1 per node.
luxfi/consensus now composes a dual-lattice AND-mode finality cert that
carries a Pulsar (Module-LWE / FIPS-204 ML-DSA) leg in parallel with a Corona
(Ring-LWE) leg, both required to finalize. Record the consumer and pin the
Pulsar-side gap in one document:
- The Pulsar leg VERIFY path (wire.VerifyBytes, unmodified FIPS-204) is
production-ready and exercised live by the consensus multi-node test.
- The Pulsar leg's no-reconstruct single-share t-of-n SIGNING is not yet
production: the current KeyShare is a GF(257) seed-share and ML-DSA s1 is
non-linear in the seed, so seed-shares admit only reconstruct-then-sign.
A no-reconstruct signer needs poly-vector (s1,s2,t0) shares; the new
RoundSigner/Partial/FlatAggregateZ model carries the interface + z-sum
primitive but no concrete poly-vector share type or RoundSigner implementor
exists on the current line. It also stays fenced fail-closed behind
PULSAR-V13-W-LEAK (distributed boundary-clear nonce proof) and
PULSAR-V13-PARTIAL-Z-PROOF.
- Permissionless safety comes from the dealerless Corona leg in AND-mode;
Pulsar genesis is TEE-gated (PULSAR-V12-TEE-BIND / mldsa-tee).
- Forward path: finish the new model (poly-vector share + concrete
RoundSigner + close the V13 ZK gates) applying gate C's DistributedSigner
decomposition; do NOT resurrect the removed v0.3 stack (two-way / backwards).
The v0.1 GF(257) threshold-signing instantiation (ThresholdSigner /
Round1 / Round2 / Combine) Lagrange-reconstructed the master seed in
aggregator memory and called FIPS 204 SignTo once — i.e. a quorum's
full ML-DSA signing key materialised in a single process. That
reconstruct-then-sign ceremony was the H-1 footgun. Its only
production reach was consensus's quasar wave_signer (deleted), via the
threshold/protocols/pulsar alias (Combine re-export dropped in the
threshold repo).
The one and only threshold-signing path is now the v0.3 algebraic
ceremony (threshold_v03.go / orchestrate.go), whose
Lagrange-linearity-of-z aggregation produces the FIPS 204 signature
WITHOUT reconstructing the secret in any party's memory.
Removed:
- threshold.go: ThresholdSigner, NewThresholdSigner, Round1, Round2,
Combine (kept the shared transcript/committee/CT helpers + error set
consumed by the v0.3 and GF(q) paths; dropped now-unused crypto/rand
+ io imports and dead ErrNilSession).
- types.go: Round1Message, Round2Message wire types.
- round.go: dead RoundSigShare (was the wave_signer carrier).
- threshold_test.go, n1_byte_equality_test.go: pure-v0.1 suites.
- round_test.go / bench_test.go / precompile_e2e_test.go: excised the
v0.1 sign cases, kept RoundContext/ApproxRoundSecurity/DKG/Large/v0.3.
- cmd/genkat: dropped v0.1 threshold-sign KAT generation + helper.
- ct/dudect/combine_ct.go + dudect_combine.c: dead CT harness for the
removed Combine path.
- shared_dkg_test.go: relocated the non-v0.1 DKG test helpers
(runDKG / runDKGWithIdentities) that threshold_test.go had hosted.
Verified: GOWORK=off go build ./... exit 0; go vet clean;
go test ./ref/go/pkg/pulsar/ exit 0 (DKG, reshare, v0.3, KAT replay,
large-committee all green).
The headline EC byte-equality (pulsar_n1_byte_equality) proved the threshold
combine bit-equals SinglePartyRun, which RECONSTRUCTS the master secret and
signs with it -- exactly the path the production leaderless signer must never
take (PULSAR-V13-HINT-LEAK). This pass re-scopes that cone as IDEALISED
CORRECTNESS and adds the HONEST production model whose residual is a STANDARD
Module-LWE/MSIS reduction, not an implementation reconstruct.
New proofs/easycrypt/Pulsar_N1_NoLeak.ec (written; machine-recheck pending
EasyCrypt -- no `ec` on host, scripts/checks/ec-compile.sh is the CI gate):
- no_leak_z_aggregate: the public masked Lagrange aggregate equals central z
WITHOUT forming the master secret (re-exports the Lean-bridged
threshold_partial_response_identity).
- public_hint_roundtrip [B, Lean-backed]: the hint is recovered from the
PUBLIC w' = A*z - c*t1*2^d via FIPS UseHint; procedure lift of the
machine-checked Lean boundary_clearance + findHintCoeff_unique. No
c*s2/c*t0/r0 anywhere.
- no_leak_reduction [C-standard, OPEN]: under Module-LWE + Module-SIS the
public transcript leaks nothing about (s1,s2,t0) beyond one FIPS 204
signature. The honest replacement for combine_body_axiom -- a reduction to
the SAME lattice assumptions ML-DSA's EUF-CMA uses, secret NEVER formed.
The CORRECTNESS CORE is machine-checked in Lean 4 + Mathlib on this host
(luxfi/proofs: Crypto.Pulsar.NoLeakAggregate / .BoundaryClearance /
Threshold_Lagrange; lake build green, #print axioms shows no sorryAx).
Disclosure updated to match reality: AXIOM-INVENTORY (new Bucket C-standard +
"A is machine-checked in Lean"), PROOF-CLAIMS (two models), BLOCKERS
(PULSAR-EC-RECON-MODEL re-scoped, first criterion now [x]), budget AXIOM
87->89 (the 2 new disclosed standard-assumption axioms). Framework gate PASS
(all 5 checks); ref/go build + tests green.
Reclassify (not reduce) the EasyCrypt axiom surface and make the trust
cone of the N1 byte-equality theorem fully disclosed. NO axioms discharged
this pass (honest): no easycrypt toolchain on host to machine-recheck a
hand proof, and every in-file-derivable axiom is already a proved lemma;
the residual A/B axioms are over abstract types/ops and the C axioms are
open security assumptions that must stay open.
- AXIOM-INVENTORY.md (new): complete bucketed census of all 78 real axiom
declarations -- A standard-math-fact (16/17, 5 Lean-bridged), B
serialization/layout (41), C OPEN security assumption (21). One row per
axiom: name, file:line, bucket, justification. Notes the framework
axiom-budget regex over-counts (87) because 9 comment lines begin with
the word "axiom".
- PROOF-CLAIMS.md (new, root, gate-read): assurance vocabulary
(machine-checked / sound-by-reduction / interop-tested / asserted-axiom
/ fail-closed-pending-review / open-research) matched to reality. States
plainly that the EC byte-equality is machine-checked (0 admits) MODULO a
C-cone that models reconstruct-then-sign (combine_body_axiom /
S_functional_spec / *_body_*_spec assert the extracted code equals the
centralised signer on the Lagrange-reconstructed master secret) -- the
abstraction the production leaderless path must never use -- and that the
no-leak property is interop-tested (CIRCL + pq-crystals), not EC-proven.
- BLOCKERS.md: add PULSAR-EC-RECON-MODEL as an explicit OPEN item
cross-referenced to the V13 external-review gate; fix the contradictory
"no open items" header.
- .assurance/reviewed-axioms.txt: list every load-bearing / security-cone
axiom (A: 5 Lean-bridged + C: combine_body_axiom, S_functional_spec,
algebraic_aggregate_ctx_body_axiom) with bucket + justification -- full
disclosure, not a blanket allow.
- .assurance/budget.txt: correct the prose (true decl count 78 vs counted
87; 0 real admit. tactics); counts held, not raised.
Framework gate (circular-proof + axiom-budget + the other three) PASS with
full disclosure. go test ./... green (no Go touched).
Cert-verification surface (red-review fixes — none touch the proven BCC
arithmetic, all close fail-open/forgery seams):
1. ConsensusCert: bind the signer bitmap to the signature via an
accountability QuorumCert over a payload root committing bitmap +
transcript root + signature bytes; Verify() replaces the structural-
only VerifyStructure (bitmap rewrite / signature splice now rejected).
2. nonceCertPayloadRoot: bind Mode, WCommitment, ClearanceProof, Consumed
(length-prefixed) — closes swappable-commitment + replay seams.
3. VerifyNonceCert / ConsensusCert.Verify: fail-closed QuorumSigVerifier
seam (one registry for both cert types) — QC signatures are no longer
trusted unchecked; unregistered verifier => reject.
4. boundaryThreshold saturates at 0 and VerifyNonceCert/RunNonceMPCDebug
gate bccParams ok — ML-DSA-44 (vacuous boundary clearance) can no
longer underflow to "everything clear".
5. partial-z: reject a challenge c that is not a FIPS 204 SampleInBall
(tau ±1 coeffs) — a degenerate c=0 can no longer strip the s1 binding.
Forward-only removal of the v0.3 Algebraic threshold path: it broadcast
per-party c*s2 / c*t0 hint material whose aggregate reconstructs the joint
key (PULSAR-V13-HINT-LEAK). Deleted threshold_v03*.go + attest.go +
orchestrate.go + tests; preserved the two helpers the no-leak BCC signer
uses (polyVecExceeds, deriveMuCtx). Removed the now-dead code orphaned by
it — including makeHint, the secret-dependent hint primitive (BCC recovers
the hint from public data via FindHint), shamir_poly.go, and the accel
import. staticcheck U1000 clean; 190 tests pass.
The v0.1/v0.2 threshold.go path (documented aggregator-trust model, tests
green) is intentionally retained — it is not the V13 leak.
ClearanceVerifier + PartialZVerifier interfaces; default verifiers FAIL
CLOSED (ErrClearanceProofUnsound / ErrPartialZProofUnsound) so production
BCC signing is DISABLED until sound, externally-reviewed proofs are
registered. ProductionBCCSigningReady()=false by default. Honest
containment, not a stand-in for the (research-grade) proofs.