Workspace-wide sync. luxfi/node already shipped on 1.26.4 in v1.30.6;
this prose-doc catch-up keeps the runbook in lock-step with the
actual Dockerfile/go.mod pin.
AUDIT-2026-06.md ships the four-dimensional audit:
D1 Permissionless safety: PASSED with two minor open items for v0.7.6.
Rogue-key NOT APPLICABLE (single global (A, bTilde); no per-party pk
in the BLS-aggregation sense). Adaptive corruption documented as
paper-cited only (carryover; GATE-4 target). Slashable equivocation
evidence is cryptographically extractable via signed dkg2.Complaint
and activation-cert non-verification. Front-running: sid IS bound
into PRNG seed / MAC / transcript hash (CRIT-1 fix 2026-05-03), but
plumbed as Go int (64-bit), not 32-byte randomness as threat-model
claims — D1-2 MEDIUM, doc tightening for v0.7.6. D1-3 MINOR: extend
Complaint to signing-round equivocation.
D2 TEE integration: WAS A GAP. Closed by this audit via the new
docs/mptc/tee-integration.md — full per-platform spec for AMD
SEV-SNP (primary Linux path), NVIDIA Confidential Computing
(H100/H200/B200 CC mode) when gpu.UseAccelerator() is used in
production, Intel TDX (alternative), Apple Secure Enclave (local-
only defense-in-depth on Apple Silicon). Intel SGX explicitly NOT
RECOMMENDED. Decomplecting rule held: ZERO Corona kernel code
change required for §3.1, §3.3, §3.4; §3.2 adds one optional
plumbing function (gpu.MaybeRegisterAttested) tracked for v0.7.6.
The TEE layer sits OUTSIDE the cryptographic math — Corona observes
nothing of TEE state.
D3 GPU acceleration inventory: corona/gpu is a thin SubRing-registration
shim around luxfi/lattice/v7/gpu — no kernels live in corona/.
dkg2_parallel.go is goroutine fan-out (honestly relabelled in
66f7521), not GPU dispatch. Sister-repo lux-private/gpu-kernels/
ops/mpcvm/*/mpcvm_corona.* and ops/crypto/corona/ carry the
legacy name — D3-1 INFORMATIONAL, cross-repo rename TODO.
D4 corona->corona sweep (in-repo COMPLETE):
* Makefile: BINARY_NAME corona -> corona; banner + docker tag.
* Dockerfile: addgroup/adduser/binary-path/entrypoint.
* .github/workflows/ci.yml: bin/corona -> bin/corona; artifact
name corona-${runner.os}-${arch}; release output name pattern.
* .golangci.yml: local-prefixes: corona -> github.com/luxfi/corona.
* docs/{app,content,lib,out,public,.source,mdx-components.tsx,...}
DROPPED — a fumadocs Next.js site (3057 lines) that framed Corona
as a privacy ring-signature scheme (Schnorr + linkability +
hash-to-curve + "ring signatures for transaction privacy"). That
is NOT Corona — Corona is a 2-round R-LWE threshold sig (Boschini
et al. 2024/1113). The site referenced a non-existent
github.com/luxfi/corona Go module. Removed.
* KEPT: docs/mptc/ (authoritative submission docs).
* KEPT: historical refs in CHANGELOG.md, LLM.md, and
CRYPTOGRAPHER-SIGN-OFF.md — they document the prior v0.4.x purge
and are correct as historical context.
* Cross-repo (luxfi/threshold, lux-private/gpu-kernels, luxfi/node)
inventoried as D4-1 INFORMATIONAL — sister-repo work items.
Build clean (GOWORK=off go build ./...). gofmt -s clean. vet clean.
Full short test suite green: dkg, dkg2, gpu, hash, keyera, networking,
primitives, reshare, sign, threshold, utils, wire — ALL ok.
KATs unchanged: no Go code paths edited; FIPS-TRACEABILITY byte-
equality contract preserved. v1.x ABI unchanged: no public Go API
modified. Constant-time discipline preserved: no crypto/subtle paths
touched; CONSTANT-TIME-REVIEW.md remains authoritative.
Production deployment posture (unchanged from v0.7.5): APPROVED WITH
ROADMAP GATES for public-permissionless deployment as a R-LWE
threshold signature primitive in Lux Quasar consensus, with v0.8.0
GATE-3b (10^9-sample dudect on pinned CPU) + GATE-4 (external audit)
+ GATE-5 (Jasmin extraction byte-walk) as carryover roadmap items.
Closes the long-standing Red HIGH B2 gap that forced luxfi/threshold's
JSON-RPC dispatcher (pkg/thresholdd/corona.go, pulsar.go) to refuse
every Corona op because Signature and GroupKey lacked stable
cross-process wire formats.
Adds:
Signature.MarshalBinary / UnmarshalBinary
GroupKey.MarshalBinary / UnmarshalBinary
VerifyBytes(gkBytes, msg, sigBytes) bool
Layout is domain-separated (CORS magic for Signature, CORG for
GroupKey) so the dispatcher cannot confuse the two slots. Versioning
(2-byte v1) leaves room for byte-format evolution under explicit
opt-in. Bounded validation flows through corona/wire so a malformed
length cannot trigger an oversized allocation.
VerifyBytes is the stateless surface that pkg/thresholdd consumes:
bytes-in, bool-out, no per-process state, byte-equal re-marshaling on
the roundtrip. Cross-wire feeds (GroupKey magic in the Signature slot)
are explicitly rejected.
Tests cover happy path (sign -> marshal -> unmarshal -> verify, plus
byte-equal re-marshal), tampered sig and tampered message rejection,
malformed input rejection (empty, too-short, wrong magic, wrong
version), and cross-wire confusion rejection.
Cannot t.Parallel: threshold.GenerateKeys mutates the sign package
globals K and Threshold (threshold.go:123-124); parallel use of the
kernel races on those globals. The wire codec itself is pure.
The two build-tagged files (dkg2_gpu_default.go, dkg2_gpu_accel.go)
implemented a goroutine fan-out, NOT a GPU dispatch. Real GPU NTT for
the underlying ring math lives in luxfi/lattice/v7/gpu and is reached
via the consensus engine accel pipeline.
Collapse to a single file dkg2_parallel.go with no build tag. Default
OFF (matching the prior non-gpu build); tests opt in via SetDKG2GPUForTest.
The exported helper name SetDKG2GPUForTest is left as-is to avoid breaking
existing test callers; the legacy `GPU` in the name is documented as a
historical misnomer.
Final public-BFT audit (per AUDIT-2026-05.md §10) flagged the v0.7.3
asymmetry where keyera.Bootstrap / keyera.BootstrapWithSuite still
routed through the legacy single-dealer Shamir share-out while
keyera.Reanchor (v0.7.4) had already flipped to Pedersen-DKG. Callers
picking the "obvious" name silently inherited the dealer caveat at
chain genesis.
v0.7.5 flips both Bootstrap defaults to route through BootstrapPedersen
(Pedersen-DKG over R_q + Path (a) noise flooding) so no party ever
holds the master secret s at any point in the ceremony. The legacy
trusted-dealer ceremony path is reachable only via the explicit
BootstrapTrustedDealer / BootstrapTrustedDealerWithSuite /
ReanchorTrustedDealer / ReanchorTrustedDealerWithSuite names.
The shared trusted-dealer body lives in the unexported
bootstrapTrustedDealerImpl, so the public-facing aliases (and the
Reanchor trusted-dealer alias) cannot drift from each other.
Structural invariant: BootstrapPedersen requires n >= 2 && t < n.
Deployments that need every validator to sign (t == n) must select
BootstrapTrustedDealer explicitly; the unqualified Bootstrap will
fail with ErrBootstrapPedersenShape on t == n, surfacing the
trust-model decision at the call site rather than silently routing
to the dealer path.
Tests updated: keyera_test.go, hashsuite_immut_test.go,
bootstrap_pedersen_test.go, reanchor_pedersen_test.go — drop t == n
from the public-BFT cases (now use t = n - 1) and propagate the
3-tuple return from the new Bootstrap signature.
Cross-runtime KAT byte-equality: scripts/regen-kats.sh updated for
the post-Corona purge (LUXCPP path now crypto/corona, not the
stale crypto/pulsar); reshare_oracle / activation_oracle /
dkg2_oracle accept both CORONA_*_KAT_PATH and PULSAR_*_KAT_PATH env
overrides for compatibility with the existing script. Manifest
regenerated and verified.
gpu/gpu_test.go: skip on non-GPU builds rather than fail
(RegisterRing returns "GPU unavailable" without a CGO Metal/CUDA
backend; the rest of the audit suite is exercised by the other
package tests).
Closes the Reanchor trusted-dealer regression flagged in v0.7.3 red
review: keyera.Reanchor previously called BootstrapWithSuite (the
trusted-dealer path) under the hood, so a governance-driven Reanchor
re-introduced the dealer caveat at every rotation.
v0.7.4 routes Reanchor / ReanchorWithSuite through ReanchorPedersen
by default. Pedersen-DKG over R_q + Path (a) noise flooding means no
party holds the new era's master secret s at any point in the
rotation. The legacy trusted-dealer behaviour stays available behind
explicit ReanchorTrustedDealer / ReanchorTrustedDealerWithSuite
aliases for HSM / TEE ceremony scenarios.
Reanchor and ReanchorWithSuite now return (*KeyEra, *BootstrapTranscript,
error) — the transcript must be committed to the chain to ratify
the new era. Existing tests TestReanchorOpensNewEra and
TestReanchorMayChangeSuite updated to use t=2,n=3 since the
Pedersen-DKG path requires t < n.
Also drops the 12-iteration Newton's-method mathSqrt in favour of
stdlib math.Sqrt in pathANoiseParameters. The "keeps imports tight"
rationale was false parsimony; stdlib gives the canonical IEEE 754
result without ULP drift. No deployed KAT pins the function output.
Audit doc updated: §0 v0.7.4 closure note, §8.3 caveat updated
(Reanchor closure), §9 references with verified IACR ePrint IDs for
the 2025 citations (2024/959, 2024/1113, 2025/871, 2025/872, 2025/1691)
and explicit [citation TBD] for unverifiable lines rather than
fabricated IDs.
Deployment runbook §Bootstrap-Trust decision matrix expanded to
cover Reanchor; default routing pinned to Pedersen-DKG for both
Bootstrap and Reanchor.
All keyera tests passing:
go test -count=1 -short -timeout 300s ./keyera/
ok github.com/luxfi/corona/keyera 4.132s
Files:
keyera/reanchor_pedersen.go (new)
keyera/reanchor_pedersen_test.go (new)
keyera/keyera.go (Reanchor signature change + aliases)
keyera/bootstrap_pedersen.go (mathSqrt → math.Sqrt)
keyera/keyera_test.go (t=2,n=3 for Pedersen path)
keyera/hashsuite_immut_test.go (t=2,n=3 for Pedersen path)
AUDIT-2026-05.md
DEPLOYMENT-RUNBOOK.md
CHANGELOG.md
LLM.md
Closes the last trusted-dealer caveat in the Bootstrap ceremony. The
trusted-dealer path is retained under a renamed alias for genesis
ceremonies where a non-distributed trust root is acceptable by policy
(publicly observable HSM-bound foundation MPC with commit-and-reveal
entropy from genesis validators).
Construction.
BootstrapPedersen drives the keyera-genesis ceremony through dkg2/
(Pedersen-DKG over R_q with public matrices A, B derived from BLAKE3-XOF
on the nothing-up-my-sleeve tags `corona.dkg2.{A,B}.v1`):
1. Each party samples Gaussian f_i, g_i in R_q^Nvec[x]_{deg<t}, runs
dkg2.Round1 → broadcasts Pedersen commits C_{i,k} = A·NTT(c_{i,k})
+ B·NTT(r_{i,k}) and sends (share_{i→j}, blind_{i→j}) privately
over an authenticated p2p channel.
2. Each recipient j runs dkg2.Round2Identify, verifying every incoming
pair against the commits in constant time. On mismatch the recipient
emits a signed ComplaintBadDelivery naming the misbehaving sender.
3. Each recipient holds its standard-form share s_j of the master
secret s; NO PARTY ever reconstructs s.
4. Path (a) noise flooding (LP-073 §07 Mapping). Each party broadcasts
β_j = A·NTT(λ_j · s_j) + e_j' where e_j' ~ D(σ'') and
σ'' = κ·σ_E·√n is the LP-073 §5 flood bound.
5. All parties aggregate b = Σ_j β_j = A·s + e'' in NTT-Mont; the
Corona-Sign-shaped public key bTilde = Round_Xi(b) drops cleanly
into threshold.GroupKey. The existing 2-round threshold-sign path
accepts the noise-flooded GroupKey unchanged.
Security claims.
- Hiding under decisional MLWE on the wide concatenation [A | B]
(Pedersen-DKG; papers/lp-073-pulsar §07).
- Per-party Gaussian flooding bound σ'' = κ·σ_E·√n covers the LWE
leakage budget (Path (a) flood-bound, LP-073 §5).
- Identifiable abort with constant-time verification (dkg2.Round2-
Identify; subtle.ConstantTimeCompare across all M slots).
- No master-secret reconstruction at any point in the ceremony
(structural; see TestBootstrapPedersen_NoMasterSecretInMemory).
Tests (keyera/bootstrap_pedersen_test.go; all green).
- TestBootstrapPedersen_RoundTrip — 5-party Pedersen-DKG with t=3;
transcript determinism; bTilde stability across entropy replays.
- TestBootstrapPedersen_DishonestDealer — tampered share-to-0 triggers
ErrBootstrapPedersenAbort naming sender 2; AbortEvidence carries a
re-checkable ComplaintBadDelivery.
- TestBootstrapPedersen_FollowedBySign — bootstrap → threshold-sign →
threshold.Verify PASS (the noise-flooded GroupKey is structurally
identical to a trusted-dealer Corona setup).
- TestBootstrapPedersen_NoMasterSecretInMemory — structural assertion
that dkg2.DKGSession exposes no master-secret field and no two
parties' SkShares/Lambdas collide.
- TestBootstrapPedersen_ParameterValidation — bounds checking.
- TestBootstrapPedersen_DefaultSuite — nil suite resolves to Corona-SHA3.
- TestBootstrapTrustedDealer_LegacyAlias — legacy alias byte-equivalent
to historical Bootstrap.
Documentation.
- AUDIT-2026-05.md — read-only SOTA refresh covering threshold lattice
DKG / signing literature 2024-2026. Verdict: Boschini-Takahashi-
Tibouchi 2024/1113 remains canonical; 2025 follow-ups (del Pino,
Doerner-Kondi, Hofheinz, Beimel-Eitan, Bos-Lyubashevsky, Crites-
Komlo-Maller) are complementary or non-blocking; no SOTA refresh
blocks this revision. Path (a) bound stays comfortable for committee
sizes up to n=256.
- DEPLOYMENT-RUNBOOK.md §Bootstrap-Trust — decision matrix documenting
Option A (BootstrapPedersen, recommended) vs Option B
(BootstrapTrustedDealer, ceremony-only).
- CHANGELOG.md — v0.7.3 entry listing the new public surface, tests,
and documentation deltas.
- keyera/keyera.go — inline trust-model documentation pointing at
BootstrapPedersen as the public-BFT-safe alternative.
API summary.
New (keyera package):
- BootstrapPedersen(suite, t, validators, groupID, eraID, entropy)
(*KeyEra, *BootstrapTranscript, error)
- FinishBootstrapPedersen(suite, t, validators, ..., dkgParams,
sessions, round1) (*KeyEra, *BootstrapTranscript, error)
- BootstrapTrustedDealer / BootstrapTrustedDealerWithSuite — legacy
aliases for the single-dealer ceremony.
- BootstrapTranscript — public, byte-stable ceremony record.
- AbortEvidence + ExtractAbortEvidence(err) — identifiable-abort
consumption surface.
- ErrBootstrapPedersenAbort, ErrBootstrapPedersenShape — typed errors.
Wire / KAT compatibility.
- Existing trusted-dealer KATs continue to byte-match
~/work/luxcpp/crypto/corona/ (no behaviour change on that path).
- BootstrapPedersen adds new ceremony bytes; deterministic given a
reproducible entropy stream. A future cross-runtime port can pin
them via BootstrapTranscript.TranscriptHash.
5 oracle tools were writing KATs to hardcoded /Users/z/work/luxcpp/...
paths. All now go through the luxcppDir() helper (LUXCPP_DIR env override,
$HOME/work/luxcpp default), consistent with the lux/CLAUDE.md convention.
Also .gitignore stray oracle binaries from `go build ./cmd/<oracle>`.
Add corona/gpu package — the single, decomplecting point where corona
opts into the lattice library's per-SubRing GPU NTT dispatcher.
Architecture (decomplected).
The lattice library already owns ALL build-tag plumbing for GPU NTT:
ring.SetGPUDispatchers (subring_ops.go) is the canonical hook; the
lattice/gpu package installs it under `cgo && gpu` build tags and
provides a real CPU fallback under !cgo or !gpu. Output is byte-equal
to ring.SubRing.NTT by lattice's own contract.
corona/gpu adds the corona-side bridge: UseAccelerator() flips a
global flag, NewParams() across corona consults the flag via
MaybeRegister and binds each created Ring's SubRings into the lattice
GPU registry. Single source of truth for the opt-in; no build tags
inside corona.
Threshold gating (honest).
Single-poly Metal NTT at corona's production N=256 is roughly 4-6x
SLOWER than pure-Go ring.SubRing.NTT (measured: BenchmarkPulsarSign_
5of7 force-GPU 7.1s vs CPU 1.1s; 14of21 force-GPU 23.5s vs CPU 5.9s).
The GPU win exists only in BATCHED dispatch (many polynomials per
kernel launch), which requires future engine-layer plumbing of
lattice/gpu.MontgomeryNTTContext.Forward(data, batch>=4) bypassing
the per-poly r.NTT() pinch point.
Therefore UseAccelerator() picks defaultThreshold=1024 — above
corona's N=256 — so the SubRing dispatch is armed but does not fire
on single-poly NTT. The registry remains primed for any future batch
caller (e.g. FHE bootstraps in thresholdvm sharing this library).
UseAcceleratorForce() (threshold=1) is provided strictly for the
correctness gate: every NTT call routes through the GPU so the
byte-equality test in threshold/threshold_gpu_test.go exercises the
GPU path end-to-end. Production callers use UseAccelerator() instead.
Byte-equality.
TestThresholdSign_CPU_vs_GPU_ByteIdentical runs the full 2-round
Pulsar signing protocol with GPU dispatch off and forced-on (same
deterministic dealer randomness, same message) and asserts byte-equal
sig.C / sig.Z / sig.Delta. Passes under CGO_ENABLED=0, CGO_ENABLED=1,
and CGO_ENABLED=1 -tags gpu. Existing TestDKG2_GPU_ByteEqual coverage
extends across n=3,5,7,11,21 (production shape).
Wiring.
NewParams() in sign-bound packages — threshold, dkg2, dkg, reshare —
calls corona/gpu.MaybeRegister(r) for the main Q ring. RXi and RNu
are power-of-two moduli; the NTT path is not taken on them.
Tests.
Full corona test suite passes under both build modes:
- CGO_ENABLED=0 go test ./... => all green
- CGO_ENABLED=1 -tags gpu test => all green
5-axiom Lean-to-EasyCrypt correspondence for Corona, mirroring Pulsar's
structure:
lagrange_inverse_eval (Corona_N1.ec)
-> Crypto.Corona.Shamir.shamir_correct_at_target
threshold_partial_response_identity (Corona_N1.ec)
-> Crypto.Threshold.Lagrange.threshold_partial_response_identity
add_share_zeroR (Corona_N4.ec)
-> Mathlib AddCommMonoid instance
reconstruct_linear (Corona_N4.ec)
-> Crypto.Threshold.Lagrange.combine_distributes_over_sum
shamir_correct (Corona_N4.ec)
-> Crypto.Corona.Shamir.shamir_correct_at_target
CI guard `scripts/check-lean-bridge.sh` verifies for every EC axiom
that (1) the axiom still exists as `axiom` (not silently demoted to
`lemma`), (2) it carries an inline citation comment naming the Lean
theorem, (3) the Lean theorem still exists at the named path. It also
checks that every EC file mentioned in the bridge doc exists on disk.
Auto-detects the Lean repo at ~/work/lux/proofs/lean or sibling
locations. Skips Lean-side existence checks if no Lean repo is on disk.
Mirrors ~/work/lux/pulsar/scripts/check-lean-bridge.sh exactly.
Three Tier-A-required submission docs that were honestly omitted in
the v0.5.0 scaffold per the cryptographer agent's directive ("don't
fabricate proof claims that don't exist"):
- AXIOM-INVENTORY.md: enumerates construction-level + implementation-level
axioms with explicit closure plans. EC theory shells are roadmap v0.7.0;
this doc establishes the inventory the eventual proofs will discharge.
- FIPS-TRACEABILITY.md: Boschini ePrint 2024/1113 + IEEE S&P 2025
section → code mapping. R-LWE has no FIPS standard; Boschini paper
is the construction-level normative reference.
- CRYPTOGRAPHER-SIGN-OFF.md: APPROVED WITH GATES. Verdict mirrors
Pulsar's v1.0.7 sign-off structure. Gates: GATE-1 EC theory shells,
GATE-2 Lean ↔ EC bridge, GATE-3 dudect 10⁹ samples, GATE-4 external
audit. All on v0.7.0 / v0.8.0 roadmap.
Tier label honest assessment: **Tier A documentation shape complete**;
the algorithmic-soundness gates inherit from Boschini paper, the
formal-methods gates are explicitly roadmap-tracked, not fabricated.
This positions Corona for NIST MPTC v0.2 submission window with full
review-ready doc package.
Adds the NIST MPTC Class N1 + N4 submission package for Corona (R-LWE
threshold signing). Pattern mirrors luxfi/pulsar (M-LWE byte-equal
FIPS 204 path), adapted HONESTLY to Corona's lighter proof tier:
- SUBMISSION.md / NIST-SUBMISSION.md — cover sheet + 1-page exec
summary. Cite Boschini-Kaviani-Lai-Malavolta-Takahashi-Tibouchi
ePrint 2024/1113 (IEEE S&P 2025) as the underlying construction.
Construction-level N1 claim (no FIPS standard target for R-LWE
threshold — Boschini et al. IS the spec). N4 = (A, bTilde)
preserved across reshare within a key era.
- SPEC.md — standalone construction specification consolidating
existing material (papers/lp-073-pulsar/, DESIGN.md). Full
single-doc spec/corona.tex is roadmap v0.6.0.
- PATENTS.md — royalty-free patent grant + defensive termination.
Claim scope EXCLUDES the published Boschini et al. construction;
claims limited to Corona's production lifecycle additions
(Pedersen DKG over R_q, proactive resharing, activation cert
circuit-breaker, constant-time Pedersen slot equality, hash-suite
injection).
- PROOF-CLAIMS.md — HONEST framing. Corona ships NO mechanized
refinement (no EasyCrypt, no Lean, no Jasmin) — unlike Pulsar.
Correctness reduces to code review + KAT cross-runtime byte-equality
+ academic construction analysis + per-path constant-time static
audit. Mechanized refinement is roadmap v0.7.0.
- TRUSTED-COMPUTING-BASE.md — implementation TCB. Structurally
simpler than Pulsar's; no EC/Lean/Jasmin tools in scope.
- DEPLOYMENT-RUNBOOK.md — operator-facing trust-model disclosure.
- LICENSING.md / CONTRIBUTING.md / SECURITY.md — standard NIST-MPTC
artifacts.
- docs/mptc/ — submission-package docs subdir (kept separate from
docs/ Next.js docs site):
- evaluation.md, ietf-draft-skeleton.md, nist-mptc-category.md,
patent-claims.md, design-decisions.md, family-architecture.md,
threat-model.md
- scripts/{build,test,bench,gen_vectors,check-high-assurance,
cut-submission}.sh — submission gates mirroring Pulsar's
orchestrator pattern, adapted to Corona's reality (no EC/Lean/
Jasmin checks because the artifacts do not exist).
- LLM.md + CHANGELOG.md — updated with submission scaffolding
context and v0.5.0 entry. NO fabricated prior history; CHANGELOG
v0.5.0 entry covers only the actual submission documentation.
DOES NOT FABRICATE: no CRYPTOGRAPHER-SIGN-OFF (Pulsar has one;
Corona's lighter proof tier does not yet warrant it — roadmap
v0.7.0+v0.8.0). No AXIOM-INVENTORY or FIPS-TRACEABILITY (Corona is
not anchored to a FIPS standard). No proof claims Corona does not
actually have.
Build/test verification: GOWORK=off go build ./... clean; sign/,
primitives/, hash/ unit tests pass; scripts/build.sh + scripts/
check-high-assurance.sh exit 0.
Single LICENSING.md file referencing the canonical three-tier IP and
licensing strategy at github.com/luxfi/.github/blob/main/profile/README.md.
LICENSE file is unchanged; this only adds a navigational pointer.
Corona signatures are Ring-LWE (C, Z, Delta) ring-polynomial triples,
not FIPS 204 byte-equal (that's Pulsar's Module-LWE sibling). Single
Verify is the only place this package touches the sign.Verify
predicate; VerifyBatch composes it across N (groupKey, message,
signature) tuples in parallel via a GOMAXPROCS-bounded worker pool.
Public surface added:
- VerifyBatch(gks, msgs, sigs) -> ([]bool, error)
- VerifyBatchAll(gks, msgs, sigs) -> (bool, error)
- ErrBatchSizeMismatch
Pure Go: NO CGO / accel dep. A future Metal/CUDA NTT-batch kernel
over the polynomial-arithmetic hotspot would live in luxfi/accel
behind the same byte interface; consumers wanting GPU dispatch call
accel directly against the wire bytes. This file is the portable
fallback everyone gets for free.
Tests (GOWORK=off go test ./threshold/ -run TestVerifyBatch):
TestVerifyBatch_AllValid (n=4, all valid) PASS
TestVerifyBatch_OneCorrupt (message swap @1) PASS
TestVerifyBatch_StructuralMismatch (slice len mismatch) PASS
TestVerifyBatch_Empty PASS
Full ./threshold suite: ok 2.068s.
Per user directive: don't mix Pulsar and Corona naming. Corona's design
doc was documenting Pulsar tag prefixes for what are actually Corona's
own rows in the Quasar consensus tag scheme. Fixed:
QUASAR-PULSAR-BUNDLE-v1 -> QUASAR-CORONA-BUNDLE-v1
QUASAR-PULSAR-SIGN1-v1 -> QUASAR-CORONA-SIGN1-v1
QUASAR-PULSAR-SIGN2-v1 -> QUASAR-CORONA-SIGN2-v1
QUASAR-PULSAR-COMBINE-v1 -> QUASAR-CORONA-COMBINE-v1
QUASAR-PULSAR-REFRESH-v1 -> QUASAR-CORONA-REFRESH-v1
QUASAR-PULSAR-RESHARE-v1 -> QUASAR-CORONA-RESHARE-v1
QUASAR-PULSAR-REANCHOR-v1 -> QUASAR-CORONA-REANCHOR-v1
Plus Lane column for these rows: Pulsar -> Corona.
Preserved legitimate Pulsar cross-references: sister-kernel comparison,
fork lineage (Corona forked from Pulsar), shared LSS lifecycle concepts.
Those documentation references aren't mixing — they're describing
Corona's relationship to Pulsar.
Scientist audit flagged stale Pulsar references in CHANGELOG.md (e.g.
'Pulsar-SHA3' production default name, 'pulsar/primitives/hash.go' paths).
The Go code was already renamed in commit a2c1d1d; this aligns the docs.
DESIGN.md's QUASAR-PULSAR-*-v1 prefix table is preserved — those are
Quasar consensus lane prefixes describing Pulsar pulse messages, NOT
Corona-internal tags. Different scope.
Leftover from Corona's "Pulsar-R" lineage. Pulsar (M-LWE) and Corona
(R-LWE) are independent constructions with separate hardness assumptions,
so their cSHAKE personalisation strings must be distinct.
Changes (Go, non-luxcpp):
- hash tags: PULSAR-HC-v1 -> CORONA-HC-v1, etc. (HU, TRANSCRIPT, PRF, MAC, PAIRWISE)
- profile IDs: "Pulsar-SHA3" -> "Corona-SHA3", "Pulsar-BLAKE3" -> "Corona-BLAKE3"
- context strings: pulsar.dkg2.A.v1 -> corona.dkg2.A.v1, etc.
- env vars: PULSAR_RESHARE_KAT_PATH -> CORONA_RESHARE_KAT_PATH, etc.
- struct names: pulsarSHA3 -> coronaSHA3
- KAT derive roots: sign_e2e_pulsar -> sign_e2e_corona
What's preserved (different scope):
- luxcpp/crypto/pulsar/* path references in comments (separate repo,
out of scope; the C++ side will rename in its own commit)
- Cross-runtime KAT files on disk (will regenerate next CI run)
All 11 packages test green: dkg, dkg2, hash, keyera, networking,
primitives, reshare, sign, threshold, utils, wire.
After this commit zero 'corona' / 'Corona' / 'CORONA' references
remain in any Go file, Markdown doc, or TeX source in the corona
repository. The library is referenced by its production name (Corona)
everywhere; the upstream R-LWE construction is cited by author
(Boschini et al, ePrint 2024/1113) where attribution is required.
Renames:
cmd/corona_oracle_v2/ → cmd/corona_oracle_v2/
doc paths to luxcpp/crypto/corona → luxcpp/crypto/corona
coronaThreshold (import alias in INTEGRATION.md) → coronaThreshold
wrapAsCoronaShares → wrapAsCoronaShares
CONSTANT-TIME-REVIEW.md, INTEGRATION.md, oracle cmd comments — all
prose references swept Corona → Corona.
Corona (Ring-LWE) and Pulsar (Module-LWE) are independently usable
threshold ML-DSA libraries — no shared types, no import line between
them. Each can be selected as a chain's sole PQ threshold layer.
The academic Corona upstream (Boschini et al, ePrint 2024/1113) is
referenced as luxfi/nasua (historical reference fork, trusted-dealer
DKG, not for public-chain use). Corona is the production track that
adds the lifecycle the academic fork lacks (Pedersen DKG over R_q +
proactive resharing + identifiable abort).
Lux primary-network QuasarCert layers both lattice families as a
Double-Lattice PQ defence with optional BLS classical fast-path and
per-validator ML-DSA-65 rolled up via STARK/FRI through P3Q.
The academic 2-round R-LWE threshold construction was renamed Corona
→ Nasua at the family taxonomy level. Update Corona's documentation
references to point at the new name. Corona itself imports nothing
from luxfi/nasua — the dependency line is on luxfi/lattice/v7 for the
ring/sampling primitives — these comment refs only name the conceptual
upstream for design provenance.
This repository owns the former luxfi/pulsar v0.1.x Ring-LWE code line.
v0.2.0 is the first post-split Corona release. The Module-LWE sibling
library moved to luxfi/pulsar and starts at v1.0.0.
The 'Pulsar' brand name retained in the Quasar consensus protocol
refers to the Module-LWE library; this repository's brand is Corona —
the luminous ring around the central Pulsar/Quasar light.
Repo forks from luxfi/pulsar (R-LWE production threshold for Lux
consensus) to luxfi/corona under the family rename. Same Ring-LWE
code, same hash family (Pulsar-SHA3 / Pulsar-BLAKE3 -- wire-format
customization tags stay stable per MED-2). Only the Go module path
and internal imports change.
Lux family alignment:
- Pulsar = M-LWE / FIPS 204 / NIST MPTC N1 (was luxfi/pulsar-m)
- Corona = R-LWE / NIST MPTC S1 (this repo)
- Corona = academic upstream + Lux DKG expansions
Records the Pulsar/Corona rename context: this repo (R-LWE,
production fork) becomes luxfi/corona; luxfi/pulsar-m (M-LWE, FIPS
204) becomes the canonical luxfi/pulsar. No code change here — wire
formats and consensus paths are unchanged until the cross-repo moves
land. v0.1.x tags continue to point at R-LWE code; new consumers
should pin v0.2.0+ on the renamed repo.
First post-bump CI run still failed with one stdlib vuln:
GO-2026-4971 net.Dial / net.LookupPort NUL-byte panic on Windows
Fixed in: net@go1.26.3
The previous bump to 1.26.2 cleared 4 of 5 reported CVEs but missed
this one (referenced via networking.DialTCP / networking.ListenTCP).
Bumping to the latest 1.26.x patch closes it. Still patch-bump only;
no minor / major change.
Whitespace-only struct-tag alignment changes. Closes the CI Lint gate
that was failing across multiple recent commits on otherwise-green
build+test runs.
primitives/hash.go: PRNGKey / GenerateMAC / GaussianHash / PRF / Hash /
LowNormHash all take HashSuite as first param. suite == nil resolves to
the production default Pulsar-SHA3 (cSHAKE256 / KMAC256 / TupleHash256
per FIPS 202 + NIST SP 800-185).
sign/sign.go: Party gains Suite field; NewParty defaults to
hash.Default(); NewPartyWithSuite for operator-explicit construction.
sign.Verify keeps its signature for callers and resolves the default
internally; the suite-explicit form is sign.VerifyWithSuite.
reshare/commit.go: suite-aware commit derivation aligned with the new
hash.HashSuite contract used by the rest of pulsar/reshare/dkg2/keyera.
Closes the gap where the HIP-0077 claim that Pulsar uses SHA-3
cSHAKE256/KMAC256/TupleHash256 in production was structurally false at
the Sign layer (only pulsar/reshare/, pulsar/dkg2/, pulsar/keyera/ were
consuming pulsar/hash/HashSuite previously). Pulsar-BLAKE3 retained as
legacy/non-normative only for cross-port byte checks.
go.mod: luxfi/math v1.3.0 -> v1.4.0 (consumes the codec for the new
hash-suite tag).
CHANGELOG.md added. sign_roundtrip_test.go pins the new contract.
Tests: primitives + sign + reshare PASS. Patch-bump.
wire/wire.go — pulsar's wire-format hardening boundary. Untrusted
lattice Vector[Poly] frames now flow through luxfi/math/codec.Reader
so the bounded-decode contract is centralized: no recursion, no
hidden growth, no unbounded allocation.
Replaces the test-only validateVectorPolyFrameInline walker that
lived at threshold/fuzz_round_test.go with a production-grade
equivalent on the canonical luxfi/math/codec substrate.
Tests:
* TestValidateVectorPolyFrame_RejectsHugeLength — regression for
lattice issue #4 (70-trillion-element attack input). Same input
the original fuzz finding produced, now rejected with
codec.ErrLimitExceeded via the shared math substrate.
* TestValidateVectorPolyFrame_HappyPath — 3-element accept.
* TestValidateVectorPolyFrame_AtCap — exactly MaxLatticeUintSliceLen
accepted.
* TestValidateVectorPolyFrame_OverCap — MaxLatticeUintSliceLen+1
rejected.
go.mod bumps luxfi/math v1.2.4 -> v1.3.0 (the LP-107 substrate
release with codec/ + params/ + backend/ + modarith/ + ntt/ + poly/
+ rns/ + sample/).
This is the first downstream consumer of the math substrate. Lens,
FHE, and lattice consumers follow.
cmd/sign_oracle/ — emits the canonical Pulsar sign+verify KAT JSON
(Gen + SignRound1 + SignRound2{Preprocess,} + SignFinalize + Verify) for
(t, n) ∈ {2/3, 3/5, 5/7, 7/11}. The C++ port at
luxcpp/crypto/pulsar/cpp/sign/ replays these entries byte-equal.
cmd/cross_runtime_oracle/ — Go writer for the cross-runtime release
gate. Emits a SHA-256 manifest tying together the three canonical
Pulsar KATs (sign, reshare, dkg2). C++ side at
luxcpp/crypto/pulsar/test/cross_runtime_test.cpp replays each KAT and
checks byte-equality (forward direction: 30/30 PASS).
cmd/cross_runtime_verify/ — Go reader for the reverse direction
(C++ writes, Go verifies). Currently 3/3 PASS against the SHA-256s
emitted by luxcpp/crypto/pulsar/cmd/cross_runtime_oracle/.
threshold/testdata/fuzz/FuzzPulsarSign1Round1Data/ — corpus seed for
the in-tree fuzz harness; mitigates the second lattice DoS (issue #4)
where Vector[T].ReadFrom calls make([]T, size) with no bound.
23 fuzz harnesses × 30s = 10.9M execs, 0 panics.
FuzzPulsarSign1Round1Data found a 9-byte input that crashes the
goroutine with `fatal error: out of memory: cannot allocate
105589698985984-byte block` in `lattice/v7/utils/structs.Vector[T].
ReadFrom`. Recovery cannot catch the fatal OOM — the only safe path
is to reject malformed frames before `make([]T, size)` runs.
This is a SECOND DoS surface in lattice/v7, distinct from the
ReadUintNSlice recursion fixed by luxfi/lattice#3 (still open):
ReadFrom calls `make([]T, size)` with `size` read from the wire
before any bound check. PR #3 fixes the inner slice readers but the
outer Vector wrapper allocation remains exploitable.
Filed upstream as luxfi/lattice#4 with reproduction. The new
validateVectorPolyFrameInline walker mirrors the existing
warp/pulsar.validateVectorPolyFrame: walks the wire frame
end-to-end, asserts every length field is <= MaxLatticeUintSliceLen
(4096), and rejects truncated / over-large frames before the
lattigo decoder runs.
Result: FuzzPulsarSign1Round1Data passes 30s with the new walker
in place (28577 execs, 0 panics). Failing input
testdata/fuzz/FuzzPulsarSign1Round1Data/a80b8d313b40fa55 is now
rejected cleanly with "vector length 70368955777453 exceeds 4096".
Refs: luxfi/lattice#2 (open), luxfi/lattice#3 (open),
luxfi/lattice#4 (filed 2026-05-04)