78 Commits
Author SHA1 Message Date
zeekay bc1b97da6b fix(deps): correct moved-tag hashes for luxfi modules in go.sum
Several luxfi versions carry TWO different contents across this workspace,
because published tags were moved instead of a new patch being cut:
age@v1.5.0, pq@v1.0.3, threshold@v1.9.4, zap@v0.6.0, zap@v0.8.1.

Adjudicated before editing, since "checksum mismatch / SECURITY ERROR" is also
what a real supply-chain attack looks like. It is not one here: sum.golang.org
holds the OLD hash while proxy.golang.org and a direct fetch BOTH serve the same
NEW bytes. Two independent transports agreeing means nothing is rewriting
content in flight — the tag moved at source. The sumdb entry is a fossil:
GOPRIVATE covers github.com/luxfi/* with GOSUMDB=off, so our own modules never
consult the checksum DB and a moved tag splits consumers silently.

The old bytes are served by nothing now, so a stale pin can never build.
Corrected to the only content that exists, in BOTH line forms (h1: and
/go.mod h1:) — Go reports these one at a time, so a partial fix just relocates
the error.

Deliberately no `go mod tidy`: this changes no selected version, only the
recorded hash of versions already chosen.

Verified: `go list -m all` resolves with no checksum error.

The durable fix is upstream: never move a published tag, cut x.y.z+1 instead.
2026-07-26 02:48:15 -07:00
zeekay 90b900fe5f chore(deps): bump geth v1.20.1 + luxfi deps — stack unification 2026-07-26 02:48:15 -07:00
zeekayandHanzo Dev c18abc71ba refactor(layout): ref/go/pkg -> pkg, ref/go/cmd -> cmd (canonical Go layout); home pulsard daemon at pkg/pulsard
Moves the Go reference implementation to the idiomatic repo-root pkg/ (library)
+ cmd/ (genkat tool) layout, dropping the ref/go/ nesting. Imports become
github.com/luxfi/pulsar/pkg/pulsar. The pulsar DAEMON (formerly the unhomed
standalone github.com/luxfi/pulsard module) now lives canonically at
github.com/luxfi/pulsar/pkg/pulsard. No replace directives.

BREAKING: consumers must update github.com/luxfi/pulsar/ref/go/pkg/... ->
github.com/luxfi/pulsar/pkg/... and bump to v1.9.0.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-06-30 15:59:28 -07:00
zeekay d2ac83dfef Pulsar v0.7.0: rip the legacy trusted-dealer / reconstruct path — no-reconstruct is now STRUCTURAL
The legacy_trusted_dealer quarantine is dead weight now that dealerless Mithril
RSS keygen + no-reconstruct signing are the production path. DELETED 9 files
(large_{types,reshare,dkg,threshold}.go, largeshamir.go, their tests,
bootstrap_dealer_test.go) — the reconstruct-at-combiner LargeCombine (the H-1
footgun) and the DealAlgShares trusted dealer are GONE, not quarantined.

Converts a POLICY guarantee (the tag is excluded from the build) into a
STRUCTURAL one (the path does not exist). The 3 surviving test consumers of the
dealer were migrated to a test-only buildAlgShareFixture (byte-faithful: the RED
nonce-reuse attack still recovers s1, so GATE A keeps its teeth). The CI invariant
was repurposed to TestCI_ReconstructAndDealerRipIsComplete — RED-fault-injected:
it FAILS if func LargeCombine(/func DealAlgShares(/the legacy tag reappear, and
FAILS if MithrilRSSKeygen/AggregateBCC disappear.

The legacy code is preserved (archived, not used) in luxfi/dealer.

RED-verified MERGE: 5/5 vectors hold; GATE-1/2/A/B/C + RSS gold proof + hyperball
+ capstone green; default + legacy-tag builds clean (tag now selects nothing);
106 .go files scanned, zero reconstruct/dealer symbols. obstruction guard
(naive_additive_seta_obstruction.go) kept + reframed (Mithril RSS is the escape,
not a dealer). Docs (VERSIONS/PULSAR/CHANGELOG) cleaned of the legacy track.
2026-06-28 18:42:59 -07:00
zeekay 61ac4d55ae docs: drop the deleted trusted-dealer / LargeCombine sections
VERSIONS.md + PULSAR.md described code that no longer exists. The keygen track
is now simply: dealerless Mithril RSS (production) + no-reconstruct signing.

- VERSIONS.md: delete the 'Trusted-dealer bootstrap (DealAlgShares)' and
  'Legacy GF(q) seed-share committee (large_*.go, quarantined behind
  legacy_trusted_dealer)' table rows; drop LargeCombine from the banned-primitive
  list; update the CI-invariant bullet to TestCI_ReconstructAndDealerRipIsComplete;
  rewrite the canonical one-line scope (dealerless KEYGEN DONE via Mithril RSS,
  leak-free NonceMPC the remaining gen-side residual). Honest scope preserved
  (standard-verifier PROVEN vs distribution-equivalence residual R1).
- PULSAR.md: un-name the dead legacy_trusted_dealer tag in the v0.5 history row.

CHANGELOG.md already states the dealerless-RSS end state (no edit needed).
2026-06-28 14:26:37 -07:00
zeekay 391dd6ddac pulsar: rip the legacy_trusted_dealer reconstruct path + trusted dealer
Delete the quarantined reconstruct-at-combiner stack and the trusted-dealer
keygen — dealerless Mithril RSS keygen + no-reconstruct signing are the
production path, so the quarantine is dead weight (it lives in git history).

Deleted (9 files):
  production: large_types.go large_reshare.go large_dkg.go large_threshold.go
              largeshamir.go   (the LargeCombine reconstruct-at-sign stack)
  tests:      large_e2e_test.go largeshamir_test.go precompile_e2e_test.go
  dealer:     bootstrap_dealer_test.go (DealAlgShares trusted-dealer keygen)
Also removed the untracked scratch zz_diag16_test.go.

Repurposed/updated the gates so they are clean, not dangling:
- ci_build_invariant_test.go: the two legacy tests (assert the tag gates files
  / dealer is test-only) are obsolete — there is no legacy to exclude. Replaced
  with TestCI_ReconstructAndDealerRipIsComplete: scans EVERY .go file and fails
  if func LargeCombine / func DealAlgShares / the legacy_trusted_dealer tag ever
  reappear, AND asserts the live MithrilRSSKeygen + AggregateBCC remain (rip
  didn't overreach). Stronger + permanent. This is the sole sanctioned holder of
  the forbidden strings. Kept TestCI_NoAssemblyOrCgoFiles verbatim.
- gate2_reachability_test.go: drop the deleted LargeCombine/DealAlgShares from
  the banned-primitive set (the ci-invariant owns 'they don't exist'); the gate
  keeps proving the LIVE reconstruct primitives are unreachable from the sign
  path. Cleaned stale comments.
- no_reconstruct_committee_test.go: drop the func LargeCombine declaration scan
  and the deleted-file check from GATE-2 structural (now the ci-invariant's job);
  GATE-1, GATE-2 behavioural, and the KeyFromSeed allowlist scan stay green.

Default build, legacy-tag build (now a no-op), and full suite all green.
2026-06-28 14:23:29 -07:00
zeekay 864b1d4752 pulsar: scrub stale dealer/LargeCombine references from surviving sources
Comment-only fixups so no surviving production file names the symbols being
ripped:
- distributed_bcc.go: the keygen fence is no longer a trusted dealer — point
  to Mithril RSS (mithril_rss.go) + the hyperball sibling; drop the redundant
  'DealAlgShares RIPPED OUT' narration block.
- types.go: drop the dangling 'LargeCombine (large_threshold.go)' reference.
- talus_dkg_vss.go: the stock-verifiable key is dealerless Mithril RSS, not a
  trusted dealer.
- mithril_rss.go: 'instead of a trusted dealer' (un-name DealAlgShares).
- naive_additive_seta_obstruction.go: fix the stale distributed_bcc_dkg.go
  filename; Mithril RSS is now IMPLEMENTED (the realized dealerless escape),
  not 'fall back to a dealer'; reframe the error + entry-point docs. The
  COMPUTED S_eta obstruction math is unchanged.

The obstruction guard's tests still pin the arithmetic; no symbol removed.
2026-06-28 14:17:09 -07:00
zeekay 03b5fa91b6 pulsar: migrate no-reconstruct SIGN tests off the trusted dealer
The DistributedBCCSigner / AggregateBCC tests bootstrapped their Shamir
s1-share committees via DealAlgShares (the trusted-dealer keygen). Replace
that with buildAlgShareFixture, an honest TEST FIXTURE: it samples one
ordinary S_eta ML-DSA key in-process and Shamir-shares s1 over GF(q) purely
to seed the signing proofs.

This is byte-faithful to the old fixture (same key, same shares) so GATE-1
(circl verify), GATE-2 (no-reconstruct) and the RED nonce-reuse attack stay
green. The production dealerless keygen is Mithril RSS (mithril_rss.go),
untouched; the production SIGN path never forms s1 — a property of signing,
independent of how the fixture dealt the shares. Prepares DealAlgShares for
deletion.
2026-06-28 14:12:26 -07:00
zeekay 1b61461bd6 bench(pulsar): mlwe-conversion overhead — de-dup regression check
Decomplected, one concern: the per-op poly<->mlwe.Poly marshal the Phase-4
DRY de-dup put into the ring hot loop (toMLWE/fromMLWE). Isolates pure
conversion vs ring math: toMLWE, fromMLWE, RoundTrip (pure tax),
NTT/INTT/MulNTT via mlwe.

VERDICT: de-dup did NOT regress perf where it matters.
- Allocations: ZERO. ntt/invNTT/mulHat all report 0 allocs/op. toMLWE in
  ISOLATION shows 1 alloc/2048B only because the sink forces the result to
  escape; -gcflags=-m proves toMLWE is inlined at every ring-op call site
  (mldsa_lattice.go ntt:230, invNTT:244, mulHat:174x2) with
  'make([]uint64,256) does not escape' -> stack-allocated. Only toMLWE's own
  generic return path (line 96) escapes, never the hot loop.
- CPU: pure conversion RoundTrip 249ns is ~12% of an NTT (2043ns), ~25% of
  MulNTT (1507ns) -- a modest constant factor, the price of one audited ring
  core (luxfi/mlwe), NOT a GC/alloc regression.

BRAID FLAGGED (not fixed -- bench task must not refactor production crypto):
the conversion is braided into every ring op because luxfi/mlwe's Ring
speaks []uint64 while pulsar's wire/FindHint frame needs [256]uint32. A
zero-copy mlwe API over borrowed buffers could reclaim the constant factor;
out of scope here.
2026-06-28 07:19:32 -07:00
zeekay b574456004 bench(pulsar): committee-output verify baseline (stock FIPS-204)
Decomplected, one concern: VerifyCtx on a genuine committee-produced
threshold signature (dealerless RSS keygen + threshold Sign in setup),
ML-DSA-65/87. The timed op is verification only.

Finding: committee output verifies at EXACTLY stock single-key cost --
P65 140 us vs single-key 136 us, P87 223 us vs 226 us, both 5 allocs/op
and identical byte footprint (33KB / 57.8KB). The verifier never learns
the signature was threshold-produced. This is the cheap baseline the
dealerless signing paths are measured against.
2026-06-28 07:16:00 -07:00
zeekay 6449aa2d97 bench(pulsar): BCC no-reconstruct distributed sign + AggregateBCC isolated
Decomplected, one concern: the DistributedBCCSigner (DealAlgShares Shamir
key) no-reconstruct path. Two isolated measurements, keygen in setup:

- BenchmarkBCCDistributedSign: the FULL ceremony per signature (fresh
  signers + NonceMPC deal + Round1/Round2/Finalize + rejection restarts).
  P65 10-17 ms, P87 14-24 ms across 5-of-3 / 5-of-4 / 7-of-5.

- BenchmarkBCCAggregate: the AggregateBCC combine path in isolation — one
  ceremony in setup captures a known-good (aggregator, R1, partials); the
  timed op is the aggregator's deterministic Finalize on those fixed inputs
  (per-partial proof verify + flat z-sum + public-w' hint recovery +
  assembly + self-verify). 1.79-3.66 ms; allocs scale exactly with quorum
  (322/424/526 = +102/partial). ~5-7x cheaper than the full ceremony.

*testing.B fixture builder + ceremony driver mirror the proven
newBCCFixture/runBCCCeremony, reusing the bccFixture struct (additive).
2026-06-28 07:14:50 -07:00
zeekay 3b364d05c6 bench(pulsar): HYPERBALL no-reconstruct sign + reconstruct-tax baseline
Decomplected, one concern: SignHyperball (no-reconstruct 3-round threshold
signer) cost per (mode in {65,87}, family). Keygen is setup; the timed op
is the full sign. Reports derived K parallel slots, per-party subset load,
sig bytes, and the mean rejection-round count (rounds/op).

Real finding (Quasar finality deployment): the no-reconstruct path runs
K=126-256 parallel commitment slots -> 250-580 ms/op and 1.8-4.6M allocs/op,
a 120-290x tax over the reconstruct path (BenchmarkHyperballReconstructTax,
1.85-2.4 ms). n=16,t=14 EXHAUSTS the 64-round budget on the hyperball path
(reported as a skip) though the reconstruct path signs it in ~8 ms -- the
real no-reconstruct scaling boundary.

Per-round cost is not independently invocable (one-shot signer loops the 3
rounds x K slots internally); reported as derived rounds/op, flagged in the
file header as a public-surface observation (not refactored).
2026-06-28 07:12:17 -07:00
zeekay 31ba79ddcc bench(pulsar): RSS dealerless keygen — combinatorial C(N,M) curve
Decomplected benchmark, one concern: MithrilRSSKeygen cost across the
committee families the dealerless line uses, swept over ML-DSA-44/65/87.

Reports ground-truth rss.NumSubsets (C(N,N-T+1)) + tau*C*eta vs gamma2 so
the combinatorial cost is measurable: C=8 (n8t8) -> 28 (n8t7) -> 560
(n16t14, the v0.6.3 overflow-fix family). The rejected n16t12 (C=4368,
tau*C*eta=856128 >= gamma2) surfaces its real ValidateCommittee reason,
making the viability wall part of the curve.

allocs/op scales ~linearly with C (load-robust): 216 -> 581 -> 10328.
2026-06-28 07:04:32 -07:00
zeekay 1ecaab88e1 docs: VERSIONS + CHANGELOG — keygen is dealerless (RSS), not trusted-dealer
The keygen-track tables claimed 'KEYGEN stays trusted-dealer (Residual B)' and
'Dealerless byte-FIPS-204 KEY DKG | PROVEN UNREACHABLE' — both false since v0.6.0.
Corrected to the accurate scoped truth: dealerless committee keygen via Mithril
RSS (mithril_rss.go) is the production keygen, standard-verifier-compatible
(proven); only the NAIVE additive S_eta lift is unreachable (not a class
impossibility — RSS is the published escape). Added v0.6.0-v0.6.3 + HYPERBALL
signer to the version ledger. Distribution-equivalence stays a labeled residual.
Docs-only; matches PULSAR.md (e5b6d6e).
2026-06-28 06:21:25 -07:00
zeekay e5b6d6e638 Merge docs/pulsar-final2: bring Pulsar docs current to v0.6.3 (dealerless RSS keygen + hyperball no-reconstruct signer + Avalanche-native sampled-cert math) 2026-06-28 06:17:39 -07:00
zeekay ac0729b51d docs(pulsar): bring PULSAR.md + DKG-PROOF-BOUNDARY.md current to v0.6.3
Updates the certificate-layer spec and the DKG proof-boundary spec/vectors
from their v0.5.0 grounding (docs/pulsar-final, 24 commits behind) to the real
v0.6.3 tree (3a6c2ea). DOCS ONLY — no code touched. Every implementation claim
re-grounded file:line in ref/go/pkg/pulsar/ at v0.6.3.

The load-bearing change: the keygen residual is CLOSED for Claim (A). v0.6.0+
ships DEALERLESS RSS keygen (mithril_rss.go MithrilRSSKeygen — Mithril short
Replicated Secret Sharing, ia.cr/2026/013; the dealer is dead at keygen too),
whose threshold signatures verify byte-for-byte under stock circl mldsa65.Verify
(mithril_rss_test.go:38 all 15 committees N<=6; mithril_rss_n8_test.go:16 T==N at
n=8,n=16). The v0.6.3 accumulateSubset per-subset mod-q reduction (mithril_rss.go
:122) fixes a uint32 overflow that wrapped the key once C(N,N-T+1)>=512,
unblocking large committees (n=16,t=14, C(16,3)=560; reconstructed ||s2||inf~205
<< gamma2). The Mithril 3-round HYPERBALL no-reconstruct signer
(mithril_rss_hyperball.go SignHyperball, gates 5/6) signs the RSS key with NO
party/coordinator ever forming the full s1/s2/y/w0/sk: each party emits only
z_j = y_j + c*s1_(j); the summed z is a standard FIPS-204 response; the secret
mask y is never serialised (commit 2c1458f). Verifies under stock circl
(mithril_rss_hyperball_test.go:41).

PULSAR.md: full v0.6.3 architecture (dealerless RSS keygen, hyperball
no-reconstruct signer, BCC/CSCP v0.4, malicious hardening v0.5, mlwe de-dup
v0.6.1/.2, overflow fix v0.6.3); the version lineage (v0.3 algebraic-broadcast
REMOVED -> ... -> v0.6.3 + hyperball); and the Avalanche-native sampled-cert
committee math — sample MANY small dealerless RSS committees, r-of-m binomial
tail, default Pulsar-HYBRID-PQ-v1 n=8,t=7,m=12,r=8 (one-committee p~2^-8.6,
P_fail~2^-59.8; NOT n=64/t=5), unbiasable stake-weighted VRF sortition seeded by
the prev finalized block, committeePlanHash bound into the Quasar subject, three
tiers FAST/HYBRID_PQ/PQ_ROOT.

DKG-PROOF-BOUNDARY.md + vectors: keygen residual restated (CLOSED via dealerless
RSS for Claim A; full FIPS-204 KeyGen-distribution-equivalence is the labelled
open-research residual). Keeps the 7 deliverables; adds RSS keygen + hyperball to
the transcript spec, DST table, share-commitment format, vectors (3 new positive,
5 new negative), and the proof checklist (18 rows). Line numbers refreshed v0.5.0
-> v0.6.3 throughout.

Claim discipline held: (A) standard-verifier-compatible is PROVEN (circl accepts;
tests cited); (B) full FIPS-204 KeyGen-distribution-equivalence + malicious-secure
CSCP/identifiable-abort are labelled residuals. Never claims FIPS/NIST-certified
threshold ML-DSA, fully-malicious-secure-proven, or global-1000-validator DKG.
2026-06-28 06:17:21 -07:00
zeekay 3a6c2eaba6 Pulsar v0.6.3: fix uint32 overflow in RSS subset-secret accumulation (unblocks large committees)
MithrilRSSKeygen / ReconstructKeyMaterial summed all C(N,M) per-subset short
secrets with raw uint32 poly.add and reduced mod q only once at the end. Each
chi_eta coefficient sits in the [q-eta, q+eta] rep (~q~2^23), so the accumulator
overflows once C(N,M) >= floor(2^32/q) = 512 — wrapping the coefficient into a
large-secret (unsignable) wall key. This silently broke the owner fault-tolerant
default n=16,t=14 (C(16,3)=560): reconstructed ||s2||_inf came out ~q/2 instead
of <= C*eta = 2240, so no signature could verify. n=8,t=7 (C=28) and all prior
committees (C<=20) were below threshold, hence unnoticed.

Fix: accumulateSubset reduces mod q PER subset. modQ is an additive homomorphism
=> the short Sum_S secret is IDENTICAL to a single final reduction for small
committees (n<=8 bit-identical) while every intermediate stays below 2q << 2^32.

Byte-preserving for small committees: golden KAT vectors unchanged, RSS gold
proof + full suite green (77s). Single-file keygen-correctness fix.
2026-06-28 05:53:29 -07:00
zeekay 5a160654c8 merge Pulsar trustless signing: dealerless RSS + general partition + overflow-fix + HYPERBALL no-reconstruct (gate 5)
Unites main's mlwe-dedup line with the trustless-signing line: general Algorithm-6
balanced partition (n=8,t=7 + n=16,t=14 sign), the per-add mod-q overflow fix (C>=512),
and the Mithril 3-round hyperball no-reconstruct threshold signing — no party/coordinator
forms the full ML-DSA key at sign time. Gate 5 closed + verified, stock-circl-verifiable.
2026-06-28 05:47:34 -07:00
zeekay 783ef02b5e Pulsar v0.6.2: complete mlwe de-dup — route SHAKE samplers + ML-DSA ring core onto luxfi/mlwe (Phases 3+4, byte-preserving)
Finishes the Pulsar->luxfi/mlwe consolidation begun in v0.6.1 (transcript,
Shamir). Phase 3 routes the FIPS-204 SHAKE samplers (ExpandA/ExpandS/
ExpandMask/SampleInBall) onto mlwe/sample/shake; Phase 4 routes the
NTT/INTT/MulNTT ring core (and deletes the duplicate 256-entry zeta
tables + montReduceLe2Q) onto mlwe/ring/mldsa. Both byte-identical by
construction: mlwe was lifted verbatim from this package.

Wire frame (bit-packers, a0+q Decompose/Power2Round) and FindHint/
no-makeHint (boundary.go) stay Pulsar-side — mlwe exposes those only via
the normalized [0,q) interface, a different byte representation.

GATE 3 + GATE 4: KAT byte-identical (9 keygen, 9 sign, 6 verify, 4
threshold-sign, 3 DKG); targeted suite 30/30; GATE-1/2, GATE A/B/C,
capstone, Mithril RSS gold proof all green. Vector SHAs unchanged.
Net -197 LOC.
2026-06-28 02:13:46 -07:00
zeekay f1309fd7de pulsar: route ML-DSA ring core (NTT/INTT/MulNTT) onto luxfi/mlwe/ring/mldsa (Phase 4, byte-preserving)
poly.ntt/invNTT/mulHat now delegate to the canonical mlwe/ring/mldsa
Ring (pulsarRing), which was lifted verbatim from this file — byte-
identical by construction. Deleted the duplicate 256-entry zeta tables
(nttZetas, nttInvZetas), montReduceLe2Q, and the now-dead mldsaQinv /
mldsaROver256 constants. Added toMLWE widening helper (fromMLWE narrows).

CT Montgomery convention preserved: bare INTT(NTT(p))==R*p, the real
contract INTT(MulNTT(NTT(a),NTT(b)))==a*b. mlwe's Ring bundles
reduce/normalize into INTT/MulNTT; this is byte-identical to the former
hand-reduced call sites because every multiply chain terminates in
normalize (only congruence-preserving add/sub intervene) before any
representation-sensitive use — audited at all invNTT/mulHat sites.

Kept Pulsar-side (mlwe exposes only the normalized [0,q) interface,
incompatible with these representations):
  - raw add/sub (no-reduction accumulation in polyDotHat)
  - reduceLe2Q/le2qModQ/modQ (Barrett reductions used by call sites)
  - decompose/power2round (a0+q wire representation; FindHint + PackT0)
  - useHint/highBitsCoeff/centeredLowBits (per-coefficient FindHint,
    boundary.go, kept Pulsar-side per spec)
  - exceeds, mulBy2toD, all FIPS-204 bit-packers (wire frame)

GATE 4: KAT byte-identical (9 keygen, 9 sign, 6 verify, 4 threshold-sign,
3 DKG); targeted suite 30/30; full suite green (minus pre-existing
RNG-flaky TestRED_PoC_MEDIUM). Vector SHAs unchanged.
2026-06-28 02:11:10 -07:00
zeekay 05fa76da1b pulsar: route FIPS-204 SHAKE samplers onto luxfi/mlwe/sample/shake (Phase 3, byte-preserving)
ExpandA/ExpandS/ExpandMask/SampleInBall now come from the canonical
github.com/luxfi/mlwe/sample/shake copy, which was lifted verbatim from
this package — byte-identical by construction. New mldsa_sample.go is the
sole Pulsar<->mlwe sampler bridge (fromMLWE converter + four adapters in
Pulsar poly shapes). Deleted the duplicate sampler bodies:

  - polyDeriveUniform, polyDeriveUniformLeqEta, polyUnpackLeGamma1
    (mldsa_lattice.go) and its now-unused encoding/binary + sha3 imports
  - polyDeriveUniformBall (mldsa_lattice.go -> bridge, thin SampleInBall)
  - expandMaskPoly (bcc_sign.go -> bridge, single-poly ExpandMask via L=1)
  - expandAPulsar (talus_dkg_vss.go -> bridge, vector ExpandA)

keyderive + mithril_rss route ExpandA/ExpandS through expandAPulsar /
expandSPulsar. Wire-frame packers (polyPackLeGamma1/T0/T1/W1/LeqEta,
polyVecPackHint) stay Pulsar-side. SampleInBall/ExpandMask keep
Pulsar-named per-poly entries (prod + no-leak/transcript tests call them
per polynomial).

GATE 3: KAT byte-identical (9 keygen, 9 sign, 6 verify, 4 threshold-sign,
3 DKG); targeted suite 30/30; full suite green (minus pre-existing
RNG-flaky TestRED_PoC_MEDIUM). Vector SHAs unchanged.
2026-06-28 01:54:56 -07:00
zeekay 8f67e8ca00 Pulsar v0.6.1: de-dup transcript + Shamir onto luxfi/mlwe (byte-preserving)
Phase 1: SP 800-185 transcript primitives (cSHAKE/KMAC/encoders) routed to
mlwe/transcript — the ONE copy; Pulsar keeps only protocol-specific hash methods
(transcriptHash prepends left_encode(nParts), not SP-800-185 TupleHash, so it
stays Pulsar-side over mlwe encoders).
Phase 2: Shamir Lagrange reconstruction + GF(257)/GF(q) field arithmetic routed
to mlwe/share; dead modInv/modPow deleted. KEPT Pulsar-side: the seeded deal
(2-byte GF257 / 4-byte GFq per-coeff draw — byte-incompatible with mlwe.Split's
8-byte rejection sampler, exactly as Corona kept its seeded deal) + wire types +
the O(n) single-point Lagrange (mlwe vends only O(n^2); routing reshare loops
through it would regress to O(n^3)).

Byte-preserving: golden KAT vectors UNCHANGED (kat_test.go replays them green);
full suite + gold proof (RSS stock-circl) + GATE-1/2/A/B/C all green. No wire
byte / modulus / domain tag / test vector changed; no-reconstruct signing math
and RSS keygen untouched. One-and-one-way DRY: Pulsar + Corona now share
mlwe/transcript + mlwe/share.
2026-06-28 01:32:37 -07:00
zeekay 7f7b04ad11 pulsar: route Shamir Lagrange + field arithmetic onto luxfi/mlwe/share (Phase 2, byte-preserving)
The GF(257) and GF(q=8380417) Lagrange interpolation and modular field
arithmetic now route through github.com/luxfi/mlwe/share, the single Shamir
surface shared with Corona. Pulsar keeps what is protocol-specific and
byte-incompatible with mlwe's sampler:
  - the seeded deal (shamirDealRandom*/Accel, LargeShamir.Deal/EvalAt) — its
    2-byte (GF257) / 4-byte (GFq) coeffStream draw + mod-reduce is byte-
    incompatible with mlwe's 8-byte rejection sampler, so it stays Pulsar-side
    (exactly as Corona kept its seeded ShamirSecretSharingGeneralWithSeed);
  - the share wire types (shamirShare uint16 / shamirShareQ uint32) and their
    (de)serialisation; EvalPointFromID/Q.

Routed:
  - shamirReconstructGF / shamirReconstructGFQ → share.Lagrange(xs,0,field)
    over share.NewPrimeField(257) and share.MLDSAField (O(n^2), unchanged);
    Pulsar's zero/dup error identity (ErrZeroEvalPoint/ErrDuplicateEvalPoint)
    is preserved by checking before the call.
  - LagrangeAtZeroQ and reshare's lagrangeAtZero keep their O(n) single-
    coefficient form (mlwe vends only the O(n^2) all-coefficients Lagrange;
    routing the BGW/reshare per-party loops through it would be O(n^3)) but
    now do ALL modular arithmetic via share.Field.Sub/Mul/Inv — so no field
    arithmetic is duplicated.
Deleted the now-dead modInvSmall/modPowSmall/modInvQ/modPowQ and their
redundant unit tests (the field inverse is owned + KAT'd in mlwe/share).

Byte-identity gate: golden vectors unchanged; KAT replays byte-identical
(9 keygen, 9 sign, 6 verify, 4 threshold-sign, 3 DKG); Shamir deal/reconstruct,
wire round-trips, zero/dup error identity, LagrangeAtZeroQ-agrees-with-recon,
reshare, Talus BGW/MPC (signing-path single-point Lagrange) all green.
2026-06-28 01:21:34 -07:00
zeekay 8f1c908c18 pulsar: route transcript onto luxfi/mlwe/transcript (Phase 1, byte-preserving)
The SP 800-185 primitives (cSHAKE256/KMAC256 + LeftEncode/RightEncode/
EncodeString/BytePad) now live solely in github.com/luxfi/mlwe/transcript,
the single hash surface shared with Corona. Pulsar keeps only what is
protocol-specific:
  - the customisation tags and N="Pulsar" function name,
  - cshake256/kmac256 Pulsar-named entries (bodies delegate to mlwe),
  - transcriptHash/transcriptHash32 (Pulsar's length-prefixed digest,
    NOT SP 800-185 TupleHash256 — re-expressed over the shared encoders).
Deleted the duplicate encoder/primitive bodies; routed the six direct
encoder callers (abort, threshold, protocol_auth, dkg_wellformed_proof,
nonce_transcript_proof, partial_proof) to transcript.LeftEncode/EncodeString.

Byte-identity gate: x/crypto resolves to v0.52.0 (== baseline, no skew);
golden vectors unchanged; KAT replays pass byte-identical (9 keygen, 9 sign,
6 verify, 4 threshold-sign, 3 DKG); full suite green.

Matches the Corona onto-mlwe precedent (keep protocol hash methods, route
bodies; generic encoders called directly). Mirrors corona refactor/onto-mlwe.
2026-06-28 01:21:34 -07:00
zeekay 2c1458fe41 pulsar: prove the secret mask y is never serialised onto the wire
TestHyperballMaskNeverOnWire: using the manual harness (exact yInt readable),
assert no party's mask y_{j,k} bytes appear in the Round-2 (w=A·y) / Round-3
(z=y+c·s1) wire data — completing the gate-5(b) no-leak proof for y.
2026-06-28 01:15:50 -07:00
zeekay 4e31aec593 pulsar: GATE 6 — fail-closed behaviours for the hyperball signer
TestHyperballSubThresholdFailsClosed: |active|≠T / unsorted / dup / OOR all
rejected before signing (no partial-quorum signature).
TestHyperballBiasedPartialCaughtAndBlamed: a biased partial makes every slot
miss FindHint/self-verify → fail-closed (no signature); blameSlot pinpoints
the culprit revealing T_j only up to it (Σ_j T_j = t−s2 never formed).
TestHyperballEquivocationCaught: a party changing w between R1 and R2 is
rejected by the binding check.
TestHyperballNonceReuseFatal: demonstrates reuse leaks the share
(z1−z2=(c1−c2)·s1, confirmable; wrong guess fails) and asserts the per-round
nonce derivation is fresh-across-rounds + deterministic-within-round.
2026-06-28 01:12:53 -07:00
zeekay 8bf7b6a1c4 pulsar: GATE 5 — no-reconstruct hyperball verifies under stock circl
TestHyperballStockCirclVerify: dealerless RSS ML-DSA-65 key signed via the
3-round hyperball protocol (NO reconstruction) verifies byte-for-byte under
unmodified circl mldsa65.Verify for n=8,t=8 and all 15 N≤6 committees — each
in a single 3-round shot. Tamper/wrong-msg/wrong-ctx rejected (non-vacuous).

TestHyperballNoReconstructStructural: (1) AST scan proves the signing source
contains no ReconstructKeyMaterial call; (2) runtime transcript oracle proves
no party's share s1_(j) nor the full reconstructed s1/s2 bytes appear on the
wire; (3) any T-1 coalition misses ≥1 subset.
2026-06-28 01:06:15 -07:00
zeekay e2ed595a31 pulsar: Mithril 3-round hyperball no-reconstruct threshold signer
mithril_rss_hyperball.go: SignHyperball runs the dealerless RSS ML-DSA-65
key with NO key reconstruction. Each active party holds only its balanced-
partition share s1_(j), samples its own hyperball mask y_j (Box-Muller
uniform-in-ball), and emits only z_j = y_j + c*s1_(j) under a leak-free
Excess gate; the coordinator sums z = Σ z_j = y + c*s1 and recovers the hint
from the PUBLIC w' = A*z − c*t1*2^d (BoundaryClear + FindHint). No party or
coordinator ever forms full s1/s2/t0/y/w/w0/sk. K-parallel slots + maxRounds
re-runs amortise acceptance; fail-closed self-verify before emit. blameSlot
gives leak-free identification (stops at culprit so Σ T_j = t−s2 never forms).

Explicit hyperballParty (private share) vs hyperballCoordinator (public
aggregates only) types make the no-reconstruct boundary structural.
2026-06-28 01:03:25 -07:00
zeekay 04dc4c3b66 pulsar: derive ML-DSA-65 hyperball params for no-reconstruct signing
Δ/δ = √(2κln2/n) leak-free gap (κ=64, n=L·N=1280, α=∞ conservative),
validated against the Mithril ML-DSA-44 reference table (predicts Δ≈56
on the diagonal vs the table's 55-57). One-sided BCC adaptation (ν=1,
no s2-side); r1 from the L∞ norm budget. Companion to the upcoming
mithril_rss_hyperball.go.
2026-06-28 00:55:35 -07:00
zeekay f2ff92091d Pulsar v0.6: dealerless RSS (Mithril) keygen — the dealer is dead at keygen too
Completes the dealerless story: v0.5.x made committee SIGNING no-reconstruct +
malicious-hardened; this makes KEYGEN dealerless via Mithril short replicated
secret sharing (luxfi/dkg v0.3.4), closing the keygen-side reconstruction
residual. Each committee's RSS-generated group key signs under the STANDARD
unmodified FIPS-204 verifier (cloudflare/circl mldsa65.Verify) — gold-proof
verified live: (t=8,n=8), (t=16,n=16), and all small committees T=2..N N=2..6
via real per-party rejection sampling (1-24 attempts). Beats the pick-2 / naive
S_eta wall by RSS-with-short-shares + local rejection (Mithril, ia.cr/2026/013),
NOT a trusted dealer, NOT centralized reconstruction.

Additive: new mithril_rss.go + dealerless RSS FIPS-leg Sign API (fail-closed,
quorum-gated); does not alter the v0.5.x no-reconstruct signing path. Integrates
clean with v0.5.1 (auth safe-by-default, epoch-pruned ledger, no-asm CI).
SCOPED: standard-verifier-compatible dealerless keygen; full FIPS-204 KeyGen-
distribution-equivalence (simulation/hiding/abort-bias) remains a labeled residual.
2026-06-28 00:31:06 -07:00
zeekay 0ea221b075 pulsar: consume dkg v0.3.4 (clean norm-only bound) + fix bad-committee test ((2,7) is norm-viable → use (12,16) norm-blown)
Full suite green. Dealerless RSS signs under stock circl at n=8,t=8 + n=16,t=16 (T==N,
N>6, owner n-of-n families); fault-tolerant n=8,t=7 awaits the general Algorithm-6
partition (Sign fails-closed 'no balanced partition' until then).
2026-06-28 00:23:58 -07:00
zeekay d2dbf00561 pulsar: consume dkg v0.3.2 (per-(N,T) bound) + prove dealerless RSS signs under stock circl at n=8,t=8 + n=16,t=16 (T==N, N>6)
The per-(N,T) norm bound admits the owner's high-t families. T==N committees (n=8,t=8;
n=16,t=16) sign under stock circl now (algorithmic base-case partition). T<N at N>6
(n=8,t=7 default) needs the general Algorithm-6 balanced partition (canonicalSharing
is table-limited to N≤6) — tracked as the next gap.
2026-06-28 00:18:17 -07:00
zeekay cfdc20afce pulsar: claim hygiene — reflect v0.5.1 auth-safe-by-default + ledger-pruning + no-asm
VERSIONS.md: add v0.5.0 (merged) + v0.5.1 (current) rows; update the malicious-
hardening status bullets — nonce single-use now epoch-pruned (bounded lifetime,
no live-window reuse), authenticated blame now SAFE-BY-DEFAULT (nil verifier =>
ErrOriginAuthRequired fail-closed; explicit UnauthenticatedAggregation opt-out),
CI invariant now also asserts pure-Go (no .s/.c/cgo) sealing GATE-C's blind spot.
Persistent-ledger residual narrowed to crash-restart only (lifetime now bounded).

PROOF-CLAIMS.md: update the residual paragraph for the same three items.

Scope kept HONEST: malicious-HARDENED, not fully-malicious-secure-PROVEN; the
'NEVER claim FIPS/NIST-certified . fully-malicious-secure-proven' line is intact.
2026-06-27 23:34:20 -07:00
zeekay d684ebf348 pulsar: CI assertion — no .s/.c/cgo files seal GATE-C blind spot (RED LOW/INFO nit #3)
GATE-C's reachability + indirection lint is complete for Go ASTs (+ go:linkname)
but cannot model hand-written assembly (.s) or C (.c/cgo): a banned reconstruct
primitive in asm/C, or a cgo escape hatch, would be invisible to the AST gates.
The no-reconstruct soundness claim therefore rests on the package being pure Go.

TestCI_NoAssemblyOrCgoFiles asserts that, three ways:
  (a) go/build classification of the default build — every non-Go compiled-source
      bucket (SFiles/CFiles/HFiles/CXX/M/F/Cgo/SWIG/syso) is empty;
  (b) raw dir scan for .s/.c/.h/.cc/.cpp/.m/.f/.syso/.swig* — catches files a
      build constraint hides from (a) but that could link under a tag;
  (c) AST ImportsOnly scan of EVERY .go file (incl. constrained + _test) for a cgo
      import 'C' — catches a constraint-hidden cgo escape.

Negative-control verified: a probe .s file is caught ('GATE-C blind spot OPENED'),
removed, suite green again. GATE C reachability + indirection stay green.
2026-06-27 23:34:20 -07:00
zeekay 878b831fbc pulsar: epoch-prune the in-memory nonce ledger (RED LOW nit #2)
The single-use ledger map grew unbounded over a validator's lifetime. Add
epoch-based pruning that bounds it to a sliding retained-epoch window WITHOUT
reopening reuse inside the live window:

  - (*InMemoryNonceLedger).PruneBefore(minEpoch) drops reservations with
    binding.Epoch < minEpoch; entries in [minEpoch, inf) are kept, so reuse there
    is still refused (ErrNonceReused). Caller picks minEpoch >= finality/reorg
    depth (a nonce from a pruned, past-finality epoch cannot be usefully replayed).
  - NoncePruner — the optional pruning capability, kept OFF the base NonceLedger
    interface (reserve/check stays minimal); persistent ledgers may prune via TTL.
  - PruneShareLedgers(minEpoch) — operator lever: sweeps the process-global
    per-share registry, pruning every ledger that implements NoncePruner. Snapshots
    under the registry lock, prunes each under its own lock (never two locks at once).

Test: PruneBefore(8) frees epochs 1..7; reuse within retained [8,10] still
ErrNonceReused; pruned slots reclaimed. Registry sweep prunes across per-share
ledgers, retained-window reuse still refused. GATE A (nonce single-use default
path + cross-committee dedup) stays green.
2026-06-27 23:34:20 -07:00
zeekay e010980b70 pulsar: origin-auth safe-by-default — fail-closed on nil verifier (RED MEDIUM nit #1)
AggregateBCC/AggregateBCCWithBlame previously aggregated WITHOUT origin
authentication when auth==nil (SetIdentity opt-in), so a production caller that
forgot to wire the verifier silently reverted to the exclude-honest-victim
footgun. Now:

  - auth == nil  => ErrOriginAuthRequired (FAIL-CLOSED), matching the nonce
    ledger's no-fail-open posture.
  - the unauthenticated path is reachable ONLY via the EXPLICIT opt-out sentinel
    UnauthenticatedAggregation (trusted-channel / test). The sentinel panics if
    ever invoked as a real verifier (fail loud, never silent).
  - DistributedBCCSigner.FinalizeWithBlame forwards d.idVerify, so a signer with
    no SetIdentity refuses FAIL-CLOSED too; opt out with
    SetIdentity(nil, UnauthenticatedAggregation).

Test (origin_auth_gate_test.go): the DEFAULT (nil verifier) free-fn AND signer
paths refuse with ErrOriginAuthRequired; the explicit opt-out and a real verifier
both aggregate to a FIPS-valid signature; the sentinel is not a usable verifier.
Trusted in-memory ceremonies (runBCCCeremony, talus, sub-quorum, sound-proof,
no-reconstruct behavioural) opt out explicitly. GATE B / RED MEDIUM flip /
capstone / no-reconstruct all stay green.
2026-06-27 23:34:20 -07:00
zeekay 5016e84742 pulsar: consume luxfi/dkg v0.3.0 (Mithril RSS) from the published tag
Drops the local replace directive; requires github.com/luxfi/dkg v0.3.0 which
ships the rss/ dealerless RSS DKG combinatorics the FIPS leg builds on.
2026-06-27 23:06:14 -07:00
zeekay 2a4758c014 pulsar: dealerless RSS FIPS-leg Sign API (fail-closed, quorum-gated)
MithrilKey.Sign reconstructs key material from any T-quorum's holdings, BCC-signs
(hint from public w', no c·s2/c·t0 leak), and FAIL-CLOSED self-verifies against
FIPS-204 before emitting. Output verifies under stock circl mldsa65.Verify.
Proven for multiple qualifying quorums under one group key. Full pulsar suite
green (65s).
2026-06-27 23:05:15 -07:00
zeekay 7dfb6e7a7e pulsar: prove dealerless RSS stock-circl-verifiable for ALL 15 committees
Every admissible Mithril committee (2≤T≤N≤6) signs and verifies under stock
circl mldsa65.Verify within a small attempt budget (1-90 attempts; even (4,6)
with C(N,M)=20, ‖s2‖∞≤80 in ~9). The centralized reconstruct+BCC signing range
equals the scheme viability range — c·s2 stays small because c is sparse (τ=49
±1), so the hint fits ω even at the larger committees.
2026-06-27 23:02:28 -07:00
zeekay 14b66132c2 pulsar: dealerless RSS (Mithril) FIPS-204 key — beats the pick-2 wall
The FIPS signing key now comes from dealerless Replicated Secret Sharing
(Mithril, ePrint 2026/013) instead of trusted-dealer DealAlgShares. Each
M-subset's short χ_η secret is sampled by its leader; the key is the SUM of
C(N,M) short shares so ‖s2‖∞ ≤ C(N,M)·η is genuinely small (vs ‖B·u‖∞≈q/2 for
the vss key). Combinatorics from luxfi/dkg/rss; FIPS-204 arithmetic is pulsar's
own circl-identical lattice code.

PROVEN (TestMithrilRSSStockCirclVerify): a NO-DEALER RSS key signs via the BCC
signer and verifies BYTE-FOR-BYTE under unmodified cloudflare/circl
mldsa65.Verify — committees (2,2)(2,3)(3,3)(2,4)(4,4) in 9-24 attempts; tamper
and wrong-message rejected (non-vacuous). Dealerless structural proof (no party
holds all subsets, no T-1 covers all) + any-quorum-same-key for all (T,N).
go.mod replace → local dkg/rss pending v0.3.0 tag.
2026-06-27 23:01:10 -07:00
zeekay 31d3501d84 Pulsar v0.5: malicious-security hardening (red-verified, 0 crit/high)
Hardens the v0.4 no-reconstruct BCC/CSCP committee signer toward malicious
security. blue→red→blue→red loop converged (red found a real HIGH, blue fixed
safe-by-construction, red re-verified MERGE):

- Nonce single-use SAFE BY DEFAULT: process-global registry keyed by share
  identity; every signer over a key-share shares one fail-closed ledger; reserve
  before secret; -race/TOCTOU clean. Closes the nonce-reuse->full-key-recovery
  HIGH on the DEFAULT API path (red PoC flipped: 2nd partial = ErrNonceReused).
- Identifiable abort: partial->PartyID authenticated (Author+AuthSig, verified
  before attribution) so a malicious member cannot frame/exclude an honest node;
  duplicate PartyID rejected; malformed-share panic closed.
- GATE-2 name-filter -> call-graph reachability + banned-primitive indirection lint.
- CI invariant: prod build never carries legacy_trusted_dealer.
- nonce-ticket lifecycle, authenticated messages + equivocation detection,
  selective-abort, order-independence (no last-mover bias).

GATE-1 (circl mldsa65.Verify accepts) + GATE-2 (no s1/seed/sk in combine) green;
354 tests pass. HONEST RESIDUALS (liveness/availability, NOT forgery/leak):
persistent crash-safe ledger; networked delivery auth (consensus-owned); BDLOP
valid-sigma wrong-z attribution; auth==nil default footgun; epoch-pruned ledger.
SCOPED CLAIM: dealerless committee nonce-gen + CEF/CSCP no-reconstruct signing +
standard-ML-DSA-verifier output, malicious-hardened; NOT FIPS/NIST-certified, NOT
fully-malicious-secure-PROVEN (mechanized), NOT global-1000-validator DKG.
2026-06-27 22:56:02 -07:00
zeekay 287c3d9446 pulsar: claim hygiene — nonce safe-by-construction, authenticated blame, honest GATE-C wording
Update VERSIONS.md + PROOF-CLAIMS.md so no claim overstates:
- nonce single-use is now claimed ENFORCED BY DEFAULT (per-share registry on the
  real API), not opt-in; w1-only dedup closes cross-committee reuse.
- authenticated-PartyID blame: blame gated on identity-signature validity; an
  attacker cannot frame/exclude an honest victim; never blame off a raw PartyID.
- GATE-C: 'complete for DIRECT calls' + companion indirection lint — drop the
  'never miss one' overstatement; the pair (not either half) closes the banned set.
- residuals restated honestly: persistent ledger (in-process default safe now),
  networked transport auth, BDLOP valid-sigma-wrong-z.
2026-06-27 22:36:52 -07:00
zeekay 5f2722f987 pulsar: GATE-C indirection lint + honest soundness wording (RED INFO)
The AST name call-graph follows DIRECT named calls only; it misses function-
value / closure / go:linkname indirection (RED PoC: e:=deriveKeyMaterial;
s:=bccSign; s(e(...)) not flagged). The old comment overstated 'never miss one'.

Fix: pair the reach graph with a companion lint forbidBannedPrimitiveIndirection
that forbids taking ANY banned primitive as a non-call value or aliasing it via
//go:linkname anywhere in the default build. With that lint green, every banned-
primitive use in the build IS a direct call the graph follows — so the PAIR is
complete for the banned set (neither half claimed complete alone). Wording
softened to state exactly this.

GATE: TestGATE_C_NoBannedPrimitiveValueIndirection — (a) the real build takes no
banned primitive as a value / linkname; (b) the name graph alone MISSES the
e:=deriveKeyMaterial; s:=bccSign indirection while the lint CATCHES it; (c)
go:linkname aliasing a banned primitive is caught.
2026-06-27 22:36:52 -07:00
zeekay 0bc2523566 pulsar: authenticate partial->PartyID; gate blame on signature validity (RED MEDIUM)
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.
2026-06-27 22:36:52 -07:00
zeekay 4f4dfb9560 pulsar: nonce dedup keys on w1 ALONE — close RED LOW cross-committee gap
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.
2026-06-27 22:36:52 -07:00
zeekay 9820f7a821 pulsar: nonce single-use SAFE BY CONSTRUCTION — close RED HIGH default-path fail-open
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.
2026-06-27 22:36:52 -07:00
zeekay d237645924 merge Phase-3b: pulsar onto luxfi/dkg — no-reconstruct vss DKG + malicious CSCP + hardening suite
Reconciled both Phase-3b lines: luxfi/dkg v0.2.0 consumption (vss no-reconstruct DKG
primitive talus_dkg_vss.go, pin-4 KAT, CSCP malicious lift, mechanized pick-2 obstruction)
+ in-house malicious hardening (nonce anti-replay, identifiable-abort sign path, GATE A-C,
nonce-ticket lifecycle, claim hygiene). HONEST RESIDUAL: stock-FIPS-204 signing leg keeps
trusted-dealer DealAlgShares (the {no-reconstruct,dealerless,stock-FIPS}=pick-2 wall);
corona carries the dealerless permissionless guarantee. Dealer-free leg = Mithril RSS (next).
2026-06-27 22:31:11 -07:00
zeekay 17f37318f1 reconcile: unify Phase-3b — luxfi/dkg vss DKG + KAT (feat) ∪ malicious hardening suite (harden)
feat brings: luxfi/dkg v0.2.0 dep, pin-4 ExpandA/t1 KAT, vss no-reconstruct DKG
  (talus_dkg_vss.go), CSCP malicious lift, the mechanized pick-2 obstruction proof.
harden brings: nonce single-use anti-replay, identifiable-abort sign path, GATE A-C
  reachability, nonce-ticket lifecycle, malicious-behavior capstone, claim hygiene.
Overlapping CSCP lift (talus_cef.go, talus_cscp_dkg.go) byte-identical → clean merge.
2026-06-27 22:28:56 -07:00
zeekay b9f6c591c3 pulsar: authenticate partial->PartyID; gate blame on signature validity (RED MEDIUM)
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.
2026-06-27 22:28:34 -07:00
zeekay 583fd91715 pulsar: nonce dedup keys on w1 ALONE — close RED LOW cross-committee gap
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.
2026-06-27 22:11:53 -07:00
zeekay 1016afb88b pulsar: nonce single-use SAFE BY CONSTRUCTION — close RED HIGH default-path fail-open
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.
2026-06-27 22:08:57 -07:00
zeekay bb7c6b532d pulsar: no-reconstruct DKG via luxfi/dkg vss (Phase-3b step 2)
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).
2026-06-27 22:02:34 -07:00
zeekay 12d6ae2287 pulsar: KAT helper -> katDKGMatrixFromCoeff (yield dkgMatrixFromCoeff to production talus_dkg_vss.go) 2026-06-27 22:02:30 -07:00
zeekay 82fec80c7f pulsar: prove luxfi/dkg CSCP bridge w1 == ideal oracle == HighBits(A·ȳ) (step 4 fidelity) 2026-06-27 21:59:19 -07:00
zeekay 4ec7a2be44 pulsar: lift TALUS CSCP to luxfi/dkg malicious cscp.SecureHighBitsVec (step 4)
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 9c2ca7202a)
2026-06-27 21:53:13 -07:00
zeekay 5054c59a65 pulsar: lift TALUS CSCP to luxfi/dkg malicious cscp.SecureHighBitsVec (step 4)
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.
2026-06-27 21:51:07 -07:00
zeekay 403086de65 pulsar: consume luxfi/dkg v0.2.0 + pin-4 ExpandA/t1 KAT (Phase-3b step 1)
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.
2026-06-27 21:39:38 -07:00
zeekay 2066d70144 pulsar: add luxfi/dkg v0.2.0 dep (Phase 3b start) 2026-06-27 21:39:32 -07:00
zeekay 73b4a037cc pulsar: malicious-behavior capstone + selective-abort + order-independence (item 5b)
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).
2026-06-27 21:21:36 -07:00
zeekay 282dba7ddd pulsar: nonce-ticket lifecycle + authenticated-message equivocation (item 5a)
- 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.
2026-06-27 21:19:02 -07:00
zeekay 6eee87d466 pulsar: GATE-2 hardened to call-graph reachability (GATE C) + CI legacy-tag invariant (items 3-4)
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.
2026-06-27 21:15:13 -07:00
zeekay 42b580dc43 pulsar: identifiable abort for the sign path (RED MEDIUM) + GATE B
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).
2026-06-27 21:10:55 -07:00
zeekay beff7b67b6 pulsar: nonce single-use anti-replay guard closes RED nonce-reuse key-recovery (HIGH) + GATE A
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).
2026-06-27 20:58:24 -07:00
zeekay f8280b7996 pulsar: claim hygiene — VERSIONS.md (v0.3 removed / v0.4 no-reconstruct / keygen track) + FINAL SCOPED claim in PROOF-CLAIMS §0
v0.3 algebraic-broadcast REMOVED (HINT-LEAK); v0.4 = BCC/CSCP no-reconstruct
(current). Keygen track documented: trusted-dealer bootstrap (test-only,
quarantined) + Pedersen-VSS dealerless exploration (feat/v02 branch) +
NonceMPC leak-free residual. PROOF-CLAIMS §0 records the user-approved scoped
claim; NEVER FIPS-certified / fully-malicious-proven / 1000-validator DKG.
2026-06-27 20:52:05 -07:00
zeekay 89d408ddb2 Pulsar v0.4: no-reconstruct BCC/CSCP committee signing is the sole production path
Red-verified (commit a3306ff): 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.
2026-06-27 20:06:25 -07:00
zeekay a3306ffc35 pulsar: no-reconstruct committee threshold sign; quarantine reconstruct+dealer
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.
2026-06-27 19:30:56 -07:00
zeekay 158658fcca Merge: scope dealerless-keygen claim correctly (Phase 0) 2026-06-27 18:28:28 -07:00
zeekay 252450f24c pulsar: scope the dealerless-keygen claim correctly (Phase 0 claim cleanup)
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.
2026-06-27 18:28:28 -07:00
zeekay 166f23d951 pulsar: TALUS docs to v1.2.0 — PULSAR-V13-W-LEAK closed (semi-honest), residuals scoped
TALUS threshold ML-DSA built: dealerless nonce DKG + CEF + REAL CSCP CarryCompare secure-comparison closes PULSAR-V13-W-LEAK (semi-honest, simulation-proven) — no node forms w0/w even transiently. FindHint + ReleaseGate bound malicious deviation to liveness (never forge/leak). Open items narrowed to Residual A (malicious-secure CSCP + networked non-simulation MPC) and Residual B (dealerless byte-FIPS-204 key DKG proven unreachable via S_eta bound -> trusted-dealer keygen; Corona carries permissionless). BLOCKERS reconciled, README/LLM/CHANGELOG/PROOF-CLAIMS refreshed; tag refs -> v1.2.0.
2026-06-27 14:14:46 -07:00
zeekay 45afcd5af9 pulsar: REAL CSCP secure-comparison circuit closes PULSAR-V13-W-LEAK (semi-honest)
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.
2026-06-27 12:34:56 -07:00
zeekay e9bcf2cecc pulsar: TALUS-TEE w1 source + nonce pool + BLOCKERS V12-item-3 (CSCP obstruction)
- talus_cef.go: TalusTEEComputeW1 — the Pulsar-TEE coordinator w1 source
  that (unlike MPC) CAN pre-filter BCC offline; returns a W-LEAK-clean cert
  (only w1 leaves the TEE). Cross-checked vs the coordinator nonce path.
- talus_sign.go: TalusNoncePool — refillable pool with the existing
  non-grindable CanonicalNonceIndex selection (TEE admits only
  boundary-clear; MPC filters online), one-time consume, fail-closed empty.
- talus_test.go: TestTalus_TEE_ComputeW1_BCCPrefilter +
  TestTalus_NoncePool_CanonicalSelection (15 TALUS tests total, all green).
- BLOCKERS.md: PULSAR-V12 item-3 documents the TALUS build + the COMPUTED
  CSCP residual; PULSAR-V13-W-LEAK marked NARROWED (DebugW gone; only the
  secure-comparison realisation of cefIdealSecureHighBits remains).
2026-06-27 11:52:08 -07:00
zeekay a389572ff0 pulsar: TALUS threshold ML-DSA — dealerless nonce DKG + CEF distributed-w1 + 2 profiles (PULSAR-V12)
Implements the TALUS construction (arXiv:2603.22109) on the existing
no-reconstruct BCC/CEF signing core:

- talus_nonce_dkg.go: dealerless Shamir Nonce DKG. No party/dealer ever
  forms the joint one-time nonce y; each party sums received shares to its
  own y_i. Replaces the trusted nonce dealing inside DealNonceMPCDebug.
- talus_cef.go: CEF distributed-w1. Per-party additive commitment
  g_i=A*(lambda_i*y_i) (no y/w reconstruct) + the proven carry-elimination
  identity (w1 recovered from per-party Decompose parts + carries) + the
  ideal secure-HighBits functionality whose OUTPUT is W-LEAK-clean (only
  w1, never w/w0). assessCSCP COMPUTES the residual CarryCompare
  obstruction (which step, primitive per T, round/comm cost, leak if
  skipped) — not faked.
- talus_mpc.go: honest-majority BGW secure multiplication + shared random
  bits, the substrate the CSCP composes from; concretely enforces TALUS
  Thm 10.1's N>=2T-1 barrier.
- talus.go: TalusTEE/TalusMPC profiles, suite IDs (65/87; 44 refused),
  Quasar evidence kind 'pulsar-talus-mldsa', honest-majority gate.
- talus_sign.go: mandatory pre-release FIPS-204 verify gate (never release
  a failed sig) + Quasar TalusEvidence (suite-pinned verifier dispatch,
  distinct from Corona).
- talus_test.go: multi-node Pulsar-MPC proof — dealerless DKG, distributed
  commitment, carry-elimination identity, BGW N>=2T-1, one-round signing
  verifying under stock cloudflare/circl mldsa{65,87}.Verify, sub-quorum
  fails, nonce one-time, failed-verify never released, no y/w/w0/s1
  reconstruct.

go build ./... green; full pulsar package tests green.
2026-06-27 11:47:18 -07:00
zeekay ab0169db00 BLOCKERS: PULSAR-V12 item-1 BUILT (no-reconstruct signer), item-2 obstruction computed; PARTIAL-Z-PROOF functionally closed on the new path 2026-06-27 11:07:32 -07:00
zeekay f39ed83a3b pulsar: dealerless ML-DSA DKG obstruction, derived not asserted (PULSAR-V12 Part 2)
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.
2026-06-27 11:06:09 -07:00
zeekay 1bebfedd95 pulsar: no-reconstruct single-share threshold ML-DSA signer (PULSAR-V12 Part 1)
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.
2026-06-27 11:01:26 -07:00
zeekay cb6b053a66 BLOCKERS: file PULSAR-V12-PARALLEL-PQ (consensus dual-PQ AND-mode consumer)
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).
2026-06-27 01:13:08 -07:00
zeekay 74011dba05 security(pulsar): rip v0.1 reconstruct-then-sign Combine path — H-1b
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).
2026-06-26 21:08:00 -07:00
zeekay b20cb7954c corona→corona: academic Corona now only in lp-220-p3q-corona 2026-06-11 10:28:26 -07:00