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.
One-and-one-way completion: Pulsar AND Corona now both consume the single
github.com/luxfi/mlwe base (v0.2.1) for the shared Module-LWE primitives.
Corona remains Module-LWE (Ringtail/Raccoon line).
WHAT THIS MERGES (transcript + Lagrange routing ONLY — no scope creep):
- hash/sha3.go: cSHAKE256 / KMAC256 / TupleHash256 / EncodeString now
vend from mlwe/transcript (the single SP 800-185 surface). Corona keeps
ONLY its domain-separation tags. hash/sp800_185.go (the local primitive
copy) is deleted; its NIST primitive vectors now live in mlwe/transcript.
- primitives/shamir.go: ComputeLagrangeCoefficients delegates field
arithmetic + interpolation to mlwe/share (NewPrimeField + Lagrange@0);
Corona keeps the adapter lifting each scalar into the R_q ring.Poly.
DELIBERATELY NOT ROUTED (kept byte-incompatible / audited):
- Lattigo 48-bit ring stays Lattigo (re-rolling audited Lattigo forbidden).
- The seeded Shamir deal (ShamirSecretSharing) stays Corona/Pulsar-specific
(its sampler is byte-incompatible with mlwe.Split).
CORRECTNESS GATE — Go byte-parity (PROVEN, this is the gate):
Every Corona KAT oracle was run on the pre-refactor baseline (main 17ac4f3,
Corona-local impls) and on this branch (mlwe v0.2.1 routed), and every
emitted byte is identical: 19/19 KAT files byte-identical old==new
(recursive diff clean + sha256 manifest match), covering the exact files
this change touches (corona_oracle_v2 transcript_hash, shamir_share,
sign_verify_e2e) plus the full oracle surface (reshare, activation, dkg,
sign, shamir_general, shamir_tk, transcripts_byte, m4_* samplers).
e2e threshold variants (3,2),(5,3),(7,4),(10,7) pass; go build ./... and
go test ./... green across all packages.
mlwe v0.2.0 -> v0.2.1 is byte-safe by source inspection: the only delta
between those tags is added *_bench_test.go files (test-only, never
compiled into the consuming binary); zero production bytes changed in
mlwe/transcript or mlwe/share. The oracle parity above confirms this
empirically.
CI-GATE NOTE — C++ cross-runtime byte gate is a POST-MERGE CONFIRMATION,
not a merge blocker:
cmd/cross_runtime_verify (requires luxcpp cmake/conan/blst) runs in CI as
a post-merge confirmation. The Go byte-parity above is the correctness
gate and is sufficient to merge, by this transitivity argument:
- Old Go bytes == New Go bytes (proven: 19/19 byte-identical).
- The C++ runtime and the Go runtime share NO build inputs — the C++
port does not depend on go.mod / luxfi/mlwe at all.
- Therefore this dependency change CANNOT shift any C++ byte, and the
C++<->Go KAT relationship is INVARIANT under this change: whatever
C++==Go (or C++!=Go) held before, holds identically after.
So the post-merge C++ run can only re-confirm the pre-existing C++<->Go
state; it cannot regress as a result of this Go-only de-dup.
PRE-EXISTING DRIFT (documented, NOT introduced here, NOT a crypto-byte diff):
The sign_verify_e2e KAT's prose "description" field references
threshold.GenerateKeysTrustedDealer (a ~13-byte wording difference vs a
plain "GenerateKeys" phrasing) — a Go<->C++ description-string mismatch in
the cross-runtime comparison, orthogonal to cryptographic bytes. It
pre-dates this branch; sign_verify_e2e.json is sha256-identical old==new
here, so this change leaves it exactly as it was.
Tag: v0.10.2
Byte-preserving de-dup: Corona's SP 800-185 transcript surface
(hash/sha3.go; deletes the local hash/sp800_185.go) and
ComputeLagrangeCoefficients (primitives/shamir.go) now consume the
shared github.com/luxfi/mlwe base — the single Module-LWE primitive
surface for the Lux stack (Corona + Pulsar).
Routed onto mlwe:
- hash/sha3.go: cSHAKE256 / KMAC256 / TupleHash256 / EncodeString
-> github.com/luxfi/mlwe/transcript. Corona retains ONLY its
domain-separation tags; the byte encodings live in mlwe/transcript.
- primitives/shamir.go: ComputeLagrangeCoefficients delegates the
field arithmetic + interpolation to github.com/luxfi/mlwe/share
(NewPrimeField + Lagrange at point 0); Corona keeps the adapter
that lifts each scalar into the R_q constant-term ring.Poly.
Deliberately NOT routed (byte-incompatible / audited):
- Lattigo 48-bit ring stays Lattigo (re-rolling audited Lattigo forbidden).
- The seeded Shamir deal (ShamirSecretSharing) stays Corona-specific
(its sampler is byte-incompatible with mlwe.Split).
Corona remains Module-LWE (Ringtail/Raccoon line).
Byte-parity gate: every Corona KAT oracle emits byte-identical output
old (Corona-local impls) == new (mlwe-routed). Go byte-parity is the
correctness gate; the C++ cross-runtime byte gate is a post-merge CI
confirmation (see merge commit CI-GATE note).
- LLM.md: Latest Tag -> v0.10.0; record the no-reconstruct signing gate +
v0.9.0 DKG cutover in the recent-commits table.
- PROOF-CLAIMS.md: catalogue the new code-level conformance gate alongside
the Lean-4 secret_aggregate_no_reconstruct proof, framed honestly as a
TEST gate (not a proof-assistant artifact).
Audit verdict (branch audit/no-reconstruct-signing): Corona threshold signing
is no-reconstruct — each party emits z_i = R_i*u + maskPrime_i + c*lambda_i*s_i
- mask_i from its OWN share; SignFinalize sums the masked partials z_sum = Sum
z_j = R*u + c*s; the secret s = Sum lambda_j*s_j appears only as the coefficient
of the public challenge c inside the masked aggregate and is never materialised.
Dealerless keygen (keyera.Bootstrap) + no-reconstruct signing => strict
trustless lane for STRICT_DUAL_PQ / POLARIS_MAX.
Structural + behavioural proof that the Corona threshold SIGNING path never
forms the full secret s, even transiently at sign time:
GATE A (structural, go/ast): every signing/verify function body is scanned;
none calls an across-party reconstruction primitive (reconstruct /
interpolate / combineShares / recover* / ShamirSecretSharing /
lagrangeAtZero / selectQuorum) and none invokes the trusted-dealer keygen
(Gen / GenerateKeysTrustedDealer). Per-party Lagrange (party.Lambda on the
party's OWN share) is the legitimate no-reconstruct mechanism and is NOT
forbidden. Negative-control verified: injecting a reconstruct call into
SignFinalize trips GATE A.
GATE B (behavioural, reflect): Round1Data/Round2Data/Signature carry no
share/sk/seed/lambda; Finalize ingests only map[int]*Round2Data (masked z
partials) — the aggregator never holds a second party's secret.
GATE C (independent verifier): a real (3,5) no-reconstruct ceremony, each
Signer from exactly ONE share, verifies under the bare Corona verifier
(public data only) and the stateless wire verifier; tamper rejected.
Verdict: Corona is a STRICT trustless lane — dealerless keygen (keyera.Bootstrap)
+ no-reconstruct signing. Sub-quorum-cannot-forge is pinned separately by
TestThresholdSubQuorumCannotForge.
The Pedersen-VSS DKG share-dealing now runs entirely on github.com/luxfi/dkg
(proven byte-identical at cutover); corona's own copy is dead. Remove it and its
now-orphaned tooling, keeping only corona-specific code (Ringtail signing, β
rounding, Path (a) finalize — all in sign/, utils/, keyera/, untouched).
Deleted:
- dkg2/ (the whole package: Round1/Round2/commits/complaint/GPU dispatch).
- cmd/dkg2_oracle/ (the dkg2 KAT generator; the DKG KAT now lives in
luxfi/dkg's own ring/vss vectors).
- keyera/cutover_gate_test.go (the transitional dkg2-vs-vss equality gate; its
job is done — permanent byte-stability is pinned by the golden KAT, and the
PIN-4 convention assertion is preserved dkg2-free in pin4_convention_test.go).
Updated:
- cmd/cross_runtime_oracle drops the dkg2 manifest leg (the luxcpp C++ dkg2
cross-runtime gate migrates to luxfi/dkg separately — flagged, not silently
broken).
- keyera/reanchor doc comments + threshold bench comment point at luxfi/dkg.
go build ./... + go test ./... -count=1 green (13 ok / 0 fail), go vet + gofmt
clean. The golden byte-stability KAT + Bootstrap→sign→verify + dishonest-dealer
abort all still pass on the vss path.
Rewire BootstrapPedersen's no-reconstruct share-dealing onto the shared
github.com/luxfi/dkg library (vss + blame + ring.Ringtail()), dropping the
in-keyera dependence on corona/dkg2. corona keeps ONLY its scheme-specific
finalize: Path (a) noise flooding + Round_Xi, which turns the per-party secret
shares s_j into the LWE-shaped Ringtail public key bTilde = Round_Xi(A·s+e'').
The vss Pedersen aggregate T = A·s1+B·u is NOT corona's group key (its B·u term
is not a small LWE error), so vss is used purely for the no-reconstruct share
distribution + identifiable blame.
Mechanism:
- BootstrapPedersen draws per-party sampling seeds from the ceremony entropy
(same sequencing as before) and threads each into vss.Party.Round1, so the
χ draws — hence the Pedersen commits and the secret shares — are byte-
identical to the prior dkg2 path (cutover gates B/C/E).
- DealPedersen runs Round 1 + opens every sealed envelope; FinishBootstrapPedersen
runs Round 3 (Pedersen verify + aggregate) then Path (a) + KeyShare assembly.
The two-phase split is the test seam for identifiable abort (tamper an opened
share, observe ErrBootstrapPedersenAbort + re-checkable blame).
- Commit digests are recomputed under the corona HashSuite over the lattigo
wire bytes of vss's commits — byte-identical to dkg2's CommitDigest — so the
noise seed and transcript hash are preserved.
- AbortEvidence now carries luxfi/dkg blame.Complaint (accused-by-NodeID,
third-party re-checkable via vss.RecheckBadDelivery); the in-process channel
identities are derived deterministically and affect only the (immediately-
opened) envelope ciphertext, never any byte-stable output.
Proof: the frozen golden KAT (bTilde / transcript / shares digests for 5/3 and
3/2) PASSES against the vss path — the group key, transcript, and shares are
preserved byte-for-byte. Determinism, Bootstrap→sign→verify, dishonest-dealer
abort (+ RecheckBadDelivery), reanchor round-trip/abort, and the no-master-secret
structural checks all green. Public API (Bootstrap/KeyEra/EpochShareState/
BootstrapTranscript/GroupKey/KeyShare) unchanged.
Capture the group key (bTilde), transcript hash, and per-validator shares from
the dkg2-backed BootstrapPedersen at the cutover point, as frozen SHA-256
digests for two committee shapes (5/3, 3/2). This is the permanent byte-for-byte
target the luxfi/dkg rewire must reproduce — proving the group-key convention,
transcript, and shares are PRESERVED (not re-baselined) when the no-reconstruct
share-dealing moves to vss while corona keeps its own Path-(a) finalize.
Phase 3a step 1: require github.com/luxfi/dkg v0.2.0 and prove, before any
rewire, that the shared library's ring.Ringtail() binding reproduces
corona/dkg2's KAT-pinned arithmetic byte-for-byte.
Gate (cutover_gate_test.go), all green:
(A) ring.Ringtail() A,B == dkg2.DeriveA/DeriveB over all 8x7 slots.
(B) vss's REAL Party.Round1 commits == dkg2.Round1WithSeed commits for the
same 32-byte sampling seed (exercises vss computeCommits, not a re-impl).
(C) vss Horner shares (ring.ScalarMulVec/VecAdd) == dkg2 big.Int Horner shares
for every recipient — the Barrett-vs-bigint paths agree on the canonical
representative in [0,q).
(D) PIN 4 pinned to ground truth: dkg2.Round2 b_ped (IMForm+INTT) = vss group
commit T (INTT-only) scaled by R^{-1}; the two differ by EXACTLY the
Montgomery factor R (MForm(b_ped)==T). corona's PRODUCTION key is NEITHER:
keyera Path-(a) noise flooding multiplies NTT-Mont operands so the R is
correctly cancelled, yielding the TRUE A·s+e'' — convention-aligned with
vss's TRUE T. dkg2's R^{-1}-scaled b_ped is an internal Round2 value the
production path discards, so the rewire (keep corona's finalize, use vss
for share-dealing) preserves the group key with NO convention change.
The prior pass swung too far: it answered "does running both give diversity?"
with a flat "No, both are Module-LWE." That under-credits the real security.
Accurate, layered claim now installed in design-decisions / README / SUBMISSION /
nist-mptc-category:
- Pulsar and Corona are distinct SCHEMES at different PARAMETER regimes
(Pulsar = FIPS-204 ML-DSA deterministic FS-with-aborts, q=8380417; Corona =
Ringtail/Raccoon masked threshold, q=0x1000000004A01, params tuned for
threshold). In AND-mode an adversary must forge BOTH; a cryptanalytic,
parameter-specific, or implementation flaw in one does not transfer — the pair
is strictly stronger than either leg alone ("both have to be cracked").
- What it is NOT: hardness-ASSUMPTION diversity. Both rest on Module-LWE/SIS, so a
fundamental Module-LWE break threatens both lattice legs; assumption-disjoint
diversity is the hash-based Magnetar (SLH-DSA) leg.
Docs-only; go build green; no shims.
Corona is Module-LWE (threshold-Raccoon/Ringtail), NOT Ring-LWE. Confirmed in code: sign/sign.go samples A in R_q^{8x7} (sign.go:87 SamplePolyMatrix M=8,N=7), secret s a rank-7 ring vector, b=A*s+e (sign.go:106-107) over R_q=Z_q[X]/(X^256+1) (LogN=8), q=0x1000000004A01 -- rank>1 module structure, not rank-1 ring-LWE. threshold/threshold.go:4-5 already said Module-LWE.
Fixed the family label across README, SPEC, SUBMISSION, NIST-SUBMISSION, PATENTS, LLM, CONTRIBUTING, SECURITY, CHANGELOG, DEPLOYMENT-RUNBOOK, Makefile, AXIOM-INVENTORY, PROOF-CLAIMS, BLOCKERS, CRYPTOGRAPHER-SIGN-OFF, FIPS-TRACEABILITY, TRUSTED-COMPUTING-BASE, AUDIT-2026-05/06, docs/mptc/*, jasmin/README, 3 Go comments + cli help string.
Also corrected the downstream false claim that Corona (Ring-LWE) and Pulsar (Module-LWE) are 'different lattice families' giving 'structural/family diversity': both are Module-LWE, so the Double-Lattice defense is construction/implementation diversity (threshold-Raccoon vs ML-DSA), not hardness-family diversity; a Module-LWE break affects both legs (hash-based Magnetar is the assumption-diversifier). Fixed companion R-SIS -> Module-SIS where it was the module scheme's SIS assumption; added the Langlois-Stehle (DCC 2015) Module-LWE citation alongside LPR 2010.
Delicate artifacts: proof identifiers (rlwe_sign_op, RLWE_Functional theory/file, *_eq_rlwe bridges, CentralRLWESign, RLWESign, rlwe_compute_*) were NOT renamed -- EasyCrypt/jasmin toolchains are absent here so a rename cannot be compile-verified, and .assurance/ gates parse those names. Added clarifying naming notes to the 5 prominent .ec files, proofs/easycrypt/README.md, AXIOM-INVENTORY.md, and jasmin/rlwe/sign.jazz. .assurance/*.txt left untouched (already say Module-LWE/Module-SIS; rlwe tokens are gate-parsed identifiers). Left AUDIT-2026-06.md:463 historical rename-log and the ProtoStar-LWE paper-title substring untouched.
go build ./... and go test ./... green (GOWORK=off; all packages ok).
dkg2/BootstrapPedersen (dealerless Pedersen DKG over R_q = Z_q[X]/(X^256+1)) is the production keygen default since v0.7.5; signing never reconstructs the master secret (SignFinalize sums partials). Corona carries the permissionless / no-trusted-dealer guarantee in the Quasar AND-mode dual-PQ cert that Pulsar's byte-FIPS-204 keygen cannot (a dealerless sum of FIPS secrets breaks ML-DSA's S_eta bound). README/LLM/BLOCKERS refreshed; stale v0.7.4 / WIP -> v0.8.0 / Production. Not FIPS byte-equal; no fabricated cert-size figure.
Part of the Lux threshold-crypto security rip (killing trusted-dealer /
full-key-reconstruction footguns).
- Rename threshold.GenerateKeys -> threshold.GenerateKeysTrustedDealer.
The function samples the full secret and sets sign.K / sign.Threshold in
one process: it IS the trusted dealer. The new name mirrors
keyera.BootstrapTrustedDealer so the trust model is explicit and
greppable. Doc comment now states it is for test/KAT/CT/CLI/oracle use
only, and that production chain keygen uses keyera.Bootstrap (dealerless
Pedersen DKG). Stays exported; every caller updated (cli, ct/dudect,
same-package tests) plus stale comment refs in cmd, keyera, wire,
reshare.
- Delete utils.ReconstructSecret. It Lagrange-interpolates the full secret
from shares and had zero callers anywhere in the repo: a
reconstruction-shaped footgun left dead. CompareSecrets and the section
retained.
- Include threshold/minority_soundness_test.go: an adversarial negative
pinning that a strict sub-quorum cannot assemble a signature that
verifies under the group key (uncancelled PRF mask fails the L2-norm
gate). A sub-quorum forgery would be a catastrophic finality break.
The EC byte-equality (corona_n1_byte_equality) proved the threshold combine
bit-equals CombineAbs, whose sign_abs_op_lifted_eq_rlwe bridge pins it to
rlwe_sign_op on the LAGRANGE-RECONSTRUCTED master secret -- exactly the path
the Boschini/Raccoon production combine must never take (CORONA-EC-RECON-MODEL).
This pass re-scopes that cone as IDEALISED CORRECTNESS and adds the HONEST
production model whose residual is a STANDARD Module-LWE/MSIS reduction.
New proofs/easycrypt/Corona_N1_NoLeak.ec (written; machine-recheck pending
EasyCrypt -- no `ec` on host, scripts/checks/ec-compile.sh is the CI gate):
- mask_telescope_zero [B, Lean-backed]: Sum_i (maskPrime_i - mask_i) = 0 --
the pairwise-PRF masks (z_i = R_i*u + maskPrime_i + c*lambda_i*s_i - mask_i)
are the same double sum reindexed (Fubini), so they cancel on aggregation.
Machine-checked in Lean as pairwise_mask_telescopes.
- no_leak_z_aggregate: the surviving aggregate is R*u + c*s by Lagrange-at-0
WITHOUT forming the master secret and with no per-party c*s_i ever exposed.
- no_leak_reduction [C-standard, OPEN]: under Module-LWE + Module-SIS the
public transcript leaks nothing about s beyond one Boschini signature. The
honest replacement for sign_abs_op_lifted_eq_rlwe -- a reduction to the SAME
substrate AXIOM-INVENTORY.md §1 already lists, secret NEVER reconstructed.
The CORRECTNESS CORE is machine-checked in Lean 4 + Mathlib on this host
(luxfi/proofs: Crypto.Corona.NoLeakAggregate / Threshold_Lagrange; lake build
green, #print axioms shows no sorryAx).
Disclosure updated to match reality: AXIOM-INVENTORY (new Bucket C-standard +
"A is machine-checked in Lean"), PROOF-CLAIMS (two models, §0 Disclosure 1),
BLOCKERS (CORONA-EC-RECON-MODEL re-scoped, first criterion now [x]), budget
AXIOM 56->58. Framework gate PASS (all 5 checks); go build + full test suite green.
reshare.Verify Lagrange-interpolates the input shares at X=0 and RETURNS the
reconstructed master secret to its caller (its own doc: "MUST NOT be used in
production"). Its only non-test caller is cmd/reshare_oracle (a KAT generator).
Left in the production package with no boundary, it is a latent master-key
footgun.
Fix (surgical split, not whole-file tag): reshare.go also defines the
production Reshare/Refresh/Share that keyera imports, so the file cannot be
tagged out. Move ONLY Verify into verify_reconstruct.go behind
//go:build corona_research; its shared interpolation helpers (selectQuorum,
lagrangeAtZero, sortedKeys) stay in reshare.go because production uses them.
Gate the sole non-test caller cmd/reshare_oracle/main.go with the same tag.
Tests: the reshare/refresh tests that reconstruct the secret (via Verify) move
into reshare_research_test.go / refresh_research_test.go / the tagged
integration_test.go and run under -tags corona_research. The production-
behaviour tests that do NOT reconstruct (determinism, arg validation, t=1
identity, fuzz, transcript) stay in the default build — no coverage lost.
Generic test helpers shared across both builds (cloneVector, runRoundsAndVerify)
move to untagged clone_test.go / signrounds_test.go.
Verification:
- default `go build ./...` exits 0; reshare.Verify ABSENT from the default
build (proven: a Verify-calling test does not compile into the default run).
- default `go test ./...`: 0 failures; production tests run.
- `go test -tags corona_research ./...`: green; reconstruction tests + oracle
compile and run (TestReshareSecretInvariant etc. pass).
- gofmt + vet clean in both build modes.
- security/framework/check.sh corona: PASS (no CLOSED-but-reconstructs blocker).
Reclassify (not reduce) the EasyCrypt axiom surface and correct the prior
inventory's understatement. NO axioms discharged this pass (honest): no
easycrypt toolchain on host, and every in-file-derivable axiom is already a
proved lemma; residual A/B axioms are over abstract ops/constants
(op group_pk_width : int., op sig_len : int.) and the C axioms are open.
- AXIOM-INVENTORY.md: replace the §2 table that mislabelled the wrapper
bridges and section-local module contracts as "Discharged in the
Wrapper". They are now correctly bucket C / OPEN: combine_body_spec hides
the Boschini combine steps 2-6, and combine_abs_op_lifted_bridge /
sign_abs_op_lifted_eq_rlwe pin the lifted op to rlwe_sign_op on the
Lagrange-RECONSTRUCTED master secret (reconstruct-then-sign). Complete
bucketed census of all 56 axioms -- A (18, 5 Lean-bridged), B (27), C
(11) -- one row per axiom with name, file:line, justification. CT
contracts (sign_round{1,2}_constant_time) classified C/fail-closed.
- PROOF-CLAIMS.md: add assurance vocabulary + two load-bearing disclosures
-- (1) the EC byte-equality MODELS reconstruct-then-sign; (2) Corona has
NO independent interop verifier (R-LWE has no NIST target), so its
byte-equality is asserted + same-construction Go<->C++ KAT, never
"proven/verified". Corrects the §4 claim that Pulsar is machine-checked
"at every step" (it too rests on the same reconstruct-then-sign cone).
- BLOCKERS.md (new): CORONA-EC-RECON-MODEL, CORONA-CT-PENDING,
CORONA-NO-INDEP-VERIFIER -- all OPEN, all cross-referenced to the shared
external-review gate.
- .assurance/reviewed-axioms.txt: list every load-bearing / security-cone
axiom (A: 5 Lean-bridged; C: combine_body_axiom, S_functional_spec,
combine_body_spec, combine_abs_op_lifted_bridge, sign_abs_op_lifted_eq_rlwe)
with bucket + justification.
- .assurance/budget.txt: correct prose (0 real admit. tactics; AXIOM=56 exact).
Framework gate (circular-proof + axiom-budget + the other three) PASS with
full disclosure. go test ./... green (no Go touched).
Closes the CRIT-1 residual (D1-2) sid-entropy gap and the CT/doc hygiene
gaps to bring Corona to the no-leak/no-gap bar. EasyCrypt proofs untouched.
PRIORITY 1 — anti-nonce-reuse / no-leak durability:
- The Round-1 nonce-PRF key no longer keys on a bare 64-bit sid. It now
derives from a 256-bit domain-separated SessionID (primitives.DeriveSessionID:
TranscriptHash("corona.sign.session-id.v1" || be64(sid) || T)) AND a fresh
per-signature 256-bit hedge salt drawn inside the kernel from party.Rand
(default crypto/rand). PRNGKeyForRound = PRF(skShare, "CoronaNonceV3" ||
sessionID || salt). Hedging restores threshold-Raccoon's fresh-per-signature
nonce posture: reuse durability no longer rests on the external consensus
layer never reissuing an sid — even an sid collision yields distinct R with
prob 2^-256. A deterministic 256-bit SessionID alone is necessary but NOT
sufficient (it repeats when sid repeats); the salt is what closes the leak.
- SignRound1 is fail-closed: rejects an all-zero derived SessionID or all-zero
salt with ErrDegenerateSession, and surfaces a short-read error. The
consensus slot-uniqueness invariant is documented as a HARD precondition at
SignRound1 and Signer.Round1 (no longer a buried comment).
- KAT/oracle determinism preserved via one seam: sign.DeterministicNonceSource
(KeyedPRNG over seed || "corona.sign.nonce-salt.v1" || partyIndex), set on
Party.Rand / Signer.SetNonceRand only by reproducibility harnesses.
- SignRound1 / Signer.Round1 now return an error; all call sites updated.
- KAT REGEN: only sign_verify_e2e.json changes (nonce-key bytes moved);
transcript_hash.json / MAC / legacy PRNGKey vectors are byte-stable, proving
the consensus-agreed transcript path was left untouched. Regenerated via
`bash scripts/regen-kats.sh`; `--verify` confirms byte-determinism (10 files).
- Regression: sign/nonce_reuse_test.go proves same-(skShare,sid) yields distinct
D (fresh R), pinned-nonce reproduces byte-identically, and the degenerate-
session guard fires. TestE2EKATReplayDeterminism rewritten to assert both the
hedged-differs and pinned-reproduces properties (was a defanged no-op).
PRIORITY 2 — constant-time hygiene:
- reshare/commit.go already used a constant-time comparator; the real gap was
the verbatim duplication of constTimePolyEqual+uint64SliceToBytes across dkg2
and reshare. Consolidated to one canonical utils.ConstantTimePolyEqual; both
delegate (no dkg2<->reshare dependency). Orphaned imports removed.
- FullRankCheck and the reshare commit path are now covered in the CT review
with their public-operand justification.
PRIORITY 3 — doc accuracy:
- CONSTANT-TIME-REVIEW.md rewritten Corona-specific and file:line-accurate:
drops the stale Pulsar/lens/warp/secp256k1 content; audits the real call
sites (hedged nonce key, masking PRF, lattigo samplers as the residual TCB
axiom, utils.ConstantTimePolyEqual, CheckL2Norm/Verify/FullRankCheck big.Int
variable-time on PUBLIC operands, activation/commit-digest array equality,
keyera/reshare zeroization).
- PROOF-CLAIMS.md §1: threshold.Combine / sign.LocalSign (nonexistent) -> the
real sign.Party.SignFinalize exposed as threshold.Signer.Finalize.
- threshold/threshold.go package doc: Ring-LWE -> Module-LWE.
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>`.