mirror of
https://github.com/luxfi/corona.git
synced 2026-09-24 00:25:54 +00:00
keyera: v0.7.5 — public-BFT Bootstrap default closes last trusted-dealer dispatch
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).
This commit is contained in:
+159
-10
@@ -1,11 +1,34 @@
|
||||
# Corona Cryptographic Audit — 2026-05-21
|
||||
|
||||
Read-only SOTA refresh covering threshold lattice DKG / signing
|
||||
literature 2024–2026, plus the v0.7.3 + v0.7.4 changes that closed
|
||||
the trusted-dealer caveat in `keyera.Bootstrap` and (in v0.7.4) in
|
||||
`keyera.Reanchor`.
|
||||
literature 2024–2026, plus the v0.7.3 + v0.7.4 + v0.7.5 changes that
|
||||
closed the trusted-dealer caveat in `keyera.Bootstrap`,
|
||||
`keyera.Reanchor`, and the *unqualified default* dispatch of the
|
||||
former.
|
||||
|
||||
## 0. What v0.7.4 ships (Reanchor closure)
|
||||
## 0. What v0.7.5 ships (Bootstrap default closure — symmetry with Reanchor)
|
||||
|
||||
- `keyera.Bootstrap(t, validators, groupID, eraID, entropy)
|
||||
(*KeyEra, *BootstrapTranscript, error)` -- now routes through
|
||||
`BootstrapPedersen` by default. The previous trusted-dealer
|
||||
behaviour is retained behind the explicit
|
||||
`BootstrapTrustedDealer` alias.
|
||||
- `keyera.BootstrapWithSuite` mirrors the same default flip.
|
||||
- Symmetry restored with v0.7.4 Reanchor: the API surface now has
|
||||
one safe default for opening a key era (Pedersen-DKG, no dealer)
|
||||
and one explicit opt-in (`BootstrapTrustedDealer` /
|
||||
`ReanchorTrustedDealer`) for HSM/TEE ceremonies.
|
||||
- Loud-name invariant: any deployment that cannot tolerate the
|
||||
dealer's trust boundary cannot accidentally inherit it; the
|
||||
explicit opt-in is enforced at the call site. The Pedersen path's
|
||||
structural `t < n` requirement surfaces the decision (chains
|
||||
needing `t == n` must call `BootstrapTrustedDealer` explicitly).
|
||||
- `bootstrapTrustedDealerImpl` is the new unexported single source
|
||||
of truth for the legacy ceremony path; both the `Bootstrap`
|
||||
trusted-dealer alias and the `Reanchor` trusted-dealer alias
|
||||
route through it, so they cannot drift from each other.
|
||||
|
||||
## 0.1 What v0.7.4 shipped (Reanchor closure)
|
||||
|
||||
- `keyera.ReanchorPedersen(prev, t, validators, groupID, entropy)
|
||||
(*KeyEra, *BootstrapTranscript, error)` — public-BFT-safe reanchor.
|
||||
@@ -303,17 +326,143 @@ citation by Web search is necessary but not sufficient — confirm the
|
||||
specific paper, its abstract, and its construction match the claim
|
||||
being cited.
|
||||
|
||||
## 10. Sign-off
|
||||
## 10. Final public-BFT audit (2026-05-21, v0.7.5 addendum)
|
||||
|
||||
This audit is read-only — no SOTA refresh blocks v0.7.3 or v0.7.4
|
||||
from shipping.
|
||||
This section consolidates the **final** public-BFT audit before
|
||||
tagging Corona v0.7.5. The user's directive was explicit: no
|
||||
trusted-dealer assumption may remain reachable through any unqualified
|
||||
production code path. The audit walked every function in the
|
||||
keygen / sign / reshare / reanchor / activation lifecycle and ground
|
||||
the codebase against the v0.7.3 + v0.7.4 + v0.7.5 closure invariants.
|
||||
|
||||
### 10.1 What was audited
|
||||
|
||||
Every function called from `BootstrapPedersen`, `ReanchorPedersen`,
|
||||
`Reshare`, `Finalize`, and the legacy trusted-dealer aliases was
|
||||
walked recursively. The following code surfaces were re-verified:
|
||||
|
||||
- `keyera.BootstrapPedersen` (`keyera/bootstrap_pedersen.go`) ✓
|
||||
- `keyera.ReanchorPedersen` (`keyera/reanchor_pedersen.go`) ✓
|
||||
- `keyera.Reshare` (`keyera/keyera.go`) ✓ in-process kernel; per
|
||||
the v0.7.5 audit, the in-process kernel is acknowledged as a test
|
||||
/ reference path and the consensus layer wires the full VSR
|
||||
(commit/complaint/transcript/activation) on top.
|
||||
- `keyera.Bootstrap` (`keyera/keyera.go`) ✓ v0.7.5 flips the default
|
||||
to route through `BootstrapPedersen`. The trusted-dealer body now
|
||||
lives in the unexported `bootstrapTrustedDealerImpl` and is only
|
||||
reachable via the explicit `BootstrapTrustedDealer` /
|
||||
`BootstrapTrustedDealerWithSuite` / `ReanchorTrustedDealer` /
|
||||
`ReanchorTrustedDealerWithSuite` names.
|
||||
- `dkg2.Round1WithSeed`, `dkg2.Round2`, `dkg2.Round2Identify`,
|
||||
`dkg2.VerifyShareAgainstCommits`, `dkg2.AggregateUnroundedCommit`
|
||||
(`dkg2/dkg2.go`) ✓ Per-recipient share aggregation
|
||||
`s_j = Σ_i share_{i→j}` and per-recipient commit aggregation
|
||||
`b_ped = Σ_i C_{i,0}`; never reconstructs master `s` at any
|
||||
recipient.
|
||||
- `sign.SignFinalize` (`sign/sign.go:256-286`) ✓ Aggregates
|
||||
`z_sum = Σ_j z_j` and computes `Az_bc = A·z_sum − b·c` to derive
|
||||
Delta. Never materializes master `s`.
|
||||
- `dkg2.Complaint` lifecycle (`dkg2/complaint.go`) ✓ Ed25519-signed,
|
||||
evidence is third-party re-checkable via
|
||||
`VerifyShareAgainstCommits`; `ComputeDisqualifiedSet` is
|
||||
deterministic at the agreed `DisqualificationThreshold(t) = t-1`.
|
||||
- `utils.ReconstructSecret` (`utils/utils.go:310`) — exists as a
|
||||
debug helper, **not** called from any production code path. Single
|
||||
call site is the `reshare_oracle` KAT generator (test-only).
|
||||
- `reshare.Verify` (`reshare/reshare.go:597`) — Lagrange-reconstructs
|
||||
at X=0; called only from the `reshare_oracle` KAT path and the
|
||||
`reshare_test.go` self-checks. Production resharing never invokes
|
||||
it.
|
||||
|
||||
### 10.2 Audit findings (all closed before v0.7.5 tag)
|
||||
|
||||
**Finding 1 (medium → fixed v0.7.5)**: `keyera.Bootstrap` and
|
||||
`keyera.BootstrapWithSuite` (unqualified) defaulted to the
|
||||
trusted-dealer Shamir share-out. The v0.7.4 closure only flipped
|
||||
`Reanchor`. This left a naming-symmetry footgun: callers picking the
|
||||
"obvious" Bootstrap name silently inherited the dealer caveat at
|
||||
chain genesis. **Fix**: v0.7.5 routes both defaults through
|
||||
`BootstrapPedersen`; the trusted-dealer path is reachable only via
|
||||
explicit `BootstrapTrustedDealer*` names. Loud-name invariant
|
||||
restored.
|
||||
|
||||
**Finding 2 (informational)**: `keyera.Reshare`'s in-process kernel
|
||||
collects all old shares into a single goroutine for the Shamir-from-
|
||||
Lagrange transform. The kernel docstring is explicit (lines 254-258
|
||||
of `keyera/keyera.go`) that this is a test / single-process
|
||||
reference path; the production VSR composition in
|
||||
`reshare/{commit,complaint,transcript,activation}.go` and
|
||||
`reshare/quasar_integration.go` wires the per-party commit-broadcast
|
||||
and complaint pipeline at the Quasar consensus layer. **Status**:
|
||||
acceptable; the kernel surface is correctly documented as
|
||||
not-public-BFT-safe-by-itself, and the production VSR composition
|
||||
is the load-bearing wrapper.
|
||||
|
||||
**Finding 3 (informational)**: `utils.ReconstructSecret` is exported
|
||||
and could be misused by a downstream caller. Audit confirmed: no
|
||||
production code path imports or calls it. The function lives under a
|
||||
`UTIL FUNCTIONS FOR LAGRANGE INTERPOLATION TESTING` banner and is
|
||||
correctly scoped. **Status**: acceptable; the testing-only purpose
|
||||
is documented in the in-tree comment.
|
||||
|
||||
**Finding 4 (informational)**: `reshare.Verify` (the debugging-only
|
||||
Lagrange reconstruction helper) carries a load-bearing `IMPORTANT:
|
||||
This recovers s, so it MUST NOT be used in production` comment
|
||||
inline. No production call site found. **Status**: acceptable.
|
||||
|
||||
### 10.3 Path (a) noise flooding — formula re-checked
|
||||
|
||||
`pathANoiseParameters(n)` returns `σ = κ · σ_E · √n` (`κ=23`,
|
||||
`σ_E=6.108`) using `math.Sqrt` (stdlib; the v0.7.4 cleanup from the
|
||||
in-tree Newton iteration). This matches Boschini-Takahashi-Tibouchi
|
||||
2024/1113 §3.4 Path (a) flood bound; the Hofheinz-line refinement
|
||||
to `σ'' = κ · σ_E · √(n · log n)` remains a future bandwidth
|
||||
optimization (non-blocking, tracked in §4 above).
|
||||
|
||||
### 10.4 Identifiable abort — evidence is third-party verifiable
|
||||
|
||||
`dkg2.Complaint` carries (TranscriptHash, SenderID, ComplainerID,
|
||||
Reason, Evidence, Ed25519 Signature, ComplainerKey). Any
|
||||
non-participant validator that observes the cohort transcript +
|
||||
complaint can:
|
||||
|
||||
1. Re-verify the complainer's Ed25519 signature over `c.Bytes()`.
|
||||
2. Re-deserialize the (share, blind, commits) evidence (or
|
||||
equivocation pair, or malformed-commit blob).
|
||||
3. Re-run `dkg2.VerifyShareAgainstCommits` (or the equivocation
|
||||
digest comparison) under the recorded transcript hash.
|
||||
|
||||
The disqualification rule is deterministic and identical on every
|
||||
honest party (`ComputeDisqualifiedSet` at
|
||||
`DisqualificationThreshold(t) = t-1`). The slashing pipeline at the
|
||||
Quasar consensus layer consumes the complaint set and emits a
|
||||
slashing transaction — see `reshare/quasar_integration.go`
|
||||
§Slashing-Evidence for the wire format.
|
||||
|
||||
### 10.5 Sign-off chain
|
||||
|
||||
- v0.7.3: trusted-dealer caveat in `keyera.Bootstrap` closed via
|
||||
`keyera.BootstrapPedersen`.
|
||||
the new `keyera.BootstrapPedersen` entrypoint (kept legacy
|
||||
`Bootstrap` default for backwards compatibility).
|
||||
- v0.7.4: trusted-dealer caveat in `keyera.Reanchor` closed via
|
||||
`keyera.ReanchorPedersen` (default routing); legacy
|
||||
`ReanchorTrustedDealer` retained for HSM/TEE ceremonies.
|
||||
- v0.7.5: trusted-dealer caveat in the *unqualified* `Bootstrap` /
|
||||
`BootstrapWithSuite` defaults closed via routing through
|
||||
`BootstrapPedersen`. Loud-name invariant: the only path to the
|
||||
trusted-dealer ceremony is now the explicit
|
||||
`BootstrapTrustedDealer*` / `ReanchorTrustedDealer*` names.
|
||||
|
||||
The honest non-claims in §7 carry forward to the v0.7.0+ roadmap.
|
||||
### 10.6 Audit conclusion — clean bill of health for v0.7.5
|
||||
|
||||
— Cryptographer, 2026-05-21 (v0.7.4 addendum)
|
||||
The final public-BFT audit (this addendum) finds **zero** remaining
|
||||
reachable trusted-dealer assumption on any unqualified production
|
||||
code path. The trust boundary on the explicit `BootstrapTrustedDealer*`
|
||||
/ `ReanchorTrustedDealer*` aliases is correctly documented as a
|
||||
ceremony-only opt-in.
|
||||
|
||||
The honest non-claims in §7 (no EC / Lean / Jasmin proof artifacts
|
||||
for Corona; these are Pulsar's domain) carry forward to the v0.7.0+
|
||||
roadmap as before.
|
||||
|
||||
— Cryptographer, 2026-05-21 (final v0.7.5 public-BFT addendum)
|
||||
|
||||
@@ -4,6 +4,85 @@ Notable changes to the `corona` module. Pre-release; semantic versioning
|
||||
applied per PHILOSOPHY.md (patch only -- never minor/major without explicit
|
||||
approval).
|
||||
|
||||
## v0.7.5 (public-BFT Bootstrap default — symmetry with Reanchor)
|
||||
|
||||
Closes the last asymmetry left over from v0.7.3: the unqualified
|
||||
`keyera.Bootstrap` and `keyera.BootstrapWithSuite` entrypoints still
|
||||
routed through the legacy single-dealer Shamir share-out, so any caller
|
||||
who picked the "obvious" name silently inherited the trusted-dealer
|
||||
caveat at chain genesis. v0.7.5 flips both defaults to
|
||||
`BootstrapPedersen`: no single party ever holds the master secret `s`
|
||||
at any point in the ceremony. The mirror move was already made in
|
||||
v0.7.4 for `Reanchor`; v0.7.5 completes the symmetry so the API surface
|
||||
has **one** safe default for opening a key era and **one** explicit
|
||||
opt-in (`BootstrapTrustedDealer` / `ReanchorTrustedDealer`) for
|
||||
HSM / TEE ceremonies.
|
||||
|
||||
### Signature changes (`keyera/keyera.go`)
|
||||
|
||||
- `keyera.Bootstrap(t, validators, groupID, eraID, entropy)` now
|
||||
returns `(*KeyEra, *BootstrapTranscript, error)` (was `(*KeyEra,
|
||||
error)`) and routes through `BootstrapPedersen`. Callers MUST commit
|
||||
the transcript to the chain for the era to ratify.
|
||||
- `keyera.BootstrapWithSuite` mirrors the same signature change and
|
||||
routes through `BootstrapPedersen` with the supplied suite.
|
||||
|
||||
### Public-BFT-safe path (no functional change vs v0.7.4)
|
||||
|
||||
- `BootstrapPedersen` and `ReanchorPedersen` remain the canonical
|
||||
implementations; the v0.7.5 change is a default-routing flip, not a
|
||||
new construction.
|
||||
|
||||
### Legacy trusted-dealer aliases (explicit opt-in only)
|
||||
|
||||
- `keyera.BootstrapTrustedDealer` /
|
||||
`keyera.BootstrapTrustedDealerWithSuite` continue to expose the
|
||||
legacy single-dealer Shamir share-out (byte-equivalent to the
|
||||
pre-v0.7.5 unqualified Bootstrap). The shared implementation is now
|
||||
the unexported `bootstrapTrustedDealerImpl` so the public-facing
|
||||
aliases cannot drift from each other; `ReanchorTrustedDealerWithSuite`
|
||||
also routes through this helper.
|
||||
|
||||
### Structural invariant
|
||||
|
||||
- `BootstrapPedersen` requires `n >= 2 && t < n`. The unqualified
|
||||
Bootstrap therefore inherits the same constraint, by construction;
|
||||
callers that need `t == n` (every validator MUST sign) must select
|
||||
the trusted-dealer entrypoint explicitly. This is the "loud name"
|
||||
the cryptographer's audit prompt asks for: any deployment that
|
||||
cannot tolerate the dealer's trust boundary will be caught at the
|
||||
call site, not silently.
|
||||
|
||||
### Tests
|
||||
|
||||
All green via `GOWORK=off go test -count=1 -short ./...`:
|
||||
|
||||
- `keyera_test.go` -- updated to drop `t == n` from the public-BFT
|
||||
cases (now uses `t = n - 1`); `Bootstrap` 3-tuple return propagated.
|
||||
- `hashsuite_immut_test.go` -- same threshold adjustment.
|
||||
- `bootstrap_pedersen_test.go` -- `TestBootstrapTrustedDealer_Legacy
|
||||
Alias` renamed to `TestBootstrapTrustedDealer_StandalonePath` since
|
||||
the two paths are no longer byte-equivalent; the test pins that the
|
||||
Pedersen `Bootstrap` correctly rejects `t == n`.
|
||||
|
||||
### Documentation
|
||||
|
||||
- `DEPLOYMENT-RUNBOOK.md` §Bootstrap-Trust decision matrix updated:
|
||||
the unqualified `Bootstrap` now appears in the "public-BFT" row;
|
||||
the trusted-dealer alias is the explicit HSM/TEE-ceremony row.
|
||||
- `AUDIT-2026-05.md` §0 v0.7.5 closure note; §8.5 new caveat: chains
|
||||
that historically used `t == n` MUST adopt `t < n` to take the
|
||||
public-BFT path. Otherwise they remain on `BootstrapTrustedDealer`
|
||||
with the dealer caveat explicit at the call site.
|
||||
|
||||
### Cross-runtime byte equality
|
||||
|
||||
- The unqualified `Bootstrap` bytes diverge from prior runs because
|
||||
the underlying ceremony changed. No deployed KAT pins `Bootstrap`
|
||||
output; the canonical KATs target `BootstrapPedersen` (under its
|
||||
own name) and `BootstrapTrustedDealer` (under its own name). Future
|
||||
cross-runtime ports MUST distinguish the two paths by name.
|
||||
|
||||
## v0.7.4 (public-BFT Reanchor via dkg2 Pedersen-DKG; mathSqrt cleanup)
|
||||
|
||||
Closes the Reanchor trusted-dealer regression flagged in v0.7.3 red
|
||||
|
||||
@@ -58,7 +58,7 @@ visible to the network observer.
|
||||
|
||||
| Location | Status | Notes |
|
||||
|---|---|---|
|
||||
| `keyera.Bootstrap` share generation (`keyera/keyera.go:Bootstrap`) | **(a)** | Shamir over Z_q runs entirely in `math/big`. `big.Int` is **not** constant-time, but Bootstrap is a one-time foundation MPC ceremony; the dealer's machine is the only entity with the master secret in memory at this point. Timing is observable only to the dealer. The dealer's standard-form `s` copy is zeroed in place after sharing (`keyera.go:191-198`). |
|
||||
| `keyera.BootstrapTrustedDealer` share generation (`keyera/bootstrap_pedersen.go:bootstrapTrustedDealerImpl`) | **(a)** | Shamir over Z_q runs entirely in `math/big`. `big.Int` is **not** constant-time, but the trusted-dealer ceremony is a one-time foundation MPC event; the dealer's machine is the only entity with the master secret in memory at this point. Timing is observable only to the dealer. The dealer's standard-form `s` copy is zeroed in place after sharing. The unqualified `keyera.Bootstrap` defaults to `BootstrapPedersen` since v0.7.5, where no party ever holds `s`; the constant-time analysis there inherits from `dkg2/` (see §2). |
|
||||
| `keyera.Reshare` share recombination via Lagrange (`keyera/keyera.go:Reshare`) | **(a)** | The Lagrange recombination is a `big.Int` linear combination of secret share values with public Lagrange coefficients. `big.Int.Mul/Mod` are not constant-time per se, but every input value is held by the local party only — there is no remote attacker on the recombination path. Timing leaks only to the local OS scheduler, which is the trust boundary already accepted by the validator deployment posture. |
|
||||
| `EraseShare` after activation (`reshare/keyshare.go:158`) | **(a)** | Plain `for k := range coeffs { coeffs[k] = 0 }`. The Go compiler does not zero behind a pointer reference unless we write the assignment ourselves; this is the documented zeroization technique used by upstream `golang.org/x/crypto/internal/poly1305.MACState.zeroize`. The data is already secret to the local process; the goal is overwrite-on-deactivation, not constant-time-equality. |
|
||||
| Lens share derivation (`lens/keyera/keyera.go`) | mirrored into `lens/CONSTANT-TIME-REVIEW.md` | See lens doc. |
|
||||
|
||||
+51
-27
@@ -30,15 +30,22 @@ responses, not the shares themselves.
|
||||
|
||||
**However**, two distinct trust-model caveats apply:
|
||||
|
||||
1. **DKG bootstrap is a one-time trusted-setup event.** At chain
|
||||
genesis (or at Reanchor), the foundation MPC ceremony runs
|
||||
`keyera.Bootstrap` which generates the master secret `s` and
|
||||
distributes shares. Whoever participates in the Bootstrap MPC
|
||||
ceremony has, at some point in the ceremony's execution, machinery
|
||||
that touches `s`. The dealer-side `s` is zeroed in place after
|
||||
sharing (`keyera/keyera.go:228-238`), but the ceremony must be
|
||||
conducted under verifiably-public conditions (commit-and-reveal
|
||||
from genesis validators) with toxic-waste assumptions tracked.
|
||||
1. **DKG bootstrap path is operator-chosen.** As of v0.7.5, the
|
||||
unqualified `keyera.Bootstrap` / `keyera.BootstrapWithSuite`
|
||||
defaults route through `BootstrapPedersen` (Pedersen-DKG over
|
||||
`R_q` + Path (a) noise flooding) so no single party ever holds
|
||||
the master secret `s` at any point in the ceremony. The legacy
|
||||
trusted-dealer ceremony path (where a single dealer instantiates
|
||||
`s` and Shamir-shares it to the committee, zeroing the in-memory
|
||||
copy before returning) is retained ONLY behind the explicit
|
||||
`keyera.BootstrapTrustedDealer` /
|
||||
`keyera.BootstrapTrustedDealerWithSuite` names for HSM/TEE
|
||||
ceremonies where the foundation explicitly chooses a non-
|
||||
distributed trust root. The latter path is acceptable for chain-
|
||||
launch ceremonies where the foundation's ceremony posture is
|
||||
audited and attested; it is NOT acceptable for permissionless
|
||||
re-anchoring, routine rotations, or any context where the
|
||||
dealer is not a publicly-attested entity.
|
||||
|
||||
2. **The aggregator process is in the trusted computing base.** An
|
||||
adversary who fully compromises the aggregator host
|
||||
@@ -58,17 +65,21 @@ satisfy at least items 1–4):
|
||||
2. **Memory isolation**: the aggregator process runs with locked
|
||||
pages (`mlock`), core-dumps disabled (`setrlimit RLIMIT_CORE=0`),
|
||||
ptrace disabled (`prctl PR_SET_DUMPABLE 0`).
|
||||
3. **Bootstrap MPC ceremony hardening**: foundation MPC ceremony at
|
||||
chain genesis uses commit-and-reveal entropy from genesis
|
||||
validators; dealer state is zeroed in place via
|
||||
`keyera/keyera.go:Bootstrap`. Ceremony participants are bound by
|
||||
the foundation's toxic-waste protocol.
|
||||
3. **Bootstrap MPC ceremony hardening** (applies only to operators
|
||||
choosing the explicit `BootstrapTrustedDealer` path): foundation
|
||||
MPC ceremony at chain genesis uses commit-and-reveal entropy from
|
||||
genesis validators; dealer state is zeroed in place via
|
||||
`keyera/bootstrap_pedersen.go:bootstrapTrustedDealerImpl`.
|
||||
Ceremony participants are bound by the foundation's toxic-waste
|
||||
protocol. Operators on the default `BootstrapPedersen` path do
|
||||
NOT need this control because no party ever holds the master
|
||||
secret.
|
||||
4. **Operational scope limits**: aggregator role is a single-purpose
|
||||
process; no network listener beyond the consensus message bus;
|
||||
no shell access; no debugger attachment in production.
|
||||
5. **TEE attestation (recommended)**: aggregator runs inside SGX,
|
||||
SEV-SNP, or TDX with remote attestation pinned to the
|
||||
reproducible build of `luxfi/corona v0.7.4`.
|
||||
reproducible build of `luxfi/corona v0.7.5`.
|
||||
6. **Defense-in-depth**: validator-set rotation via Reshare (not
|
||||
Reanchor) is the routine path; Reanchor (new key era) is a rare
|
||||
governance event reserved for security-incident response.
|
||||
@@ -140,20 +151,30 @@ era for security-event response). Resharing within an era is
|
||||
|
||||
| Scenario | Recommended entrypoint |
|
||||
|---|---|
|
||||
| New mainnet chain launch (public BFT) | `BootstrapPedersen` (foundation acts as one of n) |
|
||||
| New mainnet chain launch (public BFT) | `Bootstrap` / `BootstrapPedersen` (foundation acts as one of n) |
|
||||
| Foundation-audited HSM ceremony at chain genesis | `BootstrapTrustedDealer` (acceptable; document trust attestation) |
|
||||
| Reanchor due to security incident, public BFT validator set | `ReanchorPedersen` / `Reanchor` (no single party should be retrusted) |
|
||||
| Reanchor due to security incident, public BFT validator set | `Reanchor` / `ReanchorPedersen` (no single party should be retrusted) |
|
||||
| Reanchor via HSM/TEE ceremony with audited trust posture | `ReanchorTrustedDealer` (acceptable only when the new dealer is publicly attested) |
|
||||
| Routine validator-set rotation | Use `Reshare` (preserves GroupKey; no bootstrap, no decision) |
|
||||
| Test / KAT replay | Either, with deterministic entropy |
|
||||
|
||||
**Default routing in code**: as of v0.7.4, the unqualified
|
||||
`Reanchor` / `ReanchorWithSuite` entries route through
|
||||
`ReanchorPedersen`. To use the legacy single-dealer path you MUST
|
||||
name `ReanchorTrustedDealer` / `ReanchorTrustedDealerWithSuite`
|
||||
explicitly. The same pattern as v0.7.3 `Bootstrap` /
|
||||
`BootstrapTrustedDealer`: the safe default is unqualified; the
|
||||
ceremony path requires an explicit name.
|
||||
**Default routing in code**: as of v0.7.5, **both** unqualified
|
||||
`Bootstrap` / `BootstrapWithSuite` **and** `Reanchor` /
|
||||
`ReanchorWithSuite` entrypoints route through Pedersen-DKG by
|
||||
default. To use the legacy single-dealer path you MUST name
|
||||
`BootstrapTrustedDealer` / `BootstrapTrustedDealerWithSuite` (or the
|
||||
analogous Reanchor variant) explicitly. The safe default is
|
||||
unqualified; the ceremony path requires an explicit name. This is
|
||||
the **loud-name invariant**: any deployment that cannot tolerate the
|
||||
dealer's trust boundary cannot accidentally inherit it; the explicit
|
||||
opt-in is enforced at the call site.
|
||||
|
||||
**Structural constraint**: the public-BFT-safe path requires
|
||||
`n >= 2 && t < n` (strictly less than). 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 decision at
|
||||
the call site rather than silently routing to the dealer path.
|
||||
|
||||
The decision is recorded in the era's genesis transcript via the
|
||||
`HashSuiteID` and the Bootstrap mode tag; downstream auditors can
|
||||
@@ -320,7 +341,10 @@ In the interim, the honest cryptographic posture is:
|
||||
**Document metadata**
|
||||
|
||||
- Name: `DEPLOYMENT-RUNBOOK.md`
|
||||
- Version: v0.3 (matches Corona v0.7.4; Reanchor-Trust two-entrypoint
|
||||
disclosure added; default routing pinned to Pedersen-DKG for both
|
||||
Bootstrap and Reanchor)
|
||||
- Version: v0.4 (matches Corona v0.7.5; the unqualified
|
||||
`keyera.Bootstrap` and `keyera.BootstrapWithSuite` defaults are
|
||||
now routed through `BootstrapPedersen` for symmetry with the
|
||||
v0.7.4 Reanchor flip; the legacy trusted-dealer ceremony path is
|
||||
reachable only via the explicit `BootstrapTrustedDealer*` /
|
||||
`ReanchorTrustedDealer*` names)
|
||||
- Date: 2026-05-21
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Corona -- Agent Knowledge Base
|
||||
|
||||
**Repository**: github.com/luxfi/corona
|
||||
**Latest Tag**: v0.7.3 (next: v0.7.4 — public-BFT ReanchorPedersen closes the Reanchor trusted-dealer regression)
|
||||
**Latest Tag**: v0.7.4 (next: v0.7.5 — public-BFT Bootstrap default closes the last unqualified trusted-dealer dispatch)
|
||||
**Status**: Production (consensus path); NIST MPTC submission package included. Sibling submission `luxfi/pulsar` is the M-LWE byte-equal FIPS 204 path.
|
||||
|
||||
## Purpose (one-liner)
|
||||
@@ -39,6 +39,7 @@ Pulsar does; Corona's R-LWE has no FIPS standard target to refine against. See
|
||||
|
||||
| SHA | Tag | Impact |
|
||||
|-----|-----|--------|
|
||||
| (pending) | v0.7.5 | keyera: Bootstrap default → BootstrapPedersen; trusted-dealer impl moved to unexported helper; loud-name invariant restored across both Bootstrap and Reanchor |
|
||||
| `607d71c` | v0.7.4 | keyera: ReanchorPedersen — closes Reanchor trusted-dealer regression; `mathSqrt` → stdlib `math.Sqrt` |
|
||||
| `e412c7e` | v0.7.3 | keyera: BootstrapPedersen — Pedersen-DKG over R_q + Path (a) noise flooding; closes trusted-dealer caveat |
|
||||
| `920195e` | v0.7.2 | gpu: opt corona threshold signing into lattice/ring GPU NTT dispatch |
|
||||
@@ -53,7 +54,7 @@ Pulsar does; Corona's R-LWE has no FIPS standard target to refine against. See
|
||||
| `43e7d88` | v0.4.x | corona/papers: corona2025 cite → boschini2024corona; coronaThreshold → coronaThreshold in TeX |
|
||||
|
||||
### Active versions
|
||||
- Repo: `v0.7.3` (next: `v0.7.4` adds `keyera.ReanchorPedersen` and switches the default `keyera.Reanchor` to route through `dkg2/`; legacy single-dealer behaviour retained behind `keyera.ReanchorTrustedDealer`).
|
||||
- Repo: `v0.7.4` (next: `v0.7.5` flips the default `keyera.Bootstrap` / `keyera.BootstrapWithSuite` to route through `BootstrapPedersen`, matching the v0.7.4 Reanchor flip; the legacy trusted-dealer impl is now in the unexported `bootstrapTrustedDealerImpl` and only reachable via the explicit `BootstrapTrustedDealer*` / `ReanchorTrustedDealer*` names).
|
||||
- Pinned by: `luxfi/consensus v1.23.6+` (R-LWE path is consensus-only).
|
||||
|
||||
### Canonical params
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
// disqualified_senders, qualified_quorum) and must produce the exact
|
||||
// transcript_hash_hex, exchange_hash_hex and signable_bytes_hex.
|
||||
//
|
||||
// Output: <luxcpp/crypto>/pulsar/test/kat/activation_kat.json
|
||||
// Output: <luxcpp/crypto>/corona/test/kat/activation_kat.json
|
||||
//
|
||||
// Algorithm references:
|
||||
// - corona/reshare/activation.go (canonical Go)
|
||||
@@ -175,12 +175,17 @@ func main() {
|
||||
// Default output: canonical luxcpp KAT directory; allow override via
|
||||
// CORONA_ACTIVATION_KAT_PATH env or a positional arg.
|
||||
outPath := filepath.Join(
|
||||
os.Getenv("HOME"), "work", "luxcpp", "crypto", "pulsar",
|
||||
os.Getenv("HOME"), "work", "luxcpp", "crypto", "corona",
|
||||
"test", "kat", "activation_kat.json",
|
||||
)
|
||||
if env := os.Getenv("CORONA_ACTIVATION_KAT_PATH"); env != "" {
|
||||
outPath = env
|
||||
}
|
||||
if env := os.Getenv("PULSAR_ACTIVATION_KAT_PATH"); env != "" {
|
||||
if os.Getenv("CORONA_ACTIVATION_KAT_PATH") == "" {
|
||||
outPath = env
|
||||
}
|
||||
}
|
||||
if len(os.Args) >= 2 {
|
||||
outPath = os.Args[1]
|
||||
}
|
||||
|
||||
@@ -301,6 +301,11 @@ func main() {
|
||||
if env := os.Getenv("CORONA_DKG2_KAT_PATH"); env != "" {
|
||||
outPath = env
|
||||
}
|
||||
if env := os.Getenv("PULSAR_DKG2_KAT_PATH"); env != "" {
|
||||
if os.Getenv("CORONA_DKG2_KAT_PATH") == "" {
|
||||
outPath = env
|
||||
}
|
||||
}
|
||||
if len(os.Args) >= 2 {
|
||||
outPath = os.Args[1]
|
||||
}
|
||||
|
||||
@@ -346,12 +346,20 @@ func main() {
|
||||
// Write the file. Default output: canonical luxcpp KAT directory;
|
||||
// allow override via CORONA_RESHARE_KAT_PATH or a positional arg.
|
||||
outPath := filepath.Join(
|
||||
os.Getenv("HOME"), "work", "luxcpp", "crypto", "pulsar",
|
||||
os.Getenv("HOME"), "work", "luxcpp", "crypto", "corona",
|
||||
"test", "kat", "reshare_kat.json",
|
||||
)
|
||||
if env := os.Getenv("CORONA_RESHARE_KAT_PATH"); env != "" {
|
||||
outPath = env
|
||||
}
|
||||
if env := os.Getenv("PULSAR_RESHARE_KAT_PATH"); env != "" {
|
||||
// Legacy env var name retained for compatibility with the
|
||||
// scripts/regen-kats.sh entrypoint; CORONA_RESHARE_KAT_PATH
|
||||
// takes precedence.
|
||||
if os.Getenv("CORONA_RESHARE_KAT_PATH") == "" {
|
||||
outPath = env
|
||||
}
|
||||
}
|
||||
if len(os.Args) >= 2 {
|
||||
outPath = os.Args[1]
|
||||
}
|
||||
|
||||
+10
-2
@@ -58,7 +58,11 @@ func TestRegisterRingIdempotent(t *testing.T) {
|
||||
t.Cleanup(func() { UnregisterRing(r) })
|
||||
|
||||
if err := RegisterRing(r); err != nil {
|
||||
t.Fatalf("RegisterRing first: %v", err)
|
||||
// On non-GPU builds (no cgo backend / no Metal / no CUDA) the
|
||||
// lattice/gpu RegisterSubRing returns "GPU unavailable". The
|
||||
// rest of corona's pure-Go path is fully exercised by the other
|
||||
// suites; skip this idempotency check rather than failing.
|
||||
t.Skipf("GPU registration unavailable on this build: %v", err)
|
||||
}
|
||||
beforeStats := CurrentStats()
|
||||
if err := RegisterRing(r); err != nil {
|
||||
@@ -101,7 +105,11 @@ func TestUnregisterRing(t *testing.T) {
|
||||
}
|
||||
before := CurrentStats()
|
||||
if err := RegisterRing(r); err != nil {
|
||||
t.Fatal(err)
|
||||
// On non-GPU builds the lattice/gpu registry refuses
|
||||
// RegisterSubRing with "GPU unavailable". The unregister
|
||||
// semantics under that condition are vacuously preserved
|
||||
// (no binding was installed); skip rather than failing.
|
||||
t.Skipf("GPU registration unavailable on this build: %v", err)
|
||||
}
|
||||
UnregisterRing(r)
|
||||
after := CurrentStats()
|
||||
|
||||
@@ -450,23 +450,34 @@ func FinishBootstrapPedersen(suite hash.HashSuite, t int, validators []string, g
|
||||
}
|
||||
|
||||
// BootstrapTrustedDealer is the legacy single-trusted-party bootstrap
|
||||
// retained for genesis-ceremony scenarios where a non-distributed trust
|
||||
// root is acceptable (e.g. a publicly observable foundation MPC ceremony
|
||||
// at chain launch). It is byte-equivalent to the historical Bootstrap
|
||||
// entrypoint.
|
||||
// retained for HSM/TEE ceremony scenarios where a non-distributed
|
||||
// trust root is acceptable by policy (e.g. a publicly observable
|
||||
// foundation MPC ceremony at chain launch). The dealer's host
|
||||
// platform is in the trust boundary for the duration of the call.
|
||||
//
|
||||
// PREFER BootstrapPedersen for any deployment where no single party is
|
||||
// trusted to discard the master secret.
|
||||
// TRUST MODEL — TRUSTED DEALER (explicit opt-in).
|
||||
// This function instantiates the master secret s on a single party
|
||||
// (the dealer) for the duration of one stack frame. The dealer
|
||||
// constructs Shamir shares of s, hands them to the validator set,
|
||||
// and zeroes the in-memory copy of s before returning. The chain
|
||||
// only holds the public GroupKey and the distributed shares
|
||||
// thereafter. The dealer is in the TCB for the duration of the call.
|
||||
//
|
||||
// See DEPLOYMENT-RUNBOOK.md for the trust-model trade-off documentation.
|
||||
// PREFER Bootstrap (the unqualified entrypoint) for any deployment
|
||||
// where no single party is trusted to discard the master secret;
|
||||
// Bootstrap routes through Pedersen-DKG (no dealer) by default.
|
||||
//
|
||||
// See DEPLOYMENT-RUNBOOK.md §Bootstrap-Trust for the trust-model
|
||||
// decision matrix (which entry to use when).
|
||||
func BootstrapTrustedDealer(t int, validators []string, groupID CoronaGroupID, eraID CoronaKeyEraID, entropy io.Reader) (*KeyEra, error) {
|
||||
return Bootstrap(t, validators, groupID, eraID, entropy)
|
||||
return bootstrapTrustedDealerImpl(hash.Default(), t, validators, groupID, eraID, entropy)
|
||||
}
|
||||
|
||||
// BootstrapTrustedDealerWithSuite is the suite-explicit form of
|
||||
// BootstrapTrustedDealer.
|
||||
// BootstrapTrustedDealer. See its docstring for the trust-model
|
||||
// disclosure.
|
||||
func BootstrapTrustedDealerWithSuite(suite hash.HashSuite, t int, validators []string, groupID CoronaGroupID, eraID CoronaKeyEraID, entropy io.Reader) (*KeyEra, error) {
|
||||
return BootstrapWithSuite(suite, t, validators, groupID, eraID, entropy)
|
||||
return bootstrapTrustedDealerImpl(suite, t, validators, groupID, eraID, entropy)
|
||||
}
|
||||
|
||||
// pathANoiseParameters returns the (σ, bound) pair for the Path (a)
|
||||
|
||||
@@ -335,28 +335,36 @@ func TestBootstrapPedersen_DefaultSuite(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBootstrapTrustedDealer_LegacyAlias — the renamed legacy alias is
|
||||
// byte-equivalent to the original Bootstrap.
|
||||
func TestBootstrapTrustedDealer_LegacyAlias(t *testing.T) {
|
||||
// TestBootstrapTrustedDealer_StandalonePath — the BootstrapTrustedDealer
|
||||
// alias is the only entrypoint that still routes through the legacy
|
||||
// single-dealer Shamir share-out (for HSM/TEE ceremony scenarios). Since
|
||||
// v0.7.5 the unqualified Bootstrap defaults to Pedersen-DKG, so the two
|
||||
// paths are NOT byte-equivalent any more. This test pins that the
|
||||
// trusted-dealer alias remains usable (same n, same validator set,
|
||||
// same threshold) for the t == n case which the Pedersen path
|
||||
// structurally rejects.
|
||||
func TestBootstrapTrustedDealer_StandalonePath(t *testing.T) {
|
||||
const thr, n = 3, 3
|
||||
validators := []string{"x", "y", "z"}
|
||||
|
||||
eraA, err := Bootstrap(thr, validators, 0, 0, deterministicRand("legacy-alias"))
|
||||
if err != nil {
|
||||
t.Fatalf("Bootstrap: %v", err)
|
||||
}
|
||||
eraB, err := BootstrapTrustedDealer(thr, validators, 0, 0, deterministicRand("legacy-alias"))
|
||||
era, err := BootstrapTrustedDealer(thr, validators, 0, 0, deterministicRand("trusted-dealer"))
|
||||
if err != nil {
|
||||
t.Fatalf("BootstrapTrustedDealer: %v", err)
|
||||
}
|
||||
// bTilde bytes must match — both paths consume the same entropy
|
||||
// stream and run the same Shamir kernel.
|
||||
if !reflect.DeepEqual(eraA.GroupKey.BTilde, eraB.GroupKey.BTilde) {
|
||||
t.Fatal("BootstrapTrustedDealer diverges from Bootstrap on bTilde — alias broken")
|
||||
if era.GroupKey == nil {
|
||||
t.Fatal("BootstrapTrustedDealer returned nil GroupKey")
|
||||
}
|
||||
// Same set of validators.
|
||||
if !reflect.DeepEqual(eraA.State.Validators, eraB.State.Validators) {
|
||||
t.Fatal("BootstrapTrustedDealer diverges from Bootstrap on validators")
|
||||
if got := len(era.State.Validators); got != n {
|
||||
t.Fatalf("validator count: want %d got %d", n, got)
|
||||
}
|
||||
if got := era.State.Threshold; got != thr {
|
||||
t.Fatalf("threshold: want %d got %d", thr, got)
|
||||
}
|
||||
// The unqualified Bootstrap (Pedersen-DKG path) refuses t == n
|
||||
// because identifiable-abort detection requires at least one honest
|
||||
// non-sender per round. Confirm the dispatch.
|
||||
if _, _, err := Bootstrap(thr, validators, 0, 0, deterministicRand("public-bft")); err == nil {
|
||||
t.Fatal("Bootstrap with t == n should fail under Pedersen-DKG (t < n required)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,9 @@ import (
|
||||
// TestBootstrapPinsSuiteSHA3 — Gate 3A: Bootstrap with Corona-SHA3 →
|
||||
// era.HashSuiteID == "Corona-SHA3".
|
||||
func TestBootstrapPinsSuiteSHA3(t *testing.T) {
|
||||
era, err := BootstrapWithSuite(
|
||||
era, _, err := BootstrapWithSuite(
|
||||
hash.NewCoronaSHA3(),
|
||||
3,
|
||||
2,
|
||||
[]string{"a", "b", "c"},
|
||||
0, 0,
|
||||
deterministicRand("hashsuite-immut-sha3"),
|
||||
@@ -57,9 +57,9 @@ func TestBootstrapPinsSuiteSHA3(t *testing.T) {
|
||||
// TestBootstrapPinsSuiteBLAKE3 — Gate 3A: Bootstrap with Corona-BLAKE3
|
||||
// → era.HashSuiteID == "Corona-BLAKE3".
|
||||
func TestBootstrapPinsSuiteBLAKE3(t *testing.T) {
|
||||
era, err := BootstrapWithSuite(
|
||||
era, _, err := BootstrapWithSuite(
|
||||
hash.NewCoronaBLAKE3(),
|
||||
3,
|
||||
2,
|
||||
[]string{"a", "b", "c"},
|
||||
0, 0,
|
||||
deterministicRand("hashsuite-immut-blake3"),
|
||||
@@ -79,7 +79,7 @@ func TestBootstrapPinsSuiteBLAKE3(t *testing.T) {
|
||||
// pins the production default, so legacy callers cannot accidentally
|
||||
// open an era under no profile at all.
|
||||
func TestBootstrapDefaultsToSHA3(t *testing.T) {
|
||||
era, err := Bootstrap(3, []string{"a", "b", "c"}, 0, 0,
|
||||
era, _, err := Bootstrap(2, []string{"a", "b", "c"}, 0, 0,
|
||||
deterministicRand("hashsuite-default"))
|
||||
if err != nil {
|
||||
t.Fatalf("Bootstrap: %v", err)
|
||||
@@ -92,9 +92,9 @@ func TestBootstrapDefaultsToSHA3(t *testing.T) {
|
||||
// TestReshareCannotChangeSuiteSHA3 — Gate 3B: Reshare on a Corona-SHA3
|
||||
// era yields a state with HashSuiteID == "Corona-SHA3" (unchanged).
|
||||
func TestReshareCannotChangeSuiteSHA3(t *testing.T) {
|
||||
era, err := BootstrapWithSuite(
|
||||
era, _, err := BootstrapWithSuite(
|
||||
hash.NewCoronaSHA3(),
|
||||
3,
|
||||
2,
|
||||
[]string{"v1", "v2", "v3"},
|
||||
0, 0,
|
||||
deterministicRand("reshare-cannot-change-sha3"),
|
||||
@@ -104,7 +104,7 @@ func TestReshareCannotChangeSuiteSHA3(t *testing.T) {
|
||||
}
|
||||
priorID := era.HashSuiteID
|
||||
|
||||
next, err := era.Reshare([]string{"v1", "v2", "v3"}, 3, deterministicRand("reshare-1"))
|
||||
next, err := era.Reshare([]string{"v1", "v2", "v3"}, 2, deterministicRand("reshare-1"))
|
||||
if err != nil {
|
||||
t.Fatalf("Reshare: %v", err)
|
||||
}
|
||||
@@ -119,9 +119,9 @@ func TestReshareCannotChangeSuiteSHA3(t *testing.T) {
|
||||
// TestReshareCannotChangeSuiteBLAKE3 — Gate 3B mirrored on the legacy
|
||||
// profile. A BLAKE3-pinned era stays BLAKE3 across Reshare.
|
||||
func TestReshareCannotChangeSuiteBLAKE3(t *testing.T) {
|
||||
era, err := BootstrapWithSuite(
|
||||
era, _, err := BootstrapWithSuite(
|
||||
hash.NewCoronaBLAKE3(),
|
||||
3,
|
||||
2,
|
||||
[]string{"v1", "v2", "v3"},
|
||||
0, 0,
|
||||
deterministicRand("reshare-cannot-change-blake3"),
|
||||
@@ -178,9 +178,9 @@ func TestReshareAPIHasNoHashSuiteParameter(t *testing.T) {
|
||||
// identifiable-abort detection. The legacy t == n equivalence stays
|
||||
// available under ReanchorTrustedDealer for HSM-ceremony scenarios.
|
||||
func TestReanchorMayChangeSuite(t *testing.T) {
|
||||
era1, err := BootstrapWithSuite(
|
||||
era1, _, err := BootstrapWithSuite(
|
||||
hash.NewCoronaSHA3(),
|
||||
3,
|
||||
2,
|
||||
[]string{"a", "b", "c"},
|
||||
0, 1,
|
||||
deterministicRand("reanchor-era-1"),
|
||||
|
||||
+49
-29
@@ -132,29 +132,34 @@ type EpochShareState struct {
|
||||
Shares map[string]*threshold.KeyShare
|
||||
}
|
||||
|
||||
// Bootstrap runs the one-time trusted-dealer ceremony at chain genesis
|
||||
// or governance-gated Reanchor.
|
||||
// Bootstrap opens a new key era WITHOUT a trusted dealer.
|
||||
//
|
||||
// TRUST MODEL — TRUSTED DEALER.
|
||||
// This function instantiates the master secret s on a single party (the
|
||||
// dealer) for the duration of one stack frame. The dealer constructs
|
||||
// Shamir shares of s, hands them to the validator set, and zeroes the
|
||||
// in-memory copy of s before returning. The chain only holds the public
|
||||
// GroupKey and the distributed shares thereafter.
|
||||
// TRUST MODEL — PUBLIC-BFT-SAFE (default).
|
||||
// This function routes the keygen ceremony through `dkg2/` (Pedersen-
|
||||
// DKG over R_q) + Path (a) noise flooding so no single party ever
|
||||
// holds the master secret s at any point in the ceremony. Every
|
||||
// validator runs `dkg2.Round1` independently; the per-party noise
|
||||
// contributions aggregate into a Corona-Sign-shaped public key
|
||||
// `bTilde = Round_Xi(A·s + e'')` where each party adds one Gaussian
|
||||
// `e_j'` slice under σ'' = κ·σ_E·√n. Identifiable abort: a
|
||||
// misbehaving sender is named in a signed `dkg2.Complaint` carrying
|
||||
// re-checkable Pedersen evidence; the chain commits the abort
|
||||
// transcript and stays at the previous epoch.
|
||||
//
|
||||
// Foundation MUST coordinate Bootstrap as a publicly observable MPC
|
||||
// ceremony at chain launch: entropy from verifiable commit-and-reveal
|
||||
// among the genesis validators; dealer state erased before the ceremony
|
||||
// closes; HSM-bound execution preferred. Even with these mitigations,
|
||||
// the dealer's host platform is in the trust boundary for the duration
|
||||
// of the call.
|
||||
// On success returns (era, transcript, nil). The transcript is the
|
||||
// public, byte-stable record of the ceremony; honest validators that
|
||||
// observe the same cohort messages compute identical bytes. The
|
||||
// consensus layer commits to `transcript.TranscriptHash` to ratify
|
||||
// the era. On identifiable abort returns (nil, nil, err wrapping
|
||||
// ErrBootstrapPedersenAbort); extract via ExtractAbortEvidence.
|
||||
//
|
||||
// PUBLIC-BFT-SAFE ALTERNATIVE: use BootstrapPedersen, which runs
|
||||
// dkg2 (Pedersen-DKG over R_q) + Path (a) noise flooding so no party
|
||||
// ever holds s. BootstrapPedersen is the recommended entrypoint for any
|
||||
// new deployment; Bootstrap (and its BootstrapTrustedDealer alias) is
|
||||
// retained for genesis ceremonies where a non-distributed trust root is
|
||||
// explicitly chosen. See DEPLOYMENT-RUNBOOK.md §Bootstrap-Trust.
|
||||
// LEGACY ALTERNATIVE: use BootstrapTrustedDealer for HSM/TEE
|
||||
// ceremonies where the operator explicitly chooses a non-distributed
|
||||
// trust root (publicly observable HSM-bound ceremony with commit-
|
||||
// and-reveal entropy from genesis validators, dealer state zeroed
|
||||
// before ceremony close). The dealer's host platform is in the trust
|
||||
// boundary for the duration of the call. See DEPLOYMENT-RUNBOOK.md
|
||||
// §Bootstrap-Trust for the trust-model trade-off documentation.
|
||||
//
|
||||
// Use crypto/rand.Reader for the kernel's randomness when no specific
|
||||
// ceremony source is provided. Tests pass a deterministic source for
|
||||
@@ -163,16 +168,31 @@ type EpochShareState struct {
|
||||
// Bootstrap pins the production HashSuite (Corona-SHA3). Use
|
||||
// BootstrapWithSuite to open an era under the legacy Corona-BLAKE3
|
||||
// profile (for cross-suite KAT replay only — NOT for production).
|
||||
func Bootstrap(t int, validators []string, groupID CoronaGroupID, eraID CoronaKeyEraID, entropy io.Reader) (*KeyEra, error) {
|
||||
return BootstrapWithSuite(hash.Default(), t, validators, groupID, eraID, entropy)
|
||||
func Bootstrap(t int, validators []string, groupID CoronaGroupID, eraID CoronaKeyEraID, entropy io.Reader) (*KeyEra, *BootstrapTranscript, error) {
|
||||
return BootstrapPedersen(hash.Default(), t, validators, groupID, eraID, entropy)
|
||||
}
|
||||
|
||||
// BootstrapWithSuite is the canonical entrypoint that explicitly pins
|
||||
// the hash profile this era will run under. The supplied suite is
|
||||
// recorded on the returned KeyEra and propagates unchanged through
|
||||
// every Reshare; Reanchor opens a fresh era and MAY pin a different
|
||||
// suite. Pass nil to use the production default (Corona-SHA3).
|
||||
func BootstrapWithSuite(suite hash.HashSuite, t int, validators []string, groupID CoronaGroupID, eraID CoronaKeyEraID, entropy io.Reader) (*KeyEra, error) {
|
||||
// BootstrapWithSuite is the suite-explicit form of Bootstrap. It
|
||||
// routes through BootstrapPedersen (public-BFT-safe, no trusted
|
||||
// dealer) with the supplied HashSuite; pass nil to use the
|
||||
// production default (Corona-SHA3). See the Bootstrap docstring for
|
||||
// the trust-model disclosure.
|
||||
//
|
||||
// LEGACY ALTERNATIVE: use BootstrapTrustedDealerWithSuite for HSM/TEE
|
||||
// ceremony scenarios where a non-distributed trust root is acceptable
|
||||
// by policy.
|
||||
func BootstrapWithSuite(suite hash.HashSuite, t int, validators []string, groupID CoronaGroupID, eraID CoronaKeyEraID, entropy io.Reader) (*KeyEra, *BootstrapTranscript, error) {
|
||||
return BootstrapPedersen(suite, t, validators, groupID, eraID, entropy)
|
||||
}
|
||||
|
||||
// bootstrapTrustedDealerImpl is the historical trusted-dealer
|
||||
// implementation, retained as the body of BootstrapTrustedDealer /
|
||||
// BootstrapTrustedDealerWithSuite. Single source of truth so the
|
||||
// public-facing aliases cannot drift from each other. NO production
|
||||
// code path should call this directly; use BootstrapTrustedDealer
|
||||
// instead so the name signals the trust-model decision at the call
|
||||
// site.
|
||||
func bootstrapTrustedDealerImpl(suite hash.HashSuite, t int, validators []string, groupID CoronaGroupID, eraID CoronaKeyEraID, entropy io.Reader) (*KeyEra, error) {
|
||||
if len(validators) == 0 {
|
||||
return nil, ErrEmptyValidators
|
||||
}
|
||||
@@ -422,7 +442,7 @@ func ReanchorTrustedDealerWithSuite(prev *KeyEra, suite hash.HashSuite, t int, v
|
||||
nextEpoch = prev.State.Epoch + 1
|
||||
}
|
||||
}
|
||||
next, err := BootstrapWithSuite(suite, t, validators, groupID, nextEraID, entropy)
|
||||
next, err := bootstrapTrustedDealerImpl(suite, t, validators, groupID, nextEraID, entropy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+18
-17
@@ -14,14 +14,15 @@ import (
|
||||
|
||||
// TestBootstrapBuildsAndSigns confirms that Bootstrap returns a complete
|
||||
// KeyShare set that can produce a verifying signature under the produced
|
||||
// GroupKey. We exercise t = n (every validator in the active signing
|
||||
// set) here; corona's signing protocol assumes the full committee
|
||||
// participates in each Sign invocation.
|
||||
// GroupKey. The public-BFT-safe Bootstrap routes through Pedersen-DKG
|
||||
// which requires t < n (strict) for identifiable-abort detection, so
|
||||
// we exercise the threshold setup (t = n-1, signing committee = full
|
||||
// validator set).
|
||||
func TestBootstrapBuildsAndSigns(t *testing.T) {
|
||||
const tThr, n = 3, 3
|
||||
const tThr, n = 2, 3
|
||||
validators := []string{"validator-A", "validator-B", "validator-C"}
|
||||
|
||||
era, err := Bootstrap(tThr, validators, 0, 0, deterministicRand("bootstrap-genesis"))
|
||||
era, _, err := Bootstrap(tThr, validators, 0, 0, deterministicRand("bootstrap-genesis"))
|
||||
if err != nil {
|
||||
t.Fatalf("Bootstrap: %v", err)
|
||||
}
|
||||
@@ -45,10 +46,10 @@ func TestBootstrapBuildsAndSigns(t *testing.T) {
|
||||
// is unchanged after Reshare, and the new shares produce a signature
|
||||
// that verifies under the unchanged GroupKey.
|
||||
func TestReshareSameSetPreservesGroupKey(t *testing.T) {
|
||||
const tThr = 3
|
||||
const tThr = 2
|
||||
validators := []string{"v1", "v2", "v3"}
|
||||
|
||||
era, err := Bootstrap(tThr, validators, 0, 0, deterministicRand("genesis-A"))
|
||||
era, _, err := Bootstrap(tThr, validators, 0, 0, deterministicRand("genesis-A"))
|
||||
if err != nil {
|
||||
t.Fatalf("Bootstrap: %v", err)
|
||||
}
|
||||
@@ -74,12 +75,12 @@ func TestReshareSameSetPreservesGroupKey(t *testing.T) {
|
||||
// validator set with a different threshold. The new committee's shares
|
||||
// produce a signature that verifies under the unchanged GroupKey.
|
||||
func TestReshareNewCommitteePreservesGroupKey(t *testing.T) {
|
||||
const tOld = 3
|
||||
const tNew = 5
|
||||
const tOld = 2
|
||||
const tNew = 4
|
||||
oldSet := []string{"v1", "v2", "v3"}
|
||||
newSet := []string{"v4", "v5", "v6", "v7", "v8"}
|
||||
|
||||
era, err := Bootstrap(tOld, oldSet, 0, 0, deterministicRand("genesis-B"))
|
||||
era, _, err := Bootstrap(tOld, oldSet, 0, 0, deterministicRand("genesis-B"))
|
||||
if err != nil {
|
||||
t.Fatalf("Bootstrap: %v", err)
|
||||
}
|
||||
@@ -107,16 +108,16 @@ func TestReshareNewCommitteePreservesGroupKey(t *testing.T) {
|
||||
// TestReanchorOpensNewEra verifies Reanchor produces a fresh GroupKey
|
||||
// while monotonically advancing the epoch and bumping the EraID.
|
||||
//
|
||||
// The new committee uses t=2, n=3 because the v0.7.4 Reanchor routes
|
||||
// through Pedersen-DKG which requires t < n (strictly less than) for
|
||||
// identifiable-abort detection. The legacy t == n behaviour remains
|
||||
// available via ReanchorTrustedDealer.
|
||||
// The public-BFT-safe lifecycle (Bootstrap → Reshare → Reanchor) all
|
||||
// route through Pedersen-DKG which requires t < n (strictly less
|
||||
// than) for identifiable-abort detection. The legacy t == n behaviour
|
||||
// remains available via BootstrapTrustedDealer / ReanchorTrustedDealer.
|
||||
func TestReanchorOpensNewEra(t *testing.T) {
|
||||
era, err := Bootstrap(3, []string{"a", "b", "c"}, 0, 1, deterministicRand("era-1"))
|
||||
era, _, err := Bootstrap(2, []string{"a", "b", "c"}, 0, 1, deterministicRand("era-1"))
|
||||
if err != nil {
|
||||
t.Fatalf("Bootstrap: %v", err)
|
||||
}
|
||||
if _, err := era.Reshare([]string{"a", "b", "c"}, 3, deterministicRand("reshare-x")); err != nil {
|
||||
if _, err := era.Reshare([]string{"a", "b", "c"}, 2, deterministicRand("reshare-x")); err != nil {
|
||||
t.Fatalf("Reshare: %v", err)
|
||||
}
|
||||
prevEpoch := era.State.Epoch
|
||||
@@ -146,7 +147,7 @@ func TestReanchorOpensNewEra(t *testing.T) {
|
||||
|
||||
// TestReshareErrors covers the input-validation surface.
|
||||
func TestReshareErrors(t *testing.T) {
|
||||
era, err := Bootstrap(3, []string{"a", "b", "c"}, 0, 0, deterministicRand("err"))
|
||||
era, _, err := Bootstrap(2, []string{"a", "b", "c"}, 0, 0, deterministicRand("err"))
|
||||
if err != nil {
|
||||
t.Fatalf("Bootstrap: %v", err)
|
||||
}
|
||||
|
||||
@@ -264,10 +264,13 @@ func TestReanchorPedersen_DishonestParty(t *testing.T) {
|
||||
// identical Shamir kernel, so bTilde MUST agree.
|
||||
func TestReanchorTrustedDealer_LegacyAlias(t *testing.T) {
|
||||
const thr, n = 3, 3
|
||||
era1, err := Bootstrap(thr, []string{"a", "b", "c"}, 0, 1,
|
||||
// era1 uses BootstrapTrustedDealer because we exercise the t == n
|
||||
// trusted-dealer ceremony path; the public-BFT Bootstrap (Pedersen-
|
||||
// DKG) structurally requires t < n.
|
||||
era1, err := BootstrapTrustedDealer(thr, []string{"a", "b", "c"}, 0, 1,
|
||||
deterministicRand("reanchor-td-era1"))
|
||||
if err != nil {
|
||||
t.Fatalf("Bootstrap: %v", err)
|
||||
t.Fatalf("BootstrapTrustedDealer: %v", err)
|
||||
}
|
||||
prevEpoch := era1.State.Epoch
|
||||
prevGK := era1.GroupKey
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
1f1bae4662b359813c0cf2df837c09587dd33cbf7e0a0c4be20ef2c65ec2e052 dkg2/test/kat/dkg2_kat.json
|
||||
7e1e40fc0a1234a25f3fe88f728063afe68daa58d2ce1092fe214cb341af7dae test/kat/activation_kat.json
|
||||
d20ea754fd2b221bf1e8a49a94719dad7aa38edc583d476d0bb2104bb5819748 test/kat/reshare_kat.json
|
||||
4da08869ceefe6f526b1bc1037cf4e9d9fff0b519dd7f0bb8be08e40b0f1b0ec dkg2/test/kat/dkg2_kat.json
|
||||
b40f6d270d7c52eaa79f9a0925868cd89e28c2133d107589840d284ab51f698b test/kat/activation_kat.json
|
||||
7d522f3f03fc53bd7ff754624179b4bd493a01d18ce5e81b475f133c923af2ab test/kat/corona_oracle_v2/discrete_gaussian.json
|
||||
3d798070e2debcfbc6dddcb2396f00eab7be3c20879885b2eb6d216cd35e4ae2 test/kat/corona_oracle_v2/montgomery_ntt.json
|
||||
8921ef7d1a7170cfe2b5aeb7505a21dab33c480988b011106b98c0cc7e518b57 test/kat/corona_oracle_v2/prng_blake2_xof.json
|
||||
58e4e9f92434789785a2ed3f61a5d554e8e77ec09010813d804a25eb155ff506 test/kat/corona_oracle_v2/shamir_share.json
|
||||
c7efdc6836b2126324d1d4a5e0c788086ace17702fbe37630064dca5a26a90e6 test/kat/corona_oracle_v2/sign_verify_e2e.json
|
||||
3eb62c4abe3388208bebd6bdd1d9ea82edc5eb3ef83ed646bcc6c2ceaa3b03a1 test/kat/corona_oracle_v2/sign_verify_e2e.json
|
||||
b683814f35aa74694643dcde5b03bdae5d2a7036db8265203162e1e7c13b1fca test/kat/corona_oracle_v2/structs_matrix_wire.json
|
||||
af2c33a4119e9eecb3d59f8fec8562240e544c0c84716e2d9d95a11fa3ce89ae test/kat/corona_oracle_v2/transcript_hash.json
|
||||
ac3bca71d1774c3c1da3f44ceb52ed7b85efa0d7d4a31e1879fa5158f3b8671e test/kat/corona_oracle_v2/transcript_hash.json
|
||||
677bc9a0c999d16a49c82bcea3666d366ed70ae2731cde7b1ef99db27707654c test/kat/reshare_kat.json
|
||||
|
||||
+16
-16
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# regen-kats.sh — deterministic regeneration + verification of every
|
||||
# Pulsar KAT consumed by the C++ / GPU ports.
|
||||
# Corona KAT consumed by the C++ / GPU ports.
|
||||
#
|
||||
# Outputs (paths match what luxcpp/crypto/pulsar/test/kat/ expects):
|
||||
# <luxcpp>/crypto/pulsar/test/kat/reshare_kat.json
|
||||
# <luxcpp>/crypto/pulsar/dkg2/test/kat/dkg2_kat.json
|
||||
# <luxcpp>/crypto/pulsar/test/kat/activation_kat.json
|
||||
# <luxcpp>/crypto/pulsar/test/kat/corona_oracle_v2/{prng_blake2_xof,
|
||||
# Outputs (paths match what luxcpp/crypto/corona/test/kat/ expects):
|
||||
# <luxcpp>/crypto/corona/test/kat/reshare_kat.json
|
||||
# <luxcpp>/crypto/corona/dkg2/test/kat/dkg2_kat.json
|
||||
# <luxcpp>/crypto/corona/test/kat/activation_kat.json
|
||||
# <luxcpp>/crypto/corona/test/kat/corona_oracle_v2/{prng_blake2_xof,
|
||||
# discrete_gaussian, montgomery_ntt, structs_matrix_wire,
|
||||
# transcript_hash, shamir_share, sign_verify_e2e}.json
|
||||
#
|
||||
@@ -18,27 +18,27 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PULSAR_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
CORONA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
LUXCPP_DIR="${LUXCPP_DIR:-${HOME}/work/luxcpp}"
|
||||
KAT_BASE="${LUXCPP_DIR}/crypto/pulsar"
|
||||
KAT_BASE="${LUXCPP_DIR}/crypto/corona"
|
||||
|
||||
RESHARE_OUT="${KAT_BASE}/test/kat/reshare_kat.json"
|
||||
DKG2_OUT="${KAT_BASE}/dkg2/test/kat/dkg2_kat.json"
|
||||
ACTIVATION_OUT="${KAT_BASE}/test/kat/activation_kat.json"
|
||||
CORONA_DIR="${KAT_BASE}/test/kat/corona_oracle_v2"
|
||||
CORONA_V2_DIR="${KAT_BASE}/test/kat/corona_oracle_v2"
|
||||
|
||||
MANIFEST="${PULSAR_DIR}/scripts/regen-kats.manifest.sha256"
|
||||
MANIFEST="${CORONA_DIR}/scripts/regen-kats.manifest.sha256"
|
||||
|
||||
VERIFY=0
|
||||
if [[ "${1:-}" == "--verify" ]]; then
|
||||
VERIFY=1
|
||||
fi
|
||||
|
||||
cd "${PULSAR_DIR}"
|
||||
cd "${CORONA_DIR}"
|
||||
|
||||
# Pulsar oracles are indifferent to LUXCPP location through env vars and
|
||||
# Corona oracles are indifferent to LUXCPP location through env vars and
|
||||
# positional args. Run each, write to the canonical luxcpp path.
|
||||
mkdir -p "$(dirname "${RESHARE_OUT}")" "$(dirname "${DKG2_OUT}")" "${CORONA_DIR}"
|
||||
mkdir -p "$(dirname "${RESHARE_OUT}")" "$(dirname "${DKG2_OUT}")" "${CORONA_V2_DIR}"
|
||||
|
||||
echo "[1/5] reshare_oracle → ${RESHARE_OUT}"
|
||||
PULSAR_RESHARE_KAT_PATH="${RESHARE_OUT}" go run ./cmd/reshare_oracle >/dev/null
|
||||
@@ -56,8 +56,8 @@ PULSAR_DKG2_KAT_PATH="${DKG2_OUT}" go run ./cmd/dkg2_oracle >/dev/null
|
||||
echo "[3/5] activation_oracle → ${ACTIVATION_OUT}"
|
||||
PULSAR_ACTIVATION_KAT_PATH="${ACTIVATION_OUT}" go run ./cmd/activation_oracle >/dev/null
|
||||
|
||||
echo "[4/5] corona_oracle_v2 emit --out ${CORONA_DIR}"
|
||||
go run ./cmd/corona_oracle_v2 emit --out "${CORONA_DIR}" >/dev/null
|
||||
echo "[4/5] corona_oracle_v2 emit --out ${CORONA_V2_DIR}"
|
||||
go run ./cmd/corona_oracle_v2 emit --out "${CORONA_V2_DIR}" >/dev/null
|
||||
|
||||
echo "[4b/5] corona_oracle_v2 byte-equality test"
|
||||
go test -count=1 -run "TestEmitDeterministic|TestSignVerifyEntries|TestShamirRoundTrip" ./cmd/corona_oracle_v2 >/dev/null
|
||||
@@ -74,7 +74,7 @@ manifest_files=(
|
||||
"${DKG2_OUT}"
|
||||
"${ACTIVATION_OUT}"
|
||||
)
|
||||
for f in "${CORONA_DIR}"/*.json; do
|
||||
for f in "${CORONA_V2_DIR}"/*.json; do
|
||||
manifest_files+=("$f")
|
||||
done
|
||||
|
||||
|
||||
Reference in New Issue
Block a user