mirror of
https://github.com/luxfi/corona.git
synced 2026-07-27 02:50:34 +00:00
corona: symmetric domain separation — PULSAR-* tags -> CORONA-*
Leftover from Corona's "Pulsar-R" lineage. Pulsar (M-LWE) and Corona
(R-LWE) are independent constructions with separate hardness assumptions,
so their cSHAKE personalisation strings must be distinct.
Changes (Go, non-luxcpp):
- hash tags: PULSAR-HC-v1 -> CORONA-HC-v1, etc. (HU, TRANSCRIPT, PRF, MAC, PAIRWISE)
- profile IDs: "Pulsar-SHA3" -> "Corona-SHA3", "Pulsar-BLAKE3" -> "Corona-BLAKE3"
- context strings: pulsar.dkg2.A.v1 -> corona.dkg2.A.v1, etc.
- env vars: PULSAR_RESHARE_KAT_PATH -> CORONA_RESHARE_KAT_PATH, etc.
- struct names: pulsarSHA3 -> coronaSHA3
- KAT derive roots: sign_e2e_pulsar -> sign_e2e_corona
What's preserved (different scope):
- luxcpp/crypto/pulsar/* path references in comments (separate repo,
out of scope; the C++ side will rename in its own commit)
- Cross-runtime KAT files on disk (will regenerate next CI run)
All 11 packages test green: dkg, dkg2, hash, keyera, networking,
primitives, reshare, sign, threshold, utils, wire.
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
// activation_oracle — emits byte-equal KATs for the post-reshare
|
||||
// activation circuit-breaker. Drives reshare.ActivationMessage and
|
||||
// reshare.ReshareTranscript through their canonical SignableBytes /
|
||||
// Hash routines under the production Pulsar-SHA3 suite.
|
||||
// Hash routines under the production Corona-SHA3 suite.
|
||||
//
|
||||
// Wire format (per entry):
|
||||
//
|
||||
@@ -30,8 +30,8 @@
|
||||
// Output: <luxcpp/crypto>/pulsar/test/kat/activation_kat.json
|
||||
//
|
||||
// Algorithm references:
|
||||
// - pulsar/reshare/activation.go (canonical Go)
|
||||
// - luxcpp/crypto/pulsar/reshare/activation.hpp (C++ port)
|
||||
// - corona/reshare/activation.go (canonical Go)
|
||||
// - luxcpp/crypto/corona/reshare/activation.hpp (C++ port)
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -78,7 +78,7 @@ type Output struct {
|
||||
// ComplaintHashes so the KAT is reproducible without any RNG state.
|
||||
func counterDigest(label string, idx int) [32]byte {
|
||||
h := sha256.New()
|
||||
_, _ = io.WriteString(h, "pulsar.activation.kat.v1:")
|
||||
_, _ = io.WriteString(h, "corona.activation.kat.v1:")
|
||||
_, _ = io.WriteString(h, label)
|
||||
var b [8]byte
|
||||
for i := 0; i < 8; i++ {
|
||||
@@ -156,7 +156,7 @@ func entry(
|
||||
|
||||
func main() {
|
||||
out := Output{
|
||||
Suite: "Pulsar-SHA3",
|
||||
Suite: "Corona-SHA3",
|
||||
Version: "v1",
|
||||
}
|
||||
out.Entries = append(out.Entries,
|
||||
@@ -173,12 +173,12 @@ func main() {
|
||||
)
|
||||
|
||||
// Default output: canonical luxcpp KAT directory; allow override via
|
||||
// PULSAR_ACTIVATION_KAT_PATH env or a positional arg.
|
||||
// CORONA_ACTIVATION_KAT_PATH env or a positional arg.
|
||||
outPath := filepath.Join(
|
||||
os.Getenv("HOME"), "work", "luxcpp", "crypto", "pulsar",
|
||||
"test", "kat", "activation_kat.json",
|
||||
)
|
||||
if env := os.Getenv("PULSAR_ACTIVATION_KAT_PATH"); env != "" {
|
||||
if env := os.Getenv("CORONA_ACTIVATION_KAT_PATH"); env != "" {
|
||||
outPath = env
|
||||
}
|
||||
if len(os.Args) >= 2 {
|
||||
|
||||
@@ -43,11 +43,11 @@ import (
|
||||
// legacyBLAKE3Suite is the suite this oracle uses for every primitives.*
|
||||
// call. The JSON files produced here are the BLAKE3 KAT transcripts
|
||||
// downstream ports (C++, GPU) byte-match against. They were pinned before
|
||||
// the Pulsar-SHA3 default was wired into primitives/hash.go, so emission
|
||||
// the Corona-SHA3 default was wired into primitives/hash.go, so emission
|
||||
// must remain on the legacy BLAKE3 suite to keep the existing transcripts
|
||||
// byte-stable. A separate Pulsar-SHA3 KAT oracle lands as follow-up
|
||||
// byte-stable. A separate Corona-SHA3 KAT oracle lands as follow-up
|
||||
// (see CHANGELOG.md).
|
||||
var legacyBLAKE3Suite = pulsarhash.NewPulsarBLAKE3()
|
||||
var legacyBLAKE3Suite = pulsarhash.NewCoronaBLAKE3()
|
||||
|
||||
// MasterSeed is the deterministic root of all KAT generation. Changing it
|
||||
// invalidates every downstream port's expected outputs, so it stays fixed
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// Package main is the Pulsar cross-runtime KAT oracle.
|
||||
// Package main is the Corona cross-runtime KAT oracle.
|
||||
//
|
||||
// Emits a single JSON manifest at <out>/cross_runtime_kat.json that ties
|
||||
// together the three canonical Pulsar KATs (sign, reshare, dkg2) with
|
||||
// together the three canonical Corona KATs (sign, reshare, dkg2) with
|
||||
// the SHA-256 of each individual KAT file. The C++ side
|
||||
// (luxcpp/crypto/pulsar/test/cross_runtime_test.cpp) replays each KAT
|
||||
// (luxcpp/crypto/corona/test/cross_runtime_test.cpp) replays each KAT
|
||||
// in C++ and verifies byte-equality; running this oracle first then
|
||||
// the C++ test is the "Go → C++" direction of the gate.
|
||||
//
|
||||
// Reverse direction (C++ → Go) is handled by:
|
||||
// - luxcpp/crypto/pulsar/cmd/cross_runtime_oracle/ (C++ writer)
|
||||
// - luxcpp/crypto/corona/cmd/cross_runtime_oracle/ (C++ writer)
|
||||
// - lux/pulsar/cmd/cross_runtime_verify/ (Go reader)
|
||||
//
|
||||
// Determinism is required. Two runs with the same MasterSeed produce
|
||||
@@ -77,8 +77,8 @@ func main() {
|
||||
// hardcoded locations in luxcpp/crypto. For the cross-runtime gate
|
||||
// we hash the canonical paths.
|
||||
signPath := filepath.Join(*out, "sign_kat.json")
|
||||
resharePath := "/Users/z/work/luxcpp/crypto/pulsar/test/kat/reshare_kat.json"
|
||||
dkg2Path := "/Users/z/work/luxcpp/crypto/pulsar/dkg2/test/kat/dkg2_kat.json"
|
||||
resharePath := "/Users/z/work/luxcpp/crypto/corona/test/kat/reshare_kat.json"
|
||||
dkg2Path := "/Users/z/work/luxcpp/crypto/corona/dkg2/test/kat/dkg2_kat.json"
|
||||
|
||||
files := []struct {
|
||||
name string
|
||||
@@ -90,7 +90,7 @@ func main() {
|
||||
}
|
||||
|
||||
m := manifest{
|
||||
Description: "Pulsar cross-runtime KAT manifest. SHA-256 of each canonical Go-emitted KAT JSON. The C++ cross_runtime_test consumes the same paths and asserts byte-equality at every entry; this manifest pins the Go-side bytes so any drift produces a SHA-256 mismatch caught by the gate.",
|
||||
Description: "Corona cross-runtime KAT manifest. SHA-256 of each canonical Go-emitted KAT JSON. The C++ cross_runtime_test consumes the same paths and asserts byte-equality at every entry; this manifest pins the Go-side bytes so any drift produces a SHA-256 mismatch caught by the gate.",
|
||||
Direction: "go-to-cpp",
|
||||
}
|
||||
for _, f := range files {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Package main is the Go-side verifier for the Pulsar cross-runtime
|
||||
// Package main is the Go-side verifier for the Corona cross-runtime
|
||||
// KAT gate (C++ → Go direction).
|
||||
//
|
||||
// Reads a C++-emitted manifest produced by
|
||||
// luxcpp/crypto/pulsar/cmd/cross_runtime_oracle and confirms that each
|
||||
// luxcpp/crypto/corona/cmd/cross_runtime_oracle and confirms that each
|
||||
// SHA-256 digest in the manifest matches the bytes Go observes for the
|
||||
// same file path. Mismatch → non-zero exit code.
|
||||
//
|
||||
@@ -11,8 +11,8 @@
|
||||
// cross_runtime_verify --manifest <path/to/cross_runtime_kat_cpp.json>
|
||||
//
|
||||
// This is the reverse leg of the cross-runtime gate. The forward leg
|
||||
// (Go → C++) lives in luxcpp/crypto/pulsar/test/cross_runtime_test.cpp;
|
||||
// CTest target `pulsar_cross_runtime_kat` exercises both directions.
|
||||
// (Go → C++) lives in luxcpp/crypto/corona/test/cross_runtime_test.cpp;
|
||||
// CTest target `corona_cross_runtime_kat` exercises both directions.
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
+12
-12
@@ -10,8 +10,8 @@
|
||||
// 2. From that master seed, derives one 32-byte sub-seed per party
|
||||
// (BLAKE3(master || "party" || BE32(i))).
|
||||
// 3. Constructs n DKGSessions (party 0..n-1) — they all share the same
|
||||
// deterministic A and B matrices (derived from b"pulsar.dkg2.A.v1" /
|
||||
// b"pulsar.dkg2.B.v1" via BLAKE3-XOF).
|
||||
// deterministic A and B matrices (derived from b"corona.dkg2.A.v1" /
|
||||
// b"corona.dkg2.B.v1" via BLAKE3-XOF).
|
||||
// 4. Each party calls Round1WithSeed(party_seed[i]) to produce its
|
||||
// Commits, Shares (per recipient), and Blinds (per recipient).
|
||||
// 5. Each party calls Round2 with the assembled shares/blinds/commits.
|
||||
@@ -32,7 +32,7 @@
|
||||
// one entry must be identical. The KAT records all n of them so the C++
|
||||
// port can prove that property too.
|
||||
//
|
||||
// Output: <luxcpp/crypto>/pulsar/dkg2/test/kat/dkg2_kat.json (4 entries:
|
||||
// Output: <luxcpp/crypto>/corona/dkg2/test/kat/dkg2_kat.json (4 entries:
|
||||
// 2-of-3, 3-of-5, 5-of-7, 7-of-11).
|
||||
package main
|
||||
|
||||
@@ -159,8 +159,8 @@ func runEntry(t, n int) Entry {
|
||||
sessions := make([]*dkg2.DKGSession, n)
|
||||
for i := 0; i < n; i++ {
|
||||
// Use the legacy BLAKE3 hash suite for the canonical KAT — keeps
|
||||
// every byte in dkg2_kat.json byte-stable across the Pulsar-SHA3
|
||||
// cutover. Production-track callers use hash.Default() (Pulsar-SHA3).
|
||||
// every byte in dkg2_kat.json byte-stable across the Corona-SHA3
|
||||
// cutover. Production-track callers use hash.Default() (Corona-SHA3).
|
||||
s, err := dkg2.NewDKGSession(params, i, n, t, nil)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("NewDKGSession(%d, %d, %d): %w", i, n, t, err))
|
||||
@@ -272,8 +272,8 @@ func main() {
|
||||
out := OracleOut{
|
||||
Description: "Pedersen-style DKG over R = Z_q[X]/(X^256+1), Q=0x1000000004A01. " +
|
||||
"C_k = A·NTT(c_k) + B·NTT(r_k) — hiding under MLWE on B, binding under " +
|
||||
"MSIS on [A|B]. A derived from BLAKE3(\"pulsar.dkg2.A.v1\"); B from " +
|
||||
"BLAKE3(\"pulsar.dkg2.B.v1\"). Each entry runs the full t-of-n protocol " +
|
||||
"MSIS on [A|B]. A derived from BLAKE3(\"corona.dkg2.A.v1\"); B from " +
|
||||
"BLAKE3(\"corona.dkg2.B.v1\"). Each entry runs the full t-of-n protocol " +
|
||||
"with deterministic per-party Round1WithSeed inputs derived from " +
|
||||
"MasterSeed=0xC0FFEEF00DFACE. Wire format: structs.{Vector,Matrix}[ring.Poly]" +
|
||||
".WriteTo (LE u64). Hashes are SHA-256 of those wire bytes. CommitDigest " +
|
||||
@@ -284,8 +284,8 @@ func main() {
|
||||
M: sign.M,
|
||||
Nvec: sign.N,
|
||||
Xi: sign.Xi,
|
||||
TagAHex: hex.EncodeToString([]byte("pulsar.dkg2.A.v1")),
|
||||
TagBHex: hex.EncodeToString([]byte("pulsar.dkg2.B.v1")),
|
||||
TagAHex: hex.EncodeToString([]byte("corona.dkg2.A.v1")),
|
||||
TagBHex: hex.EncodeToString([]byte("corona.dkg2.B.v1")),
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
@@ -295,10 +295,10 @@ func main() {
|
||||
|
||||
// Default output path: canonical luxcpp KAT directory. Pass an
|
||||
// argument to override. When the oracle is invoked via `go run` from
|
||||
// the pulsar repo root, the relative form ../../../luxcpp/... resolves
|
||||
// the corona repo root, the relative form ../../../luxcpp/... resolves
|
||||
// correctly; otherwise pass an absolute path.
|
||||
outPath := "../../../luxcpp/crypto/pulsar/dkg2/test/kat/dkg2_kat.json"
|
||||
if env := os.Getenv("PULSAR_DKG2_KAT_PATH"); env != "" {
|
||||
outPath := "../../../luxcpp/crypto/corona/dkg2/test/kat/dkg2_kat.json"
|
||||
if env := os.Getenv("CORONA_DKG2_KAT_PATH"); env != "" {
|
||||
outPath = env
|
||||
}
|
||||
if len(os.Args) >= 2 {
|
||||
|
||||
+10
-10
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// reshare_oracle — emits byte-equal KATs for the proactive secret-resharing
|
||||
// protocol implemented in github.com/luxfi/corona/reshare. The C++ port at
|
||||
// ~/work/luxcpp/crypto/pulsar/reshare/ replays each entry's seeds and must
|
||||
// ~/work/luxcpp/crypto/corona/reshare/ replays each entry's seeds and must
|
||||
// produce share bytes whose SHA-256 commitment matches the entry's
|
||||
// new_share_sha256_hex field.
|
||||
//
|
||||
@@ -35,16 +35,16 @@
|
||||
// old_set, new_set, t_old, t_new) and must reproduce
|
||||
// (old_shares_hex, new_shares_hex) exactly.
|
||||
//
|
||||
// Output: ~/work/luxcpp/crypto/pulsar/test/kat/reshare_kat.json
|
||||
// Output: ~/work/luxcpp/crypto/corona/test/kat/reshare_kat.json
|
||||
//
|
||||
// Algorithm references:
|
||||
// - pulsar/reshare/reshare.go (canonical Go)
|
||||
// - pulsar/papers/lp-073-pulsar/sections/06-resharing.tex (paper)
|
||||
// - corona/reshare/reshare.go (canonical Go)
|
||||
// - corona/papers/lp-073-pulsar/sections/06-resharing.tex (paper)
|
||||
//
|
||||
// Note on RNG choice: production reshare.Reshare consumes from
|
||||
// crypto/rand.Reader by default. For KAT determinism we substitute a
|
||||
// SHA-256-counter PRNG (counterRand below). The same counterRand is
|
||||
// implemented in C++ (luxcpp/crypto/pulsar/reshare/counter_rand.cpp).
|
||||
// implemented in C++ (luxcpp/crypto/corona/reshare/counter_rand.cpp).
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -72,7 +72,7 @@ const (
|
||||
// QByteLen = len(big.Int.Bytes(Q)).
|
||||
QByteLen = 7
|
||||
// PolyCount is sign.Nvec = 7 (the secret-vector dimension in the
|
||||
// production Pulsar path). Smaller values would also work; 7 lets
|
||||
// production Corona path). Smaller values would also work; 7 lets
|
||||
// the KAT exercise the same shape as production.
|
||||
PolyCount = 7
|
||||
)
|
||||
@@ -212,14 +212,14 @@ func main() {
|
||||
}
|
||||
|
||||
out := OracleOut{
|
||||
Description: "Pulsar proactive resharing KAT. " +
|
||||
Description: "Corona proactive resharing KAT. " +
|
||||
"Each entry deterministically reconstructs (1) Shamir shares of " +
|
||||
"a planted secret s for the old committee and (2) reshared " +
|
||||
"shares for the new committee, using counterRand SHA-256 streams " +
|
||||
"seeded by old_shamir_seed_hex / reshare_rng_seed_hex. The new " +
|
||||
"shares interpolate (any t_new of them) to the SAME s. The " +
|
||||
"public key b in production is computed from s and is therefore " +
|
||||
"unchanged across resharing — see pulsar/papers/lp-073-pulsar/" +
|
||||
"unchanged across resharing — see corona/papers/lp-073-pulsar/" +
|
||||
"sections/06-resharing.tex.",
|
||||
Modulus: Q,
|
||||
NPoly: N,
|
||||
@@ -344,12 +344,12 @@ func main() {
|
||||
}
|
||||
|
||||
// Write the file. Default output: canonical luxcpp KAT directory;
|
||||
// allow override via PULSAR_RESHARE_KAT_PATH or a positional arg.
|
||||
// allow override via CORONA_RESHARE_KAT_PATH or a positional arg.
|
||||
outPath := filepath.Join(
|
||||
os.Getenv("HOME"), "work", "luxcpp", "crypto", "pulsar",
|
||||
"test", "kat", "reshare_kat.json",
|
||||
)
|
||||
if env := os.Getenv("PULSAR_RESHARE_KAT_PATH"); env != "" {
|
||||
if env := os.Getenv("CORONA_RESHARE_KAT_PATH"); env != "" {
|
||||
outPath = env
|
||||
}
|
||||
if len(os.Args) >= 2 {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// Package main is the Pulsar sign+verify KAT oracle.
|
||||
// Package main is the Corona sign+verify KAT oracle.
|
||||
//
|
||||
// Given a fixed master seed, it emits a deterministic JSON file of
|
||||
// known-answer test vectors covering the full LP-073 Pulsar threshold
|
||||
// known-answer test vectors covering the full LP-073 Corona threshold
|
||||
// signature pipeline (Gen + SignRound1 + SignRound2{Preprocess,} +
|
||||
// SignFinalize + Verify) for the canonical (t, n) configurations
|
||||
// 2-of-3, 3-of-5, 5-of-7, 7-of-11. The C++ port at
|
||||
// luxcpp/crypto/pulsar/cpp/sign/ replays these entries byte-equal.
|
||||
// luxcpp/crypto/corona/cpp/sign/ replays these entries byte-equal.
|
||||
//
|
||||
// Pulsar's sign/sign.go is byte-identical to corona/sign/sign.go (the
|
||||
// Corona's sign/sign.go is byte-identical to corona/sign/sign.go (the
|
||||
// only diff is the import path), so the JSON shape mirrors the existing
|
||||
// corona sign_verify_e2e KAT.
|
||||
//
|
||||
@@ -127,7 +127,7 @@ type signEntry struct {
|
||||
}
|
||||
|
||||
func emitSignVerify(outDir string) error {
|
||||
root := derive("sign_e2e_pulsar")
|
||||
root := derive("sign_e2e_corona")
|
||||
cfgs := []struct{ t, n int }{
|
||||
{2, 3}, {3, 5}, {5, 7}, {7, 11},
|
||||
}
|
||||
@@ -139,10 +139,10 @@ func emitSignVerify(outDir string) error {
|
||||
Description string `json:"description"`
|
||||
Entries []signEntry `json:"entries"`
|
||||
}{
|
||||
Description: "Full Pulsar Sign+Verify round-trip (LP-073 Q-witness). " +
|
||||
Description: "Full Corona Sign+Verify round-trip (LP-073 Q-witness). " +
|
||||
"For each (t,n,msg,seed): Gen → SignRound1 (all parties) → " +
|
||||
"SignRound2Preprocess+SignRound2 (all parties) → SignFinalize → " +
|
||||
"Verify. Pulsar's sign module is byte-identical to the original construction at " +
|
||||
"Verify. Corona's sign module is byte-identical to the original construction at " +
|
||||
"the Go source level (only the import path differs). The current " +
|
||||
"KAT signs with K=Threshold=n; the t field documents the " +
|
||||
"threshold-aware variant for downstream use. SHA-256 hashes are " +
|
||||
|
||||
+4
-4
@@ -139,11 +139,11 @@ type Complaint struct {
|
||||
//
|
||||
// Format:
|
||||
//
|
||||
// "pulsar.dkg2.complaint.v1" || transcript || sender_id_be32 ||
|
||||
// "corona.dkg2.complaint.v1" || transcript || sender_id_be32 ||
|
||||
// complainer_id_be32 || reason_u8 || evidence_len_be32 || evidence
|
||||
func (c *Complaint) Bytes() []byte {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("pulsar.dkg2.complaint.v1")
|
||||
buf.WriteString("corona.dkg2.complaint.v1")
|
||||
buf.Write(c.TranscriptHash[:])
|
||||
var b4 [4]byte
|
||||
binary.BigEndian.PutUint32(b4[:], uint32(c.SenderID))
|
||||
@@ -186,10 +186,10 @@ func (c *Complaint) Verify() error {
|
||||
// commit to the SET of complaints in the Round 2 transcript and the
|
||||
// activation message.
|
||||
//
|
||||
// Pass nil for the production default (Pulsar-SHA3).
|
||||
// Pass nil for the production default (Corona-SHA3).
|
||||
func ComplaintHash(suite hash.HashSuite, c *Complaint) [32]byte {
|
||||
s := hash.Resolve(suite)
|
||||
return s.TranscriptHash([]byte("pulsar.dkg2.complaint-hash.v1"), c.Bytes(), c.Signature)
|
||||
return s.TranscriptHash([]byte("corona.dkg2.complaint-hash.v1"), c.Bytes(), c.Signature)
|
||||
}
|
||||
|
||||
// DisqualificationThreshold returns the minimum number of distinct,
|
||||
|
||||
+23
-23
@@ -2,10 +2,10 @@
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package dkg2 implements a Pedersen-style verifiable-secret-sharing-based
|
||||
// distributed key generation over the Pulsar polynomial ring
|
||||
// distributed key generation over the Corona polynomial ring
|
||||
// R_q = Z_q[X]/(X^256 + 1).
|
||||
//
|
||||
// dkg2 is the production parallel-track keygen for Pulsar. It replaces the
|
||||
// dkg2 is the production parallel-track keygen for Corona. It replaces the
|
||||
// pseudoinverse-recoverable Feldman commit C_k = A · NTT(c_k) of the
|
||||
// upstream Corona DKG with a Pedersen commit
|
||||
//
|
||||
@@ -16,7 +16,7 @@
|
||||
// decisional MLWE on B; binding holds under MSIS on the wide concatenation
|
||||
// [A | B]. Formal statements live in
|
||||
// papers/lp-073-pulsar/sections/07-pedersen-dkg.tex; Lean theorem references
|
||||
// are in proofs/lean/Crypto/Pulsar/dkg2.lean.
|
||||
// are in proofs/lean/Crypto/Corona/dkg2.lean.
|
||||
//
|
||||
// # Round structure
|
||||
//
|
||||
@@ -45,14 +45,14 @@
|
||||
// # Hash suite
|
||||
//
|
||||
// dkg2 routes every cohort-bound digest through the canonical
|
||||
// hash.HashSuite (Pulsar-SHA3 in production; Pulsar-BLAKE3 retained for
|
||||
// hash.HashSuite (Corona-SHA3 in production; Corona-BLAKE3 retained for
|
||||
// byte-equality with pre-cutover KATs). NewDKGSession accepts a HashSuite;
|
||||
// nil resolves to the production default. Matrix derivation (A, B) uses a
|
||||
// dedicated, version-pinned BLAKE3 path to keep KAT bytes stable across
|
||||
// the SHA3 cutover — the matrix derivation is structural, not transcript-
|
||||
// bound, so it has its own version tag (pulsar.dkg2.A.v1 / .B.v1). The
|
||||
// bound, so it has its own version tag (corona.dkg2.A.v1 / .B.v1). The
|
||||
// Round 1.5 commit digest, by contrast, is HashSuite-bound and uses the
|
||||
// PULSAR-TRANSCRIPT-v1 customization of the active suite.
|
||||
// CORONA-TRANSCRIPT-v1 customization of the active suite.
|
||||
//
|
||||
// # Identifiable abort
|
||||
//
|
||||
@@ -64,12 +64,12 @@
|
||||
//
|
||||
// # File-level invariants
|
||||
//
|
||||
// - All ring arithmetic uses sign.Q (the Pulsar 48-bit prime).
|
||||
// - All ring arithmetic uses sign.Q (the Corona 48-bit prime).
|
||||
// - Sampler parameters reuse sign.SigmaE / sign.BoundE for both c_{i,k}
|
||||
// and r_{i,k}, mirroring the Pulsar secret distribution.
|
||||
// - A is derived from the 16-byte tag b"pulsar.dkg2.A.v1" via BLAKE3-XOF
|
||||
// and r_{i,k}, mirroring the Corona secret distribution.
|
||||
// - A is derived from the 16-byte tag b"corona.dkg2.A.v1" via BLAKE3-XOF
|
||||
// (KAT-pinned).
|
||||
// - B is derived from the 16-byte tag b"pulsar.dkg2.B.v1" via BLAKE3-XOF
|
||||
// - B is derived from the 16-byte tag b"corona.dkg2.B.v1" via BLAKE3-XOF
|
||||
// (KAT-pinned).
|
||||
// - Commits are stored in NTT-Montgomery form (matches A, B).
|
||||
// - Shares are stored in standard coefficient form (NTT=false, mont=false).
|
||||
@@ -80,7 +80,7 @@
|
||||
//
|
||||
// Round1WithSeed pins every byte of the protocol output for byte-equal C++
|
||||
// porting. See cmd/dkg2_oracle for the canonical generator and
|
||||
// luxcpp/crypto/pulsar/dkg2/test/kat/dkg2_kat.json for the 4 reference
|
||||
// luxcpp/crypto/corona/dkg2/test/kat/dkg2_kat.json for the 4 reference
|
||||
// entries (2-of-3, 3-of-5, 5-of-7, 7-of-11).
|
||||
package dkg2
|
||||
|
||||
@@ -113,18 +113,18 @@ import (
|
||||
// breaking compatibility.
|
||||
//
|
||||
// Matrix derivation is BLAKE3 directly, NOT the active HashSuite. This keeps
|
||||
// public-matrix bytes stable across the Pulsar-SHA3 cutover (the matrices
|
||||
// public-matrix bytes stable across the Corona-SHA3 cutover (the matrices
|
||||
// are structural, not transcript-bound; their version tag covers any future
|
||||
// rotation).
|
||||
var (
|
||||
tagA = []byte("pulsar.dkg2.A.v1")
|
||||
tagB = []byte("pulsar.dkg2.B.v1")
|
||||
tagA = []byte("corona.dkg2.A.v1")
|
||||
tagB = []byte("corona.dkg2.B.v1")
|
||||
)
|
||||
|
||||
// Customization tag bound into the Round 1.5 commit-digest under the active
|
||||
// HashSuite. The suite ID is bound into the digest input as well so two
|
||||
// suites can never produce a colliding digest for the same commit vector.
|
||||
const tagCommitDigest = "PULSAR-DKG2-COMMIT-DIGEST-v1"
|
||||
const tagCommitDigest = "CORONA-DKG2-COMMIT-DIGEST-v1"
|
||||
|
||||
var (
|
||||
ErrInvalidThreshold = errors.New("dkg2: threshold must be > 0 and < total parties")
|
||||
@@ -150,7 +150,7 @@ type Params struct {
|
||||
//
|
||||
// Mirrors dkg.NewParams: the RXi ring is the post-rounding modulus
|
||||
// (Xi = 30 bits, sign.QXi = 0x40000 = 2^18) — not prime, but that's the
|
||||
// canonical Pulsar layout used by sign.Gen and the Round2 b_ped output.
|
||||
// canonical Corona layout used by sign.Gen and the Round2 b_ped output.
|
||||
// ring.NewRing returns a non-prime-modulus error here that we deliberately
|
||||
// ignore (matches dkg/dkg.go:53), and the constructor remains usable
|
||||
// because RoundVector only needs the ring as a coefficient container.
|
||||
@@ -240,7 +240,7 @@ func (r *Round1Output) SerializeCommits() ([]byte, error) {
|
||||
|
||||
// CommitDigest returns the Round 1.5 cross-party-consistency digest under
|
||||
// the supplied HashSuite. Passing nil resolves to the production default
|
||||
// (Pulsar-SHA3); pass hash.NewPulsarBLAKE3() for byte-equal replay against
|
||||
// (Corona-SHA3); pass hash.NewCoronaBLAKE3() for byte-equal replay against
|
||||
// the canonical KATs.
|
||||
//
|
||||
// Format (suite-agnostic):
|
||||
@@ -260,7 +260,7 @@ func (r *Round1Output) CommitDigest(suite hash.HashSuite) ([32]byte, error) {
|
||||
}
|
||||
|
||||
// CommitDigestBLAKE3 returns the legacy BLAKE3 commit digest used by the
|
||||
// pre-SHA3-cutover KAT (pulsar/dkg2 oracle, luxcpp dkg2_kat.json). Format:
|
||||
// pre-SHA3-cutover KAT (corona/dkg2 oracle, luxcpp dkg2_kat.json). Format:
|
||||
//
|
||||
// BLAKE3(serialize(Commits[0]) || ... || serialize(Commits[t-1]))[:32]
|
||||
//
|
||||
@@ -301,9 +301,9 @@ type DKGSession struct {
|
||||
//
|
||||
// suite parameterizes the cohort-bound hash routines (Round 1.5 commit
|
||||
// digest, complaint transcripts). nil resolves to the production default
|
||||
// (Pulsar-SHA3). Public-matrix derivation is deliberately HashSuite-
|
||||
// (Corona-SHA3). Public-matrix derivation is deliberately HashSuite-
|
||||
// independent — it uses a dedicated BLAKE3 path so KAT bytes stay stable
|
||||
// across the Pulsar-SHA3 cutover (see package documentation).
|
||||
// across the Corona-SHA3 cutover (see package documentation).
|
||||
//
|
||||
// Mirrors dkg.NewDKGSession exactly except that the matrices A, B are
|
||||
// derived from public domain-separated tags via BLAKE3, removing the
|
||||
@@ -603,11 +603,11 @@ func uint64SliceToBytes(s []uint64) []byte {
|
||||
//
|
||||
// Returns (s_j, u_j, b_ped) on success.
|
||||
//
|
||||
// s_j = Σ_i share_{i→j} (Pulsar secret share)
|
||||
// u_j = Σ_i blind_{i→j} (private; discarded by Pulsar Sign callers)
|
||||
// s_j = Σ_i share_{i→j} (Corona secret share)
|
||||
// u_j = Σ_i blind_{i→j} (private; discarded by Corona Sign callers)
|
||||
// b_ped = Σ_i C_{i,0} (rounded to Xi, Pedersen-shaped pk)
|
||||
//
|
||||
// b_ped has shape Round_Xi(A·s + B·t_master). Pulsar Sign verification
|
||||
// b_ped has shape Round_Xi(A·s + B·t_master). Corona Sign verification
|
||||
// running in 2-secret mode (path (b)) takes (A, B, b_ped) jointly; see
|
||||
// papers/lp-073-pulsar/sections/07-pedersen-dkg.tex §Mapping for the
|
||||
// integration recipe.
|
||||
|
||||
+19
-19
@@ -87,8 +87,8 @@ func TestDKG2_DeterministicMatrices(t *testing.T) {
|
||||
// the hash exchanged in Round 1.5 to defeat the cross-party-inconsistency
|
||||
// attack (Finding 2 of RED-DKG-REVIEW.md).
|
||||
//
|
||||
// Runs against both supported hash suites (Pulsar-SHA3 default and
|
||||
// Pulsar-BLAKE3 legacy) so neither the signature surface nor the byte
|
||||
// Runs against both supported hash suites (Corona-SHA3 default and
|
||||
// Corona-BLAKE3 legacy) so neither the signature surface nor the byte
|
||||
// stability silently regresses across the cutover.
|
||||
func TestDKG2_CommitDigestConsistency(t *testing.T) {
|
||||
params, err := NewParams()
|
||||
@@ -101,8 +101,8 @@ func TestDKG2_CommitDigestConsistency(t *testing.T) {
|
||||
s hash.HashSuite
|
||||
}{
|
||||
{"default", nil},
|
||||
{"sha3", hash.NewPulsarSHA3()},
|
||||
{"blake3", hash.NewPulsarBLAKE3()},
|
||||
{"sha3", hash.NewCoronaSHA3()},
|
||||
{"blake3", hash.NewCoronaBLAKE3()},
|
||||
}
|
||||
|
||||
for _, sc := range suites {
|
||||
@@ -160,7 +160,7 @@ func TestDKG2_HashSuiteCrossProfile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("NewParams: %v", err)
|
||||
}
|
||||
sess, err := NewDKGSession(params, 0, 3, 2, hash.NewPulsarSHA3())
|
||||
sess, err := NewDKGSession(params, 0, 3, 2, hash.NewCoronaSHA3())
|
||||
if err != nil {
|
||||
t.Fatalf("NewDKGSession: %v", err)
|
||||
}
|
||||
@@ -172,11 +172,11 @@ func TestDKG2_HashSuiteCrossProfile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Round1: %v", err)
|
||||
}
|
||||
dSHA3, err := out.CommitDigest(hash.NewPulsarSHA3())
|
||||
dSHA3, err := out.CommitDigest(hash.NewCoronaSHA3())
|
||||
if err != nil {
|
||||
t.Fatalf("CommitDigest sha3: %v", err)
|
||||
}
|
||||
dBLAKE3, err := out.CommitDigest(hash.NewPulsarBLAKE3())
|
||||
dBLAKE3, err := out.CommitDigest(hash.NewCoronaBLAKE3())
|
||||
if err != nil {
|
||||
t.Fatalf("CommitDigest blake3: %v", err)
|
||||
}
|
||||
@@ -679,7 +679,7 @@ func TestDKG2_FilterQualifiedQuorum(t *testing.T) {
|
||||
//
|
||||
// A · NTT(s) + B · NTT(t_master) ?= Σ_i C_{i,0} (mod q)
|
||||
//
|
||||
// where s = Σ_j λ_j · s_j is the reconstructed Pulsar secret and
|
||||
// where s = Σ_j λ_j · s_j is the reconstructed Corona secret and
|
||||
// t_master = Σ_j λ_j · u_j is the reconstructed blinding scalar (Lagrange
|
||||
// recombination over an arbitrary t-subset T).
|
||||
//
|
||||
@@ -869,23 +869,23 @@ func TestDKG2_VerifyShareAgainstCommits_Pure(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestDKG2_SignIntegration_PathC — closes the loop from DKG2 output to a
|
||||
// Pulsar Sign-compatible public key via the recommended path (c) of
|
||||
// Corona Sign-compatible public key via the recommended path (c) of
|
||||
// papers/lp-073-pulsar/sections/08a-pedersen-dkg.tex.
|
||||
//
|
||||
// Path (c) in production: run dkg2, recombine s = Σ_j λ_j s_j over a
|
||||
// t-subset, sample fresh Gaussian e, build b = A·s + e, round to bTilde,
|
||||
// then run Pulsar Sign as normal under bTilde. This test mechanises the
|
||||
// "DKG-output → Pulsar-shaped pk" leg and confirms (i) recombined s has
|
||||
// then run Corona Sign as normal under bTilde. This test mechanises the
|
||||
// "DKG-output → Corona-shaped pk" leg and confirms (i) recombined s has
|
||||
// the expected dimension and lattice shape, (ii) the b = A·s + e
|
||||
// construction yields a Pulsar-shaped bTilde of the right shape, and
|
||||
// construction yields a Corona-shaped bTilde of the right shape, and
|
||||
// (iii) the round-trip RestoreVector(RoundVector(b)) deviates from b by
|
||||
// at most the Xi rounding tolerance — i.e., Pulsar Sign Verify's
|
||||
// at most the Xi rounding tolerance — i.e., Corona Sign Verify's
|
||||
// L2-norm check would accept a signature produced under (A, bTilde).
|
||||
//
|
||||
// The full Sign1/Sign2/Combine path uses sign.Gen which generates s
|
||||
// internally; injecting an externally-supplied DKG s into Sign requires
|
||||
// the small refactor of sign.Gen to accept an external secret. That
|
||||
// refactor is independent of dkg2 and tracked in pulsar/sign; the
|
||||
// refactor is independent of dkg2 and tracked in corona/sign; the
|
||||
// algebraic compatibility this test confirms is the binding contract
|
||||
// between dkg2 and Sign.
|
||||
func TestDKG2_SignIntegration_PathC(t *testing.T) {
|
||||
@@ -953,9 +953,9 @@ func TestDKG2_SignIntegration_PathC(t *testing.T) {
|
||||
utils.MatrixVectorMul(r, A, sNTT, asN)
|
||||
utils.ConvertVectorFromNTT(r, asN)
|
||||
|
||||
// Sample fresh small e — uses the same parameters Pulsar Sign Gen
|
||||
// Sample fresh small e — uses the same parameters Corona Sign Gen
|
||||
// uses (sign.go:66), so the resulting (A, b) pair is statistically
|
||||
// identical to a trusted-dealer Pulsar setup.
|
||||
// identical to a trusted-dealer Corona setup.
|
||||
eSeed := make([]byte, sign.KeySize)
|
||||
for i := range eSeed {
|
||||
eSeed[i] = byte(0xE5 ^ i)
|
||||
@@ -974,7 +974,7 @@ func TestDKG2_SignIntegration_PathC(t *testing.T) {
|
||||
}
|
||||
|
||||
// Round and restore — the deviation must be within the Xi rounding
|
||||
// tolerance. The Pulsar Sign verify path does exactly this round-trip
|
||||
// tolerance. The Corona Sign verify path does exactly this round-trip
|
||||
// (sign/sign.go:284-285).
|
||||
bTilde := utils.RoundVector(r, params.RXi, b, sign.Xi)
|
||||
bRestored := utils.RestoreVector(r, params.RXi, bTilde, sign.Xi)
|
||||
@@ -1033,7 +1033,7 @@ func TestDKG2_KAT(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("CommitDigestBLAKE3: %v", err)
|
||||
}
|
||||
dSHA3, err := out.CommitDigest(hash.NewPulsarSHA3())
|
||||
dSHA3, err := out.CommitDigest(hash.NewCoronaSHA3())
|
||||
if err != nil {
|
||||
t.Fatalf("CommitDigest sha3: %v", err)
|
||||
}
|
||||
@@ -1048,7 +1048,7 @@ func TestDKG2_KAT(t *testing.T) {
|
||||
t.Fatalf("Round1WithSeed (2): %v", err)
|
||||
}
|
||||
dBLAKE3b, _ := out2.CommitDigestBLAKE3()
|
||||
dSHA3b, _ := out2.CommitDigest(hash.NewPulsarSHA3())
|
||||
dSHA3b, _ := out2.CommitDigest(hash.NewCoronaSHA3())
|
||||
if dBLAKE3 != dBLAKE3b {
|
||||
t.Fatal("CommitDigestBLAKE3 not stable across sessions")
|
||||
}
|
||||
|
||||
@@ -26,12 +26,12 @@ import (
|
||||
// fuzzMaxRawSize bounds the input handed to the lattigo decoder so
|
||||
// the unpatched-upstream-lattigo DoS path (luxfi/lattice#2) is
|
||||
// neutralized by the recover boundary in <1ms. See
|
||||
// pulsar/threshold/fuzz_round_test.go for the rationale.
|
||||
// corona/threshold/fuzz_round_test.go for the rationale.
|
||||
const fuzzMaxRawSize = 1024
|
||||
|
||||
// decodeVectorWithRecover decodes a Vector[Poly] from raw bytes with
|
||||
// the same defense-in-depth stack as
|
||||
// pulsar/threshold/fuzz_round_test.go: hard byte cap + recover.
|
||||
// corona/threshold/fuzz_round_test.go: hard byte cap + recover.
|
||||
func decodeVectorWithRecover(raw []byte) (err error) {
|
||||
if len(raw) > fuzzMaxRawSize {
|
||||
return fmt.Errorf("input exceeds fuzzMaxRawSize")
|
||||
|
||||
+15
-15
@@ -3,9 +3,9 @@
|
||||
|
||||
package hash
|
||||
|
||||
// PulsarBLAKE3 is a NON-NORMATIVE legacy suite kept for byte-equality
|
||||
// checks against transcripts produced before the Pulsar-SHA3 profile
|
||||
// was pinned as canonical. NEW deployments MUST use Pulsar-SHA3.
|
||||
// CoronaBLAKE3 is a NON-NORMATIVE legacy suite kept for byte-equality
|
||||
// checks against transcripts produced before the Corona-SHA3 profile
|
||||
// was pinned as canonical. NEW deployments MUST use Corona-SHA3.
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
@@ -13,22 +13,22 @@ import (
|
||||
"github.com/zeebo/blake3"
|
||||
)
|
||||
|
||||
// pulsarBLAKE3 implements HashSuite using BLAKE3 primitives.
|
||||
type pulsarBLAKE3 struct{}
|
||||
// coronaBLAKE3 implements HashSuite using BLAKE3 primitives.
|
||||
type coronaBLAKE3 struct{}
|
||||
|
||||
// NewPulsarBLAKE3 returns the legacy BLAKE3 suite. NOT for production.
|
||||
func NewPulsarBLAKE3() HashSuite { return pulsarBLAKE3{} }
|
||||
// NewCoronaBLAKE3 returns the legacy BLAKE3 suite. NOT for production.
|
||||
func NewCoronaBLAKE3() HashSuite { return coronaBLAKE3{} }
|
||||
|
||||
func (pulsarBLAKE3) ID() string { return "Pulsar-BLAKE3" }
|
||||
func (coronaBLAKE3) ID() string { return "Corona-BLAKE3" }
|
||||
|
||||
func (pulsarBLAKE3) Hc(transcript []byte) []byte {
|
||||
func (coronaBLAKE3) Hc(transcript []byte) []byte {
|
||||
h := blake3.New()
|
||||
_, _ = h.Write([]byte(tagHC))
|
||||
_, _ = h.Write(transcript)
|
||||
return h.Sum(nil)[:32]
|
||||
}
|
||||
|
||||
func (pulsarBLAKE3) Hu(transcript []byte, outLen int) []byte {
|
||||
func (coronaBLAKE3) Hu(transcript []byte, outLen int) []byte {
|
||||
h := blake3.New()
|
||||
_, _ = h.Write([]byte(tagHU))
|
||||
_, _ = h.Write(transcript)
|
||||
@@ -37,7 +37,7 @@ func (pulsarBLAKE3) Hu(transcript []byte, outLen int) []byte {
|
||||
return out
|
||||
}
|
||||
|
||||
func (pulsarBLAKE3) TranscriptHash(parts ...[]byte) [32]byte {
|
||||
func (coronaBLAKE3) TranscriptHash(parts ...[]byte) [32]byte {
|
||||
h := blake3.New()
|
||||
_, _ = h.Write([]byte(tagTranscript))
|
||||
for _, p := range parts {
|
||||
@@ -51,7 +51,7 @@ func (pulsarBLAKE3) TranscriptHash(parts ...[]byte) [32]byte {
|
||||
return out
|
||||
}
|
||||
|
||||
func (pulsarBLAKE3) PRF(key, msg []byte, outLen int) []byte {
|
||||
func (coronaBLAKE3) PRF(key, msg []byte, outLen int) []byte {
|
||||
keyArr := blake3SizedKey(key)
|
||||
h, _ := blake3.NewKeyed(keyArr[:])
|
||||
_, _ = h.Write([]byte(tagPRF))
|
||||
@@ -61,7 +61,7 @@ func (pulsarBLAKE3) PRF(key, msg []byte, outLen int) []byte {
|
||||
return out
|
||||
}
|
||||
|
||||
func (pulsarBLAKE3) MAC(key, msg []byte, outLen int) []byte {
|
||||
func (coronaBLAKE3) MAC(key, msg []byte, outLen int) []byte {
|
||||
keyArr := blake3SizedKey(key)
|
||||
h, _ := blake3.NewKeyed(keyArr[:])
|
||||
_, _ = h.Write([]byte(tagMAC))
|
||||
@@ -71,7 +71,7 @@ func (pulsarBLAKE3) MAC(key, msg []byte, outLen int) []byte {
|
||||
return out
|
||||
}
|
||||
|
||||
func (pulsarBLAKE3) DerivePairwise(
|
||||
func (coronaBLAKE3) DerivePairwise(
|
||||
kex []byte,
|
||||
chainID, groupID []byte,
|
||||
eraID, generation uint64,
|
||||
@@ -111,7 +111,7 @@ func blake3SizedKey(kex []byte) [32]byte {
|
||||
return out
|
||||
}
|
||||
h := blake3.New()
|
||||
_, _ = h.Write([]byte("PULSAR-KDF-KEY-v1"))
|
||||
_, _ = h.Write([]byte("CORONA-KDF-KEY-v1"))
|
||||
_, _ = h.Write(kex)
|
||||
var out [32]byte
|
||||
copy(out[:], h.Sum(nil)[:32])
|
||||
|
||||
+10
-10
@@ -2,15 +2,15 @@
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package hash defines the canonical hashing profile used by every
|
||||
// Pulsar reshare / activation / pairwise routine.
|
||||
// Corona reshare / activation / pairwise routine.
|
||||
//
|
||||
// Two profiles are shipped:
|
||||
//
|
||||
// - Pulsar-SHA3 — the production profile. Built on cSHAKE256, KMAC256,
|
||||
// - Corona-SHA3 — the production profile. Built on cSHAKE256, KMAC256,
|
||||
// and TupleHash256 from FIPS 202 / NIST SP 800-185. KATs in the
|
||||
// reshare oracle are emitted under this profile.
|
||||
//
|
||||
// - Pulsar-BLAKE3 — the legacy / non-normative profile. Preserved so
|
||||
// - Corona-BLAKE3 — the legacy / non-normative profile. Preserved so
|
||||
// historical bytes can be reproduced for cross-checks. Marked NOT
|
||||
// for production.
|
||||
//
|
||||
@@ -34,12 +34,12 @@
|
||||
// produce different bytes (cross-profile collision avoidance).
|
||||
package hash
|
||||
|
||||
// HashSuite is the canonical hashing surface every Pulsar reshare,
|
||||
// HashSuite is the canonical hashing surface every Corona reshare,
|
||||
// activation, and pairwise routine uses. Implementations are
|
||||
// stateless, goroutine-safe, and deterministic.
|
||||
type HashSuite interface {
|
||||
// ID returns the profile identifier, e.g. "Pulsar-SHA3" or
|
||||
// "Pulsar-BLAKE3". Bound into transcripts so two profiles can
|
||||
// ID returns the profile identifier, e.g. "Corona-SHA3" or
|
||||
// "Corona-BLAKE3". Bound into transcripts so two profiles can
|
||||
// never collide on the byte level.
|
||||
ID() string
|
||||
|
||||
@@ -82,14 +82,14 @@ type HashSuite interface {
|
||||
) []byte
|
||||
}
|
||||
|
||||
// Default returns the production hash suite: Pulsar-SHA3.
|
||||
// Default returns the production hash suite: Corona-SHA3.
|
||||
func Default() HashSuite { return defaultSuite }
|
||||
|
||||
// DefaultID is the string ID of the production suite.
|
||||
const DefaultID = "Pulsar-SHA3"
|
||||
const DefaultID = "Corona-SHA3"
|
||||
|
||||
// LegacyBLAKE3ID is the string ID of the non-normative legacy suite.
|
||||
const LegacyBLAKE3ID = "Pulsar-BLAKE3"
|
||||
const LegacyBLAKE3ID = "Corona-BLAKE3"
|
||||
|
||||
// resolve picks the suite to use for a given call.
|
||||
func resolve(s HashSuite) HashSuite {
|
||||
@@ -103,4 +103,4 @@ func resolve(s HashSuite) HashSuite {
|
||||
func Resolve(s HashSuite) HashSuite { return resolve(s) }
|
||||
|
||||
// defaultSuite is the package-level singleton for the production profile.
|
||||
var defaultSuite HashSuite = NewPulsarSHA3()
|
||||
var defaultSuite HashSuite = NewCoronaSHA3()
|
||||
|
||||
+16
-16
@@ -12,14 +12,14 @@ import (
|
||||
|
||||
// TestSuiteIDs ensures the two suites declare distinct, stable IDs.
|
||||
func TestSuiteIDs(t *testing.T) {
|
||||
sha3 := NewPulsarSHA3()
|
||||
bl3 := NewPulsarBLAKE3()
|
||||
sha3 := NewCoronaSHA3()
|
||||
bl3 := NewCoronaBLAKE3()
|
||||
|
||||
if sha3.ID() != "Pulsar-SHA3" {
|
||||
t.Errorf("SHA3 suite ID: want Pulsar-SHA3, got %q", sha3.ID())
|
||||
if sha3.ID() != "Corona-SHA3" {
|
||||
t.Errorf("SHA3 suite ID: want Corona-SHA3, got %q", sha3.ID())
|
||||
}
|
||||
if bl3.ID() != "Pulsar-BLAKE3" {
|
||||
t.Errorf("BLAKE3 suite ID: want Pulsar-BLAKE3, got %q", bl3.ID())
|
||||
if bl3.ID() != "Corona-BLAKE3" {
|
||||
t.Errorf("BLAKE3 suite ID: want Corona-BLAKE3, got %q", bl3.ID())
|
||||
}
|
||||
if sha3.ID() == bl3.ID() {
|
||||
t.Error("SHA3 and BLAKE3 suites must declare distinct IDs")
|
||||
@@ -31,7 +31,7 @@ func TestSuiteIDs(t *testing.T) {
|
||||
|
||||
// TestDefaultIsSHA3 — production default must be SHA3.
|
||||
func TestDefaultIsSHA3(t *testing.T) {
|
||||
if Default().ID() != "Pulsar-SHA3" {
|
||||
if Default().ID() != "Corona-SHA3" {
|
||||
t.Fatalf("production default must be SHA3; got %q", Default().ID())
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func TestResolveNil(t *testing.T) {
|
||||
if Resolve(nil).ID() != DefaultID {
|
||||
t.Errorf("Resolve(nil) must return the production default")
|
||||
}
|
||||
bl3 := NewPulsarBLAKE3()
|
||||
bl3 := NewCoronaBLAKE3()
|
||||
if Resolve(bl3).ID() != bl3.ID() {
|
||||
t.Errorf("Resolve(suite) must return that suite")
|
||||
}
|
||||
@@ -50,8 +50,8 @@ func TestResolveNil(t *testing.T) {
|
||||
// TestSuitesProduceDifferentBytes — two suites with distinct IDs must
|
||||
// produce distinct bytes for the same input.
|
||||
func TestSuitesProduceDifferentBytes(t *testing.T) {
|
||||
sha3 := NewPulsarSHA3()
|
||||
bl3 := NewPulsarBLAKE3()
|
||||
sha3 := NewCoronaSHA3()
|
||||
bl3 := NewCoronaBLAKE3()
|
||||
|
||||
t1 := sha3.TranscriptHash([]byte("a"), []byte("b"))
|
||||
t2 := bl3.TranscriptHash([]byte("a"), []byte("b"))
|
||||
@@ -76,7 +76,7 @@ func TestSuitesProduceDifferentBytes(t *testing.T) {
|
||||
// TestPRFAndMACDifferByCustomization — same suite, same key, same
|
||||
// message, but PRF and MAC must produce distinct bytes.
|
||||
func TestPRFAndMACDifferByCustomization(t *testing.T) {
|
||||
for _, s := range []HashSuite{NewPulsarSHA3(), NewPulsarBLAKE3()} {
|
||||
for _, s := range []HashSuite{NewCoronaSHA3(), NewCoronaBLAKE3()} {
|
||||
key := []byte("00000000000000000000000000000000") // 32 bytes
|
||||
msg := []byte("same-message")
|
||||
prf := s.PRF(key, msg, 32)
|
||||
@@ -90,7 +90,7 @@ func TestPRFAndMACDifferByCustomization(t *testing.T) {
|
||||
// TestHcAndHuDifferByCustomization — same suite, same input, different
|
||||
// tags → different bytes.
|
||||
func TestHcAndHuDifferByCustomization(t *testing.T) {
|
||||
for _, s := range []HashSuite{NewPulsarSHA3(), NewPulsarBLAKE3()} {
|
||||
for _, s := range []HashSuite{NewCoronaSHA3(), NewCoronaBLAKE3()} {
|
||||
hc := s.Hc([]byte("transcript"))
|
||||
hu := s.Hu([]byte("transcript"), 32)
|
||||
if bytes.Equal(hc, hu) {
|
||||
@@ -102,7 +102,7 @@ func TestHcAndHuDifferByCustomization(t *testing.T) {
|
||||
// TestTranscriptHashAvoidsConcatenationCollisions — TupleHash framing
|
||||
// must reject two distinct lists whose naive concatenation is identical.
|
||||
func TestTranscriptHashAvoidsConcatenationCollisions(t *testing.T) {
|
||||
for _, s := range []HashSuite{NewPulsarSHA3(), NewPulsarBLAKE3()} {
|
||||
for _, s := range []HashSuite{NewCoronaSHA3(), NewCoronaBLAKE3()} {
|
||||
a := s.TranscriptHash([]byte("abc"), []byte(""))
|
||||
b := s.TranscriptHash([]byte("a"), []byte("bc"))
|
||||
if a == b {
|
||||
@@ -118,7 +118,7 @@ func TestTranscriptHashAvoidsConcatenationCollisions(t *testing.T) {
|
||||
// TestPairwiseCanonicalOrdering — DerivePairwise must produce the same
|
||||
// bytes for (i, j) and (j, i).
|
||||
func TestPairwiseCanonicalOrdering(t *testing.T) {
|
||||
for _, s := range []HashSuite{NewPulsarSHA3(), NewPulsarBLAKE3()} {
|
||||
for _, s := range []HashSuite{NewCoronaSHA3(), NewCoronaBLAKE3()} {
|
||||
kex := []byte("0123456789abcdef0123456789abcdef")
|
||||
ab := s.DerivePairwise(kex, []byte("chain"), []byte("group"), 7, 3, 2, 5, 32)
|
||||
ba := s.DerivePairwise(kex, []byte("chain"), []byte("group"), 7, 3, 5, 2, 32)
|
||||
@@ -131,7 +131,7 @@ func TestPairwiseCanonicalOrdering(t *testing.T) {
|
||||
// TestPairwiseDistinctEras — different (era, generation) MUST yield
|
||||
// different pairwise material.
|
||||
func TestPairwiseDistinctEras(t *testing.T) {
|
||||
for _, s := range []HashSuite{NewPulsarSHA3(), NewPulsarBLAKE3()} {
|
||||
for _, s := range []HashSuite{NewCoronaSHA3(), NewCoronaBLAKE3()} {
|
||||
kex := []byte("0123456789abcdef0123456789abcdef")
|
||||
base := s.DerivePairwise(kex, []byte("chain"), []byte("group"), 7, 3, 2, 5, 32)
|
||||
era2 := s.DerivePairwise(kex, []byte("chain"), []byte("group"), 8, 3, 2, 5, 32)
|
||||
@@ -221,7 +221,7 @@ func TestTupleHash256NISTVector(t *testing.T) {
|
||||
|
||||
// TestSuiteDeterminism — same input, two calls, identical bytes.
|
||||
func TestSuiteDeterminism(t *testing.T) {
|
||||
for _, s := range []HashSuite{NewPulsarSHA3(), NewPulsarBLAKE3()} {
|
||||
for _, s := range []HashSuite{NewCoronaSHA3(), NewCoronaBLAKE3()} {
|
||||
a := s.TranscriptHash([]byte("a"), []byte("b"))
|
||||
b := s.TranscriptHash([]byte("a"), []byte("b"))
|
||||
if a != b {
|
||||
|
||||
+25
-25
@@ -3,17 +3,17 @@
|
||||
|
||||
package hash
|
||||
|
||||
// PulsarSHA3 is the production hash suite for Pulsar. Built on
|
||||
// CoronaSHA3 is the production hash suite for Corona. Built on
|
||||
// cSHAKE256 / KMAC256 / TupleHash256 from FIPS 202 and NIST SP 800-185.
|
||||
//
|
||||
// Customization tags pin every operation to the Pulsar protocol:
|
||||
// Customization tags pin every operation to the Corona protocol:
|
||||
//
|
||||
// Hc "PULSAR-HC-v1"
|
||||
// Hu "PULSAR-HU-v1"
|
||||
// TranscriptHash "PULSAR-TRANSCRIPT-v1"
|
||||
// PRF "PULSAR-PRF-v1" (KMAC256)
|
||||
// MAC "PULSAR-MAC-v1" (KMAC256)
|
||||
// DerivePairwise "PULSAR-PAIRWISE-v1" (KMAC256)
|
||||
// Hc "CORONA-HC-v1"
|
||||
// Hu "CORONA-HU-v1"
|
||||
// TranscriptHash "CORONA-TRANSCRIPT-v1"
|
||||
// PRF "CORONA-PRF-v1" (KMAC256)
|
||||
// MAC "CORONA-MAC-v1" (KMAC256)
|
||||
// DerivePairwise "CORONA-PAIRWISE-v1" (KMAC256)
|
||||
//
|
||||
// Distinct customization strings are essential — same primitive +
|
||||
// different tag = independent oracle. Bumping any tag invalidates
|
||||
@@ -26,46 +26,46 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
tagHC = "PULSAR-HC-v1"
|
||||
tagHU = "PULSAR-HU-v1"
|
||||
tagTranscript = "PULSAR-TRANSCRIPT-v1"
|
||||
tagPRF = "PULSAR-PRF-v1"
|
||||
tagMAC = "PULSAR-MAC-v1"
|
||||
tagPairwise = "PULSAR-PAIRWISE-v1"
|
||||
tagHC = "CORONA-HC-v1"
|
||||
tagHU = "CORONA-HU-v1"
|
||||
tagTranscript = "CORONA-TRANSCRIPT-v1"
|
||||
tagPRF = "CORONA-PRF-v1"
|
||||
tagMAC = "CORONA-MAC-v1"
|
||||
tagPairwise = "CORONA-PAIRWISE-v1"
|
||||
)
|
||||
|
||||
// pulsarSHA3 implements HashSuite using the SP 800-185 primitives.
|
||||
type pulsarSHA3 struct{}
|
||||
// coronaSHA3 implements HashSuite using the SP 800-185 primitives.
|
||||
type coronaSHA3 struct{}
|
||||
|
||||
// NewPulsarSHA3 returns the production hash suite.
|
||||
func NewPulsarSHA3() HashSuite { return pulsarSHA3{} }
|
||||
// NewCoronaSHA3 returns the production hash suite.
|
||||
func NewCoronaSHA3() HashSuite { return coronaSHA3{} }
|
||||
|
||||
func (pulsarSHA3) ID() string { return "Pulsar-SHA3" }
|
||||
func (coronaSHA3) ID() string { return "Corona-SHA3" }
|
||||
|
||||
func (pulsarSHA3) Hc(transcript []byte) []byte {
|
||||
func (coronaSHA3) Hc(transcript []byte) []byte {
|
||||
return cshake256Stream(tagHC, transcript, 32)
|
||||
}
|
||||
|
||||
func (pulsarSHA3) Hu(transcript []byte, outLen int) []byte {
|
||||
func (coronaSHA3) Hu(transcript []byte, outLen int) []byte {
|
||||
return cshake256Stream(tagHU, transcript, outLen)
|
||||
}
|
||||
|
||||
func (pulsarSHA3) TranscriptHash(parts ...[]byte) [32]byte {
|
||||
func (coronaSHA3) TranscriptHash(parts ...[]byte) [32]byte {
|
||||
out := tupleHash256(parts, 32, tagTranscript)
|
||||
var fixed [32]byte
|
||||
copy(fixed[:], out)
|
||||
return fixed
|
||||
}
|
||||
|
||||
func (pulsarSHA3) PRF(key, msg []byte, outLen int) []byte {
|
||||
func (coronaSHA3) PRF(key, msg []byte, outLen int) []byte {
|
||||
return kmac256(key, msg, outLen, tagPRF)
|
||||
}
|
||||
|
||||
func (pulsarSHA3) MAC(key, msg []byte, outLen int) []byte {
|
||||
func (coronaSHA3) MAC(key, msg []byte, outLen int) []byte {
|
||||
return kmac256(key, msg, outLen, tagMAC)
|
||||
}
|
||||
|
||||
func (pulsarSHA3) DerivePairwise(
|
||||
func (coronaSHA3) DerivePairwise(
|
||||
kex []byte,
|
||||
chainID, groupID []byte,
|
||||
eraID, generation uint64,
|
||||
|
||||
@@ -33,11 +33,11 @@ import (
|
||||
"github.com/luxfi/corona/hash"
|
||||
)
|
||||
|
||||
// TestBootstrapPinsSuiteSHA3 — Gate 3A: Bootstrap with Pulsar-SHA3 →
|
||||
// era.HashSuiteID == "Pulsar-SHA3".
|
||||
// TestBootstrapPinsSuiteSHA3 — Gate 3A: Bootstrap with Corona-SHA3 →
|
||||
// era.HashSuiteID == "Corona-SHA3".
|
||||
func TestBootstrapPinsSuiteSHA3(t *testing.T) {
|
||||
era, err := BootstrapWithSuite(
|
||||
hash.NewPulsarSHA3(),
|
||||
hash.NewCoronaSHA3(),
|
||||
3,
|
||||
[]string{"a", "b", "c"},
|
||||
0, 0,
|
||||
@@ -54,11 +54,11 @@ func TestBootstrapPinsSuiteSHA3(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBootstrapPinsSuiteBLAKE3 — Gate 3A: Bootstrap with Pulsar-BLAKE3
|
||||
// → era.HashSuiteID == "Pulsar-BLAKE3".
|
||||
// TestBootstrapPinsSuiteBLAKE3 — Gate 3A: Bootstrap with Corona-BLAKE3
|
||||
// → era.HashSuiteID == "Corona-BLAKE3".
|
||||
func TestBootstrapPinsSuiteBLAKE3(t *testing.T) {
|
||||
era, err := BootstrapWithSuite(
|
||||
hash.NewPulsarBLAKE3(),
|
||||
hash.NewCoronaBLAKE3(),
|
||||
3,
|
||||
[]string{"a", "b", "c"},
|
||||
0, 0,
|
||||
@@ -89,11 +89,11 @@ func TestBootstrapDefaultsToSHA3(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestReshareCannotChangeSuiteSHA3 — Gate 3B: Reshare on a Pulsar-SHA3
|
||||
// era yields a state with HashSuiteID == "Pulsar-SHA3" (unchanged).
|
||||
// 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(
|
||||
hash.NewPulsarSHA3(),
|
||||
hash.NewCoronaSHA3(),
|
||||
3,
|
||||
[]string{"v1", "v2", "v3"},
|
||||
0, 0,
|
||||
@@ -120,7 +120,7 @@ func TestReshareCannotChangeSuiteSHA3(t *testing.T) {
|
||||
// profile. A BLAKE3-pinned era stays BLAKE3 across Reshare.
|
||||
func TestReshareCannotChangeSuiteBLAKE3(t *testing.T) {
|
||||
era, err := BootstrapWithSuite(
|
||||
hash.NewPulsarBLAKE3(),
|
||||
hash.NewCoronaBLAKE3(),
|
||||
3,
|
||||
[]string{"v1", "v2", "v3"},
|
||||
0, 0,
|
||||
@@ -170,11 +170,11 @@ func TestReshareAPIHasNoHashSuiteParameter(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestReanchorMayChangeSuite — Gate 3C: ReanchorWithSuite from a
|
||||
// Pulsar-SHA3 era to a Pulsar-BLAKE3 era yields era_2.HashSuiteID ==
|
||||
// "Pulsar-BLAKE3", and era_1 is unchanged.
|
||||
// Corona-SHA3 era to a Corona-BLAKE3 era yields era_2.HashSuiteID ==
|
||||
// "Corona-BLAKE3", and era_1 is unchanged.
|
||||
func TestReanchorMayChangeSuite(t *testing.T) {
|
||||
era1, err := BootstrapWithSuite(
|
||||
hash.NewPulsarSHA3(),
|
||||
hash.NewCoronaSHA3(),
|
||||
3,
|
||||
[]string{"a", "b", "c"},
|
||||
0, 1,
|
||||
@@ -189,7 +189,7 @@ func TestReanchorMayChangeSuite(t *testing.T) {
|
||||
|
||||
era2, err := ReanchorWithSuite(
|
||||
era1,
|
||||
hash.NewPulsarBLAKE3(),
|
||||
hash.NewCoronaBLAKE3(),
|
||||
3,
|
||||
[]string{"d", "e", "f"},
|
||||
0,
|
||||
|
||||
+25
-25
@@ -1,7 +1,7 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package keyera is the lifecycle wrapper for a Pulsar group lineage.
|
||||
// Package keyera is the lifecycle wrapper for a Corona group lineage.
|
||||
//
|
||||
// One KeyEra is opened by Bootstrap (a one-time foundation MPC ceremony
|
||||
// at chain genesis or governance-gated Reanchor). The trust is confined
|
||||
@@ -18,7 +18,7 @@
|
||||
//
|
||||
// BLS lane: each validator has its OWN keypair.
|
||||
// ML-DSA lane: each validator has its OWN keypair.
|
||||
// Pulsar lane: each validator has a SHARE of one group key.
|
||||
// Corona lane: each validator has a SHARE of one group key.
|
||||
//
|
||||
// Within a key era:
|
||||
//
|
||||
@@ -60,17 +60,17 @@ var (
|
||||
ErrMissingShare = errors.New("keyera: share missing for validator")
|
||||
)
|
||||
|
||||
// PulsarKeyEraID is a monotonically increasing identifier for a key era.
|
||||
// CoronaKeyEraID is a monotonically increasing identifier for a key era.
|
||||
// Bumped only on Reanchor (rare governance event). All resharings
|
||||
// within an era keep the same era ID.
|
||||
type PulsarKeyEraID uint64
|
||||
type CoronaKeyEraID uint64
|
||||
|
||||
// PulsarGroupID identifies one Pulsar group for grouped Quasar setups
|
||||
// CoronaGroupID identifies one Corona group for grouped Quasar setups
|
||||
// where validator sets are partitioned into smaller groups, each with
|
||||
// its own GroupKey lineage. For the single-group case it is zero.
|
||||
type PulsarGroupID uint64
|
||||
type CoronaGroupID uint64
|
||||
|
||||
// KeyEra is one Pulsar group lineage. The GroupKey (A, bTilde) is set at
|
||||
// KeyEra is one Corona group lineage. The GroupKey (A, bTilde) is set at
|
||||
// Bootstrap and persists across every Reshare within the era. State is
|
||||
// the current epoch's share distribution; it rotates each Reshare.
|
||||
//
|
||||
@@ -81,8 +81,8 @@ type PulsarGroupID uint64
|
||||
// field is read-only after Bootstrap returns; Reshare propagates it
|
||||
// without parameterisation.
|
||||
type KeyEra struct {
|
||||
EraID PulsarKeyEraID
|
||||
GroupID PulsarGroupID
|
||||
EraID CoronaKeyEraID
|
||||
GroupID CoronaGroupID
|
||||
GroupKey *threshold.GroupKey
|
||||
GenesisEpoch uint64
|
||||
HashSuiteID string
|
||||
@@ -96,12 +96,12 @@ type KeyEra struct {
|
||||
// Three lineage fields, kept distinct (do not collapse — they mean
|
||||
// different things):
|
||||
//
|
||||
// - KeyEraID: Pulsar group-key lineage. Bumps only at Reanchor (fresh
|
||||
// - KeyEraID: Corona group-key lineage. Bumps only at Reanchor (fresh
|
||||
// GroupKey).
|
||||
// - Generation: LSS resharing version within this key era. Bumps
|
||||
// every Refresh / Reshare under the same GroupKey. Aligns with
|
||||
// LSS's Generation field; managed by threshold/protocols/lss when
|
||||
// this state is driven through the LSS-Pulsar adapter.
|
||||
// this state is driven through the LSS-Corona adapter.
|
||||
// - RollbackFrom: nonzero only when this state descends from a
|
||||
// Rollback (= the prior Generation that was reverted from). Zero
|
||||
// on ordinary forward transitions.
|
||||
@@ -136,7 +136,7 @@ type EpochShareState struct {
|
||||
//
|
||||
// The trust is confined to genesis of the key era: someone (the dealer)
|
||||
// momentarily knows the master secret s while constructing the shares.
|
||||
// If s is retained, copied, or exfiltrated, the long-lived Pulsar group
|
||||
// If s is retained, copied, or exfiltrated, the long-lived Corona group
|
||||
// key is compromised. Foundation MUST coordinate Bootstrap as a
|
||||
// publicly observable MPC ceremony at chain launch — the entropy MUST
|
||||
// come from a verifiable commit-and-reveal among the genesis
|
||||
@@ -152,10 +152,10 @@ type EpochShareState struct {
|
||||
// ceremony source is provided. Tests pass a deterministic source for
|
||||
// KAT replay.
|
||||
//
|
||||
// Bootstrap pins the production HashSuite (Pulsar-SHA3). Use
|
||||
// BootstrapWithSuite to open an era under the legacy Pulsar-BLAKE3
|
||||
// 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 PulsarGroupID, eraID PulsarKeyEraID, entropy io.Reader) (*KeyEra, error) {
|
||||
func Bootstrap(t int, validators []string, groupID CoronaGroupID, eraID CoronaKeyEraID, entropy io.Reader) (*KeyEra, error) {
|
||||
return BootstrapWithSuite(hash.Default(), t, validators, groupID, eraID, entropy)
|
||||
}
|
||||
|
||||
@@ -163,8 +163,8 @@ func Bootstrap(t int, validators []string, groupID PulsarGroupID, eraID PulsarKe
|
||||
// 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 (Pulsar-SHA3).
|
||||
func BootstrapWithSuite(suite hash.HashSuite, t int, validators []string, groupID PulsarGroupID, eraID PulsarKeyEraID, entropy io.Reader) (*KeyEra, error) {
|
||||
// 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) {
|
||||
if len(validators) == 0 {
|
||||
return nil, ErrEmptyValidators
|
||||
}
|
||||
@@ -244,7 +244,7 @@ func BootstrapWithSuite(suite hash.HashSuite, t int, validators []string, groupI
|
||||
// The bare Shamir kernel runs in-process; for distributed deployments
|
||||
// the consensus layer wraps this in the full Verifiable Secret Resharing
|
||||
// (VSR) exchange (commits, complaints, activation cert) defined in
|
||||
// pulsar/reshare. This kernel exists to (a) drive the cryptographic core,
|
||||
// corona/reshare. This kernel exists to (a) drive the cryptographic core,
|
||||
// (b) be reused as the trusted-collaborator path for single-process
|
||||
// integration tests, and (c) provide a reference against which the
|
||||
// distributed protocol can be byte-equality checked.
|
||||
@@ -342,12 +342,12 @@ func (era *KeyEra) Reshare(newValidators []string, newThreshold int, randSource
|
||||
// is not a routine operation.
|
||||
//
|
||||
// Reanchor inherits the prior era's HashSuiteID. To migrate to a
|
||||
// different suite (e.g. moving from legacy Pulsar-BLAKE3 to production
|
||||
// Pulsar-SHA3) call ReanchorWithSuite.
|
||||
func Reanchor(prev *KeyEra, t int, validators []string, groupID PulsarGroupID, entropy io.Reader) (*KeyEra, error) {
|
||||
// different suite (e.g. moving from legacy Corona-BLAKE3 to production
|
||||
// Corona-SHA3) call ReanchorWithSuite.
|
||||
func Reanchor(prev *KeyEra, t int, validators []string, groupID CoronaGroupID, entropy io.Reader) (*KeyEra, error) {
|
||||
var suite hash.HashSuite
|
||||
if prev != nil && prev.HashSuiteID == hash.LegacyBLAKE3ID {
|
||||
suite = hash.NewPulsarBLAKE3()
|
||||
suite = hash.NewCoronaBLAKE3()
|
||||
} else {
|
||||
suite = hash.Default()
|
||||
}
|
||||
@@ -359,8 +359,8 @@ func Reanchor(prev *KeyEra, t int, validators []string, groupID PulsarGroupID, e
|
||||
// that may pin a hash profile different from the prior era's
|
||||
// (Reshare cannot — that is enforced by Reshare not accepting a suite
|
||||
// parameter). nil suite resolves to the production default.
|
||||
func ReanchorWithSuite(prev *KeyEra, suite hash.HashSuite, t int, validators []string, groupID PulsarGroupID, entropy io.Reader) (*KeyEra, error) {
|
||||
var nextEraID PulsarKeyEraID
|
||||
func ReanchorWithSuite(prev *KeyEra, suite hash.HashSuite, t int, validators []string, groupID CoronaGroupID, entropy io.Reader) (*KeyEra, error) {
|
||||
var nextEraID CoronaKeyEraID
|
||||
var nextEpoch uint64
|
||||
if prev != nil {
|
||||
nextEraID = prev.EraID + 1
|
||||
@@ -458,7 +458,7 @@ func computeFullCommitteeLagrange(r *ring.Ring, n int) []ring.Poly {
|
||||
//
|
||||
// In a single-process simulation the material is freshly drawn from
|
||||
// randSource. In a distributed deployment the consensus layer overrides
|
||||
// this with authenticated pairwise KEX from pulsar/reshare/pairwise.go,
|
||||
// this with authenticated pairwise KEX from corona/reshare/pairwise.go,
|
||||
// ensuring both endpoints derive the same value without a shared
|
||||
// trusted dealer.
|
||||
func derivePairwiseMaterial(K int, randSource io.Reader) (map[int][][]byte, []map[int][]byte) {
|
||||
|
||||
@@ -15,7 +15,7 @@ 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; pulsar's signing protocol assumes the full committee
|
||||
// set) here; corona's signing protocol assumes the full committee
|
||||
// participates in each Sign invocation.
|
||||
func TestBootstrapBuildsAndSigns(t *testing.T) {
|
||||
const tThr, n = 3, 3
|
||||
|
||||
+8
-8
@@ -39,8 +39,8 @@ func must(op string, err error) {
|
||||
// 2026-05-03 in coordination with the C++ port at luxcpp/crypto).
|
||||
//
|
||||
// `suite` selects the hash profile. nil resolves to the production
|
||||
// default (Pulsar-SHA3). Output bytes differ between Pulsar-SHA3 and
|
||||
// Pulsar-BLAKE3 — this is the F22 cross-profile separation.
|
||||
// default (Corona-SHA3). Output bytes differ between Corona-SHA3 and
|
||||
// Corona-BLAKE3 — this is the F22 cross-profile separation.
|
||||
func PRNGKey(suite hash.HashSuite, skShare structs.Vector[ring.Poly]) []byte {
|
||||
s := hash.Resolve(suite)
|
||||
buf := new(bytes.Buffer)
|
||||
@@ -59,7 +59,7 @@ func PRNGKey(suite hash.HashSuite, skShare structs.Vector[ring.Poly]) []byte {
|
||||
// Domain tag distinguishes from any other future per-share keying.
|
||||
//
|
||||
// `suite` selects the hash profile. nil resolves to the production
|
||||
// default (Pulsar-SHA3).
|
||||
// default (Corona-SHA3).
|
||||
func PRNGKeyForRound(suite hash.HashSuite, skShare structs.Vector[ring.Poly], sid int64) []byte {
|
||||
s := hash.Resolve(suite)
|
||||
skBuf := new(bytes.Buffer)
|
||||
@@ -76,7 +76,7 @@ func PRNGKeyForRound(suite hash.HashSuite, skShare structs.Vector[ring.Poly], si
|
||||
// GenerateMAC generates a MAC for a given TildeD matrix and mask.
|
||||
//
|
||||
// `suite` selects the hash profile. nil resolves to the production
|
||||
// default (Pulsar-SHA3).
|
||||
// default (Corona-SHA3).
|
||||
func GenerateMAC(suite hash.HashSuite, TildeD structs.Matrix[ring.Poly], MACKey []byte, partyID int, sid int, T []int, otherParty int, verify bool) []byte {
|
||||
s := hash.Resolve(suite)
|
||||
buf := new(bytes.Buffer)
|
||||
@@ -101,7 +101,7 @@ func GenerateMAC(suite hash.HashSuite, TildeD structs.Matrix[ring.Poly], MACKey
|
||||
// GaussianHash hashes parameters to a Gaussian distribution.
|
||||
//
|
||||
// `suite` selects the hash profile. nil resolves to the production
|
||||
// default (Pulsar-SHA3).
|
||||
// default (Corona-SHA3).
|
||||
func GaussianHash(suite hash.HashSuite, r *ring.Ring, hashIn []byte, mu string, sigmaU float64, boundU float64, length int) structs.Vector[ring.Poly] {
|
||||
s := hash.Resolve(suite)
|
||||
transcript := new(bytes.Buffer)
|
||||
@@ -120,7 +120,7 @@ func GaussianHash(suite hash.HashSuite, r *ring.Ring, hashIn []byte, mu string,
|
||||
// PRF generates pseudorandom ring elements.
|
||||
//
|
||||
// `suite` selects the hash profile. nil resolves to the production
|
||||
// default (Pulsar-SHA3).
|
||||
// default (Corona-SHA3).
|
||||
func PRF(suite hash.HashSuite, r *ring.Ring, sd_ij []byte, PRFKey []byte, mu string, hashIn []byte, n int) structs.Vector[ring.Poly] {
|
||||
s := hash.Resolve(suite)
|
||||
msg := new(bytes.Buffer)
|
||||
@@ -139,7 +139,7 @@ func PRF(suite hash.HashSuite, r *ring.Ring, sd_ij []byte, PRFKey []byte, mu str
|
||||
// Hash hashes precomputable values.
|
||||
//
|
||||
// `suite` selects the hash profile. nil resolves to the production
|
||||
// default (Pulsar-SHA3).
|
||||
// default (Corona-SHA3).
|
||||
func Hash(suite hash.HashSuite, A structs.Matrix[ring.Poly], b structs.Vector[ring.Poly], D map[int]structs.Matrix[ring.Poly], sid int, T []int) []byte {
|
||||
s := hash.Resolve(suite)
|
||||
buf := new(bytes.Buffer)
|
||||
@@ -168,7 +168,7 @@ func Hash(suite hash.HashSuite, A structs.Matrix[ring.Poly], b structs.Vector[ri
|
||||
// LowNormHash hashes to low norm ring elements.
|
||||
//
|
||||
// `suite` selects the hash profile. nil resolves to the production
|
||||
// default (Pulsar-SHA3).
|
||||
// default (Corona-SHA3).
|
||||
func LowNormHash(suite hash.HashSuite, r *ring.Ring, A structs.Matrix[ring.Poly], b structs.Vector[ring.Poly], h structs.Vector[ring.Poly], mu string, kappa int) ring.Poly {
|
||||
s := hash.Resolve(suite)
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
@@ -260,17 +260,17 @@ func TestGenerateRandomSeed(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestPulsarSHA3VsBLAKE3_DistinctOutput is the F22 fix surfaced as a test:
|
||||
// TestCoronaSHA3VsBLAKE3_DistinctOutput is the F22 fix surfaced as a test:
|
||||
// the same byte-identical inputs must produce different bytes under
|
||||
// Pulsar-SHA3 and Pulsar-BLAKE3 across every Sign-path primitive. If two
|
||||
// Corona-SHA3 and Corona-BLAKE3 across every Sign-path primitive. If two
|
||||
// suites collide here, customization tags or framing are broken.
|
||||
func TestPulsarSHA3VsBLAKE3_DistinctOutput(t *testing.T) {
|
||||
func TestCoronaSHA3VsBLAKE3_DistinctOutput(t *testing.T) {
|
||||
r, err := ring.NewRing(256, []uint64{8380417})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sha3 := hash.NewPulsarSHA3()
|
||||
bl3 := hash.NewPulsarBLAKE3()
|
||||
sha3 := hash.NewCoronaSHA3()
|
||||
bl3 := hash.NewCoronaBLAKE3()
|
||||
|
||||
prng, _ := sampling.NewPRNG()
|
||||
sampler := ring.NewUniformSampler(prng, r)
|
||||
@@ -402,15 +402,15 @@ func TestPulsarSHA3VsBLAKE3_DistinctOutput(t *testing.T) {
|
||||
//
|
||||
// The legacy BLAKE3 KATs in cmd/corona_oracle_v2/ historically reflected
|
||||
// raw blake3.New() framing in primitives/hash.go. After the suite
|
||||
// refactor, primitives now uses pulsarBLAKE3.PRF / pulsarBLAKE3.Hu /
|
||||
// pulsarBLAKE3.MAC which prepend customization tags and length-prefix —
|
||||
// refactor, primitives now uses coronaBLAKE3.PRF / coronaBLAKE3.Hu /
|
||||
// coronaBLAKE3.MAC which prepend customization tags and length-prefix —
|
||||
// so the BLAKE3 oracle output_hex no longer byte-matches pre-refactor
|
||||
// transcripts. New Pulsar-SHA3 KATs are not yet emitted.
|
||||
// transcripts. New Corona-SHA3 KATs are not yet emitted.
|
||||
//
|
||||
// This is documented in pulsar/CHANGELOG.md as follow-up work. The test
|
||||
// here is a guard rail: it fails if anyone hand-edits the legacy BLAKE3
|
||||
// JSON in tree before regeneration, so the C++ port team gets a loud
|
||||
// signal.
|
||||
func TestKATsRegenerated(t *testing.T) {
|
||||
t.Skip("legacy BLAKE3 KATs and new Pulsar-SHA3 KATs land as follow-up; see pulsar/CHANGELOG.md")
|
||||
t.Skip("legacy BLAKE3 KATs and new Corona-SHA3 KATs land as follow-up; see pulsar/CHANGELOG.md")
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ package reshare
|
||||
// Activation message canonical bytes (signed by the new committee
|
||||
// under the UNCHANGED GroupKey):
|
||||
//
|
||||
// "QUASAR-PULSAR-ACTIVATE-v1" ||
|
||||
// "QUASAR-CORONA-ACTIVATE-v1" ||
|
||||
// transcript_hash (32 bytes; from TranscriptInputs.Hash)
|
||||
// reshare_transcript_hash (32 bytes; from ReshareTranscript.Hash)
|
||||
|
||||
@@ -61,7 +61,7 @@ func buildExchangeTranscriptParts(rt *ReshareTranscript) [][]byte {
|
||||
return b[:]
|
||||
}
|
||||
parts := [][]byte{
|
||||
[]byte("pulsar.reshare.exchange-transcript.v1"),
|
||||
[]byte("corona.reshare.exchange-transcript.v1"),
|
||||
}
|
||||
|
||||
commitParties := make([]int, 0, len(rt.CommitDigests))
|
||||
@@ -101,7 +101,7 @@ func buildExchangeTranscriptParts(rt *ReshareTranscript) [][]byte {
|
||||
// threshold-signs to produce an activation cert.
|
||||
func (a *ActivationMessage) SignableBytes(suite hash.HashSuite) []byte {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("QUASAR-PULSAR-ACTIVATE-v1")
|
||||
buf.WriteString("QUASAR-CORONA-ACTIVATE-v1")
|
||||
t := a.Transcript.Hash(suite)
|
||||
buf.Write(t[:])
|
||||
rth := a.ReshareTranscript.Hash(suite)
|
||||
@@ -123,7 +123,7 @@ var (
|
||||
)
|
||||
|
||||
// VerifyActivation runs the chain-level activation check.
|
||||
// suite=nil resolves to the production default (Pulsar-SHA3).
|
||||
// suite=nil resolves to the production default (Corona-SHA3).
|
||||
func VerifyActivation(
|
||||
cert *ActivationCert,
|
||||
localTranscriptHash [32]byte,
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestActivationMessageSignableBytesStable(t *testing.T) {
|
||||
t.Fatal("SignableBytes non-deterministic")
|
||||
}
|
||||
// Format: 25-byte personalization prefix + 32 + 32 = 89 bytes.
|
||||
const wantLen = len("QUASAR-PULSAR-ACTIVATE-v1") + 32 + 32
|
||||
const wantLen = len("QUASAR-CORONA-ACTIVATE-v1") + 32 + 32
|
||||
if len(b1) != wantLen {
|
||||
t.Fatalf("unexpected SignableBytes length: %d (want %d)", len(b1), wantLen)
|
||||
}
|
||||
|
||||
+7
-7
@@ -16,7 +16,7 @@ package reshare
|
||||
// C_{i,k} = A_R · NTT(c_{i,k}) + B_R · NTT(r_{i,k})
|
||||
//
|
||||
// The matrices A, B are derived from nothing-up-my-sleeve domain-separated
|
||||
// tags via the canonical Pulsar HashSuite XOF (cSHAKE256 under Pulsar-SHA3,
|
||||
// tags via the canonical Corona HashSuite XOF (cSHAKE256 under Corona-SHA3,
|
||||
// BLAKE3 under the legacy suite).
|
||||
|
||||
import (
|
||||
@@ -40,8 +40,8 @@ import (
|
||||
// from dkg2's tags so a DKG commit cannot be repurposed as a reshare
|
||||
// commit (and vice versa).
|
||||
var (
|
||||
tagReshareA = []byte("pulsar.reshare.A.v1")
|
||||
tagReshareB = []byte("pulsar.reshare.B.v1")
|
||||
tagReshareA = []byte("corona.reshare.A.v1")
|
||||
tagReshareB = []byte("corona.reshare.B.v1")
|
||||
)
|
||||
|
||||
// CommitParams holds the public matrices used to commit to and verify
|
||||
@@ -55,9 +55,9 @@ type CommitParams struct {
|
||||
|
||||
// NewCommitParams derives the commitment matrices from the canonical
|
||||
// tags using the supplied HashSuite. suite=nil resolves to the
|
||||
// production default (Pulsar-SHA3). Two suites with distinct IDs derive
|
||||
// production default (Corona-SHA3). Two suites with distinct IDs derive
|
||||
// distinct matrices, so legacy BLAKE3 KATs cannot be replayed as
|
||||
// Pulsar-SHA3 transcripts.
|
||||
// Corona-SHA3 transcripts.
|
||||
func NewCommitParams(suite hash.HashSuite) (*CommitParams, error) {
|
||||
s := hash.Resolve(suite)
|
||||
r, err := ring.NewRing(1<<sign.LogN, []uint64{sign.Q})
|
||||
@@ -235,11 +235,11 @@ func polyMulScalarNTTOnly(r *ring.Ring, p ring.Poly, s, q *big.Int) {
|
||||
|
||||
// CommitDigest returns the canonical 32-byte digest over a commit
|
||||
// vector under the supplied HashSuite. suite=nil resolves to the
|
||||
// production default (Pulsar-SHA3).
|
||||
// production default (Corona-SHA3).
|
||||
func CommitDigest(commits []structs.Vector[ring.Poly], suite hash.HashSuite) [32]byte {
|
||||
s := hash.Resolve(suite)
|
||||
parts := make([][]byte, 0, 1+len(commits))
|
||||
parts = append(parts, []byte("pulsar.reshare.commit-digest.v1"))
|
||||
parts = append(parts, []byte("corona.reshare.commit-digest.v1"))
|
||||
for _, v := range commits {
|
||||
var buf bytes.Buffer
|
||||
_, _ = v.WriteTo(&buf)
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestCommitToPolyAndVerify(t *testing.T) {
|
||||
const tThr = 3
|
||||
|
||||
// Sample (c_k, r_k) for k = 0..t-1 from a Gaussian PRNG. We use
|
||||
// the same Gaussian as Pulsar secrets.
|
||||
// the same Gaussian as Corona secrets.
|
||||
prng, _ := sampling.NewKeyedPRNG([]byte("commit-test-prng"))
|
||||
gauss := ring.NewGaussianSampler(prng, params.R,
|
||||
ring.DiscreteGaussian{Sigma: sign.SigmaE, Bound: sign.BoundE}, false)
|
||||
|
||||
@@ -136,11 +136,11 @@ type Complaint struct {
|
||||
//
|
||||
// Format:
|
||||
//
|
||||
// "pulsar.reshare.complaint.v1" || transcript || sender_id_be32 ||
|
||||
// "corona.reshare.complaint.v1" || transcript || sender_id_be32 ||
|
||||
// complainer_id_be32 || reason_u8 || evidence_len_be32 || evidence
|
||||
func (c *Complaint) Bytes() []byte {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("pulsar.reshare.complaint.v1")
|
||||
buf.WriteString("corona.reshare.complaint.v1")
|
||||
buf.Write(c.TranscriptHash[:])
|
||||
var b4 [4]byte
|
||||
binary.BigEndian.PutUint32(b4[:], uint32(c.SenderID))
|
||||
@@ -184,7 +184,7 @@ func (c *Complaint) Verify() error {
|
||||
// final disqualification result.
|
||||
func ComplaintHash(c *Complaint) [32]byte {
|
||||
h := blake3.New()
|
||||
_, _ = h.Write([]byte("pulsar.reshare.complaint-hash.v1"))
|
||||
_, _ = h.Write([]byte("corona.reshare.complaint-hash.v1"))
|
||||
_, _ = h.Write(c.Bytes())
|
||||
_, _ = h.Write(c.Signature)
|
||||
var out [32]byte
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// The activation cert is the post-reshare circuit-breaker; it must be
|
||||
// produced over a deterministic byte stream that uniquely binds the
|
||||
// resharing transcript hash and the protocol-domain prefix
|
||||
// "QUASAR-PULSAR-ACTIVATE-v1". This harness exercises the
|
||||
// "QUASAR-CORONA-ACTIVATE-v1". This harness exercises the
|
||||
// SignableBytes encoder against arbitrary mutated inputs.
|
||||
|
||||
package reshare
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
// 1. SignableBytes never panics on arbitrary inputs.
|
||||
// 2. Output is deterministic: same struct → same bytes across calls.
|
||||
// 3. The output begins with the canonical protocol prefix
|
||||
// "QUASAR-PULSAR-ACTIVATE-v1".
|
||||
// "QUASAR-CORONA-ACTIVATE-v1".
|
||||
// 4. The output length is exactly len(prefix) + 32 + 32 = 89 bytes,
|
||||
// irrespective of how large the input fields are.
|
||||
func FuzzActivationMessageSignableBytes(f *testing.F) {
|
||||
@@ -35,8 +35,8 @@ func FuzzActivationMessageSignableBytes(f *testing.F) {
|
||||
f.Add(seedActivationBytes("lux-testnet", "g0", 1, 2, "refresh", 5, 0, 1))
|
||||
f.Add(seedActivationBytes("", "", 0, 0, "", 0, 0, 0))
|
||||
|
||||
prefix := []byte("QUASAR-PULSAR-ACTIVATE-v1")
|
||||
const wantLen = len("QUASAR-PULSAR-ACTIVATE-v1") + 32 + 32
|
||||
prefix := []byte("QUASAR-CORONA-ACTIVATE-v1")
|
||||
const wantLen = len("QUASAR-CORONA-ACTIVATE-v1") + 32 + 32
|
||||
|
||||
f.Fuzz(func(t *testing.T, raw []byte) {
|
||||
msg := decodeFuzzActivation(raw)
|
||||
@@ -68,7 +68,7 @@ func FuzzActivationMessageSignableBytes(f *testing.F) {
|
||||
// TestFuzzCorpus_ActivationReplay re-runs the seed corpus
|
||||
// deterministically for CI replay.
|
||||
func TestFuzzCorpus_ActivationReplay(t *testing.T) {
|
||||
prefix := []byte("QUASAR-PULSAR-ACTIVATE-v1")
|
||||
prefix := []byte("QUASAR-CORONA-ACTIVATE-v1")
|
||||
seeds := [][]byte{
|
||||
seedActivationBytes("lux-mainnet", "quasar-pq", 100, 101, "reshare", 3, 2, 0),
|
||||
seedActivationBytes("lux-testnet", "g0", 1, 2, "refresh", 5, 0, 1),
|
||||
|
||||
@@ -56,7 +56,7 @@ func fuzzCommitDigestRecover(raw []byte) (err error) {
|
||||
// strict: rejects truncated inputs, oversized evidence claims, and
|
||||
// version-tag mismatches with a clean error rather than a panic.
|
||||
func parseComplaintBytes(raw []byte) (*Complaint, error) {
|
||||
const versionTag = "pulsar.reshare.complaint.v1"
|
||||
const versionTag = "corona.reshare.complaint.v1"
|
||||
if len(raw) < len(versionTag)+32+4+4+1+4 {
|
||||
return nil, fmt.Errorf("truncated: %d bytes", len(raw))
|
||||
}
|
||||
@@ -91,8 +91,8 @@ func addSmallSeeds(f *testing.F) {
|
||||
f.Add([]byte{})
|
||||
f.Add([]byte{0x00})
|
||||
f.Add(bytes.Repeat([]byte{0xff}, 32))
|
||||
f.Add(append([]byte("pulsar.reshare.complaint.v1"), bytes.Repeat([]byte{0x00}, 41)...))
|
||||
f.Add(append([]byte("pulsar.reshare.complaint.v1"),
|
||||
f.Add(append([]byte("corona.reshare.complaint.v1"), bytes.Repeat([]byte{0x00}, 41)...))
|
||||
f.Add(append([]byte("corona.reshare.complaint.v1"),
|
||||
append(bytes.Repeat([]byte{0x00}, 41),
|
||||
[]byte{0xff, 0xff, 0xff, 0xff}...)...)) // huge evidence claim
|
||||
}
|
||||
@@ -137,7 +137,7 @@ func TestFuzzCorpus_ReshareComplaintReplay(t *testing.T) {
|
||||
for _, raw := range [][]byte{
|
||||
{},
|
||||
{0x00},
|
||||
append([]byte("pulsar.reshare.complaint.v1"), bytes.Repeat([]byte{0x00}, 41)...),
|
||||
append([]byte("corona.reshare.complaint.v1"), bytes.Repeat([]byte{0x00}, 41)...),
|
||||
} {
|
||||
_, _ = parseComplaintBytes(raw)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Package reshare fuzz harness for transcript binding.
|
||||
//
|
||||
// Property anchor: proofs/definitions/transcript-binding.tex
|
||||
// Definition ref:pulsar-transcript ("Pulsar TranscriptInputs.Hash") —
|
||||
// Definition ref:pulsar-transcript ("Corona TranscriptInputs.Hash") —
|
||||
// the canonical TranscriptHash is collision-resistant, and any two
|
||||
// distinct field tuples yield distinct hashes by collision resistance
|
||||
// of TupleHash256.
|
||||
@@ -41,8 +41,8 @@ func FuzzTranscriptInputsHash(f *testing.F) {
|
||||
// Seed corpus: a few representative TranscriptInputs values pulled
|
||||
// from the activation_oracle KAT shape. Each is encoded as a flat
|
||||
// byte stream the harness decodes into a TranscriptInputs.
|
||||
f.Add(seedTranscriptBytes("lux-mainnet", "quasar-pq", 1, 100, 101, 11, 11, "Pulsar-SHA3", "v1", "reshare"))
|
||||
f.Add(seedTranscriptBytes("lux-testnet", "g0", 0, 1, 2, 3, 3, "Pulsar-BLAKE3", "v1", "refresh"))
|
||||
f.Add(seedTranscriptBytes("lux-mainnet", "quasar-pq", 1, 100, 101, 11, 11, "Corona-SHA3", "v1", "reshare"))
|
||||
f.Add(seedTranscriptBytes("lux-testnet", "g0", 0, 1, 2, 3, 3, "Corona-BLAKE3", "v1", "refresh"))
|
||||
f.Add(seedTranscriptBytes("", "", 0, 0, 0, 0, 0, "", "", ""))
|
||||
|
||||
f.Fuzz(func(t *testing.T, raw []byte) {
|
||||
@@ -89,8 +89,8 @@ func FuzzTranscriptInputsHash(f *testing.F) {
|
||||
// hash to stable values without invoking the native fuzzer.
|
||||
func TestFuzzCorpus_TranscriptReplay(t *testing.T) {
|
||||
seeds := [][]byte{
|
||||
seedTranscriptBytes("lux-mainnet", "quasar-pq", 1, 100, 101, 11, 11, "Pulsar-SHA3", "v1", "reshare"),
|
||||
seedTranscriptBytes("lux-testnet", "g0", 0, 1, 2, 3, 3, "Pulsar-BLAKE3", "v1", "refresh"),
|
||||
seedTranscriptBytes("lux-mainnet", "quasar-pq", 1, 100, 101, 11, 11, "Corona-SHA3", "v1", "reshare"),
|
||||
seedTranscriptBytes("lux-testnet", "g0", 0, 1, 2, 3, 3, "Corona-BLAKE3", "v1", "refresh"),
|
||||
seedTranscriptBytes("", "", 0, 0, 0, 0, 0, "", "", ""),
|
||||
}
|
||||
for i, s := range seeds {
|
||||
|
||||
+8
-8
@@ -3,7 +3,7 @@
|
||||
|
||||
package reshare
|
||||
|
||||
// KeyShare regeneration for Corona / Pulsar integration.
|
||||
// KeyShare regeneration for Corona / Corona integration.
|
||||
//
|
||||
// The Reshare and Refresh kernels operate on bare Shamir shares — the
|
||||
// SkShare field of the production-grade
|
||||
@@ -19,13 +19,13 @@ package reshare
|
||||
// GroupKey *GroupKey
|
||||
// }
|
||||
//
|
||||
// All KDF derivations use the canonical Pulsar HashSuite (KMAC256
|
||||
// under Pulsar-SHA3, keyed BLAKE3 under the legacy suite). Domain-
|
||||
// All KDF derivations use the canonical Corona HashSuite (KMAC256
|
||||
// under Corona-SHA3, keyed BLAKE3 under the legacy suite). Domain-
|
||||
// separation tags:
|
||||
//
|
||||
// "pulsar.reshare.prf-seed.v1" — for Seeds
|
||||
// "pulsar.reshare.mac-key.v1" — for MACKeys
|
||||
// "pulsar.reshare.lambda-bind.v1" — bound into Lambda derivation when
|
||||
// "corona.reshare.prf-seed.v1" — for Seeds
|
||||
// "corona.reshare.mac-key.v1" — for MACKeys
|
||||
// "corona.reshare.lambda-bind.v1" — bound into Lambda derivation when
|
||||
// the committee computes Lambdas
|
||||
// from a shared transcript hash.
|
||||
|
||||
@@ -41,7 +41,7 @@ import (
|
||||
"github.com/luxfi/lattice/v7/utils/structs"
|
||||
)
|
||||
|
||||
// PartyKeyShare is the Pulsar-internal mirror of
|
||||
// PartyKeyShare is the Corona-internal mirror of
|
||||
// corona/threshold.KeyShare.
|
||||
type PartyKeyShare struct {
|
||||
Index int
|
||||
@@ -120,7 +120,7 @@ func PartyKeyShareFromShare(
|
||||
|
||||
// KDFOutput derives a fixed-length output from a keying material under
|
||||
// the supplied HashSuite's pairwise KDF. suite=nil resolves to the
|
||||
// production default (Pulsar-SHA3).
|
||||
// production default (Corona-SHA3).
|
||||
//
|
||||
// The tag is folded into the chainID label with a `|` separator so two
|
||||
// callers with distinct tags but the same remaining inputs always
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package reshare — Gate 4 negative-transcript tests for the Pulsar
|
||||
// Package reshare — Gate 4 negative-transcript tests for the Corona
|
||||
// VSR transcript and activation message (Mar-3-2026 PQ Consensus
|
||||
// Architecture Freeze).
|
||||
//
|
||||
@@ -63,7 +63,7 @@ func baselineTranscriptInputs() TranscriptInputs {
|
||||
ThresholdNew: 13,
|
||||
GroupPublicKeyHash: [32]byte{0xa0, 0xa1, 0xa2, 0xa3, 0xa4},
|
||||
NebulaRoot: [32]byte{0xb0, 0xb1, 0xb2, 0xb3, 0xb4},
|
||||
HashSuiteID: "Pulsar-SHA3",
|
||||
HashSuiteID: "Corona-SHA3",
|
||||
ImplementationVersion: "pulsar-go-1.0.0",
|
||||
Variant: "reshare",
|
||||
}
|
||||
@@ -88,7 +88,7 @@ func baselineActivationMessage() ActivationMessage {
|
||||
}
|
||||
|
||||
// honestThresholdVerifier returns a verifier closure that mimics the
|
||||
// behaviour of a real Pulsar.Verify under an unchanged GroupKey: it
|
||||
// behaviour of a real Corona.Verify under an unchanged GroupKey: it
|
||||
// accepts iff the bytes-to-be-signed equal the baseline activation
|
||||
// message's bytes-to-be-signed, and rejects everything else.
|
||||
//
|
||||
@@ -139,7 +139,7 @@ func mutateTranscriptField(t *testing.T, base ActivationMessage, field string) A
|
||||
case "nebula_root":
|
||||
m.Transcript.NebulaRoot = [32]byte{0xee, 0xee, 0xee, 0xee}
|
||||
case "hash_suite_id":
|
||||
m.Transcript.HashSuiteID = "Pulsar-BLAKE3"
|
||||
m.Transcript.HashSuiteID = "Corona-BLAKE3"
|
||||
case "implementation_version":
|
||||
m.Transcript.ImplementationVersion = "pulsar-rs-2.0.0"
|
||||
case "variant":
|
||||
|
||||
+9
-9
@@ -20,7 +20,7 @@ package reshare
|
||||
//
|
||||
// We use X25519 + Ed25519 here as the kernel KEX. The auth_kex_ij is
|
||||
// derived via a transcript-bound mix of the X25519 output produced by
|
||||
// the canonical Pulsar HashSuite (cSHAKE256 under Pulsar-SHA3, BLAKE3
|
||||
// the canonical Corona HashSuite (cSHAKE256 under Corona-SHA3, BLAKE3
|
||||
// under the legacy suite). For the hybrid post-quantum mode, swap
|
||||
// X25519 for ML-KEM-768 + X25519 — out of scope for this kernel.
|
||||
|
||||
@@ -75,7 +75,7 @@ func X25519Pair(privA, pubB []byte) ([]byte, error) {
|
||||
// returns the auth_kex_ij value. The signed ephemeral protects against
|
||||
// active man-in-the-middle.
|
||||
//
|
||||
// suite=nil resolves to the production default (Pulsar-SHA3).
|
||||
// suite=nil resolves to the production default (Corona-SHA3).
|
||||
func AuthenticatedKex(
|
||||
privIEph []byte,
|
||||
pubJEph []byte,
|
||||
@@ -84,7 +84,7 @@ func AuthenticatedKex(
|
||||
transcriptHash [32]byte,
|
||||
suite hash.HashSuite,
|
||||
) ([]byte, error) {
|
||||
signedMsg := append([]byte("pulsar.reshare.kex-bind.v1"), transcriptHash[:]...)
|
||||
signedMsg := append([]byte("corona.reshare.kex-bind.v1"), transcriptHash[:]...)
|
||||
signedMsg = append(signedMsg, pubJEph...)
|
||||
if !ed25519.Verify(jStaticKey, signedMsg, sigJEph) {
|
||||
return nil, errors.New("reshare: peer ephemeral signature invalid")
|
||||
@@ -97,7 +97,7 @@ func AuthenticatedKex(
|
||||
|
||||
s := hash.Resolve(suite)
|
||||
out := s.TranscriptHash(
|
||||
[]byte("pulsar.reshare.auth-kex.v1"),
|
||||
[]byte("corona.reshare.auth-kex.v1"),
|
||||
transcriptHash[:],
|
||||
shared,
|
||||
)
|
||||
@@ -111,13 +111,13 @@ func SignEphemeral(
|
||||
pubEph []byte,
|
||||
transcriptHash [32]byte,
|
||||
) []byte {
|
||||
signedMsg := append([]byte("pulsar.reshare.kex-bind.v1"), transcriptHash[:]...)
|
||||
signedMsg := append([]byte("corona.reshare.kex-bind.v1"), transcriptHash[:]...)
|
||||
signedMsg = append(signedMsg, pubEph...)
|
||||
return ed25519.Sign(priv, signedMsg)
|
||||
}
|
||||
|
||||
// DeriveSeeds populates the per-pair PRF seed map for a committee of
|
||||
// size K. suite=nil resolves to the production default (Pulsar-SHA3).
|
||||
// size K. suite=nil resolves to the production default (Corona-SHA3).
|
||||
//
|
||||
// eraID and generation are passed through to the suite's DerivePairwise.
|
||||
// For pre-Bucket-B callsites that only have a single epochID, fold it
|
||||
@@ -149,7 +149,7 @@ func DeriveSeeds(
|
||||
}
|
||||
out[pair] = KDFOutput(
|
||||
suite,
|
||||
"pulsar.reshare.prf-seed.v1",
|
||||
"corona.reshare.prf-seed.v1",
|
||||
keyMat,
|
||||
chainID, groupID,
|
||||
eraID, generation,
|
||||
@@ -161,7 +161,7 @@ func DeriveSeeds(
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DeriveMACKeys mirrors DeriveSeeds with the "pulsar.reshare.mac-key.v1"
|
||||
// DeriveMACKeys mirrors DeriveSeeds with the "corona.reshare.mac-key.v1"
|
||||
// tag and only off-diagonal entries (a party never MACs to itself).
|
||||
// suite=nil resolves to the production default.
|
||||
func DeriveMACKeys(
|
||||
@@ -182,7 +182,7 @@ func DeriveMACKeys(
|
||||
}
|
||||
out[pair] = KDFOutput(
|
||||
suite,
|
||||
"pulsar.reshare.mac-key.v1",
|
||||
"corona.reshare.mac-key.v1",
|
||||
keyMat,
|
||||
chainID, groupID,
|
||||
eraID, generation,
|
||||
|
||||
+5
-5
@@ -2,7 +2,7 @@
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package reshare implements two distinct proactive secret-sharing
|
||||
// primitives for Pulsar lattice threshold signatures over the ring
|
||||
// primitives for Corona lattice threshold signatures over the ring
|
||||
// R_q = Z_q[X]/(X^N+1):
|
||||
//
|
||||
// 1. Refresh — same-committee zero-polynomial proactive update
|
||||
@@ -39,7 +39,7 @@
|
||||
// boundaries (`protocol/quasar/epoch.go: ReshareEpoch`).
|
||||
//
|
||||
// Both primitives leave the public key b = A·s + e (and its rounded
|
||||
// form b̃) UNCHANGED. The genesis values (A, b, e) — and Pulsar's
|
||||
// form b̃) UNCHANGED. The genesis values (A, b, e) — and Corona's
|
||||
// `bTilde` and Corona's `GroupKey` — are persistent for the entire
|
||||
// group lineage. Only the share distribution changes. This is the
|
||||
// fundamental property that lets Quasar avoid running a full DKG on
|
||||
@@ -71,7 +71,7 @@
|
||||
// quorum logic, deterministic disqualification of
|
||||
// misbehaving senders.
|
||||
// - keyshare.go — Wraps reshared SkShare values into complete
|
||||
// Corona/Pulsar `KeyShare` instances by
|
||||
// Corona/Corona `KeyShare` instances by
|
||||
// regenerating Lambda, Seeds, MACKeys, and
|
||||
// attaching the unchanged GroupKey pointer.
|
||||
// - pairwise.go — Authenticated pairwise KEX (X25519 / ML-KEM
|
||||
@@ -160,7 +160,7 @@ type Share = structs.Vector[ring.Poly]
|
||||
//
|
||||
// Reshare is deterministic given a deterministic randSource, which lets
|
||||
// the cmd/reshare_oracle/main.go KAT path reproduce results across
|
||||
// implementations (Go and luxcpp/crypto/pulsar/reshare).
|
||||
// implementations (Go and luxcpp/crypto/corona/reshare).
|
||||
func Reshare(
|
||||
r *ring.Ring,
|
||||
oldShares map[int]Share,
|
||||
@@ -428,7 +428,7 @@ func Refresh(
|
||||
// within each c_{i,d} we draw the nVec polynomials in 0..nVec-1
|
||||
// order, and within each polynomial we draw the N coefficients in
|
||||
// 0..N-1 order. This iteration order is locked by the Go reference
|
||||
// and the C++ port at luxcpp/crypto/pulsar/reshare/.
|
||||
// and the C++ port at luxcpp/crypto/corona/reshare/.
|
||||
for _, i := range parties {
|
||||
_ = i
|
||||
// z_i has degree (t-1) and constant term 0. We store
|
||||
|
||||
@@ -515,7 +515,7 @@ func TestReshareWithSignGenShares(t *testing.T) {
|
||||
|
||||
secret := pickSecret(r, "primitives-bridge", testNVec)
|
||||
|
||||
// Use the pulsar primitives' standard Shamir variant.
|
||||
// Use the corona primitives' standard Shamir variant.
|
||||
tOld, nOld := 3, 5
|
||||
primSharesMap := primitives.ShamirSecretSharingGeneral(
|
||||
r, secret, tOld, nOld,
|
||||
|
||||
@@ -17,10 +17,10 @@ package reshare
|
||||
// this hash.
|
||||
//
|
||||
// Domain separation. Every input field is unambiguously length-prefixed
|
||||
// (TupleHash framing under Pulsar-SHA3, hand-rolled length prefixes
|
||||
// under the legacy Pulsar-BLAKE3 suite). The customization tag is
|
||||
// "PULSAR-TRANSCRIPT-v1" — distinct from any DKG transcript tag (e.g.
|
||||
// "pulsar.dkg2.commit.v1") and from any Pulsar Sign transcript tag.
|
||||
// (TupleHash framing under Corona-SHA3, hand-rolled length prefixes
|
||||
// under the legacy Corona-BLAKE3 suite). The customization tag is
|
||||
// "CORONA-TRANSCRIPT-v1" — distinct from any DKG transcript tag (e.g.
|
||||
// "corona.dkg2.commit.v1") and from any Corona Sign transcript tag.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -33,7 +33,7 @@ import (
|
||||
// string. Kept as an exported symbol because some external consumers
|
||||
// (cross-language KAT loaders) reference it; the canonical transcript
|
||||
// binding is now produced via the HashSuite layer.
|
||||
const TranscriptPersonalization = "pulsar.reshare.transcript.v1"
|
||||
const TranscriptPersonalization = "corona.reshare.transcript.v1"
|
||||
|
||||
// TranscriptInputs holds the public binding fields for one resharing
|
||||
// invocation. All fields are mandatory; the transcript hash is well-
|
||||
@@ -60,7 +60,7 @@ type TranscriptInputs struct {
|
||||
|
||||
// Hash returns the canonical 32-byte transcript binding for the inputs
|
||||
// under the supplied HashSuite. nil resolves to the production default
|
||||
// (Pulsar-SHA3); pass hash.NewPulsarBLAKE3() to reproduce legacy bytes.
|
||||
// (Corona-SHA3); pass hash.NewCoronaBLAKE3() to reproduce legacy bytes.
|
||||
func (t *TranscriptInputs) Hash(suite hash.HashSuite) [32]byte {
|
||||
s := hash.Resolve(suite)
|
||||
parts := buildTranscriptParts(t)
|
||||
@@ -102,7 +102,7 @@ func buildTranscriptParts(t *TranscriptInputs) [][]byte {
|
||||
}
|
||||
|
||||
// ValidatorSetHash returns the canonical hash of a validator set.
|
||||
// suite=nil resolves to the production default (Pulsar-SHA3).
|
||||
// suite=nil resolves to the production default (Corona-SHA3).
|
||||
func ValidatorSetHash(publicKeys [][]byte, suite hash.HashSuite) [32]byte {
|
||||
s := hash.Resolve(suite)
|
||||
sorted := make([][]byte, len(publicKeys))
|
||||
@@ -113,7 +113,7 @@ func ValidatorSetHash(publicKeys [][]byte, suite hash.HashSuite) [32]byte {
|
||||
}
|
||||
}
|
||||
parts := make([][]byte, 0, 2+len(sorted))
|
||||
parts = append(parts, []byte("pulsar.reshare.validator-set.v1"))
|
||||
parts = append(parts, []byte("corona.reshare.validator-set.v1"))
|
||||
var nBuf [4]byte
|
||||
binary.BigEndian.PutUint32(nBuf[:], uint32(len(sorted)))
|
||||
parts = append(parts, nBuf[:])
|
||||
|
||||
@@ -101,7 +101,7 @@ func TestValidatorSetHashUniqueness(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestTranscriptHashCrossLanguageCompatibility — emits the
|
||||
// canonical bytes the C++ port at luxcpp/crypto/pulsar/reshare/ MUST
|
||||
// canonical bytes the C++ port at luxcpp/crypto/corona/reshare/ MUST
|
||||
// reproduce. Useful as a fixed-vector smoke test when porting.
|
||||
func TestTranscriptHashFixedVector(t *testing.T) {
|
||||
in := TranscriptInputs{
|
||||
|
||||
+5
-5
@@ -16,8 +16,8 @@ import (
|
||||
// Party struct holds all state and methods for a party in the protocol.
|
||||
//
|
||||
// Suite is the hash profile this party uses for every primitives.* call.
|
||||
// NewParty defaults it to hash.Default() (Pulsar-SHA3). Operators that need
|
||||
// to interoperate with old transcripts can override with NewPulsarBLAKE3().
|
||||
// NewParty defaults it to hash.Default() (Corona-SHA3). Operators that need
|
||||
// to interoperate with old transcripts can override with NewCoronaBLAKE3().
|
||||
type Party struct {
|
||||
ID int
|
||||
Ring *ring.Ring
|
||||
@@ -37,7 +37,7 @@ type Party struct {
|
||||
}
|
||||
|
||||
// NewParty initializes a new Party instance with the production hash suite
|
||||
// (Pulsar-SHA3). To use a different suite, set Party.Suite after construction
|
||||
// (Corona-SHA3). To use a different suite, set Party.Suite after construction
|
||||
// or call NewPartyWithSuite.
|
||||
func NewParty(id int, r *ring.Ring, r_xi *ring.Ring, r_nu *ring.Ring, sampler *ring.UniformSampler) *Party {
|
||||
return NewPartyWithSuite(id, r, r_xi, r_nu, sampler, hash.Default())
|
||||
@@ -286,14 +286,14 @@ func (party *Party) SignFinalize(z map[int]structs.Vector[ring.Poly], A structs.
|
||||
}
|
||||
|
||||
// Verify verifies the correctness of the signature using the production
|
||||
// hash suite (Pulsar-SHA3). For non-default suites use VerifyWithSuite.
|
||||
// hash suite (Corona-SHA3). For non-default suites use VerifyWithSuite.
|
||||
// Note: This function does not modify its inputs - it creates copies where needed.
|
||||
func Verify(r *ring.Ring, r_xi *ring.Ring, r_nu *ring.Ring, z structs.Vector[ring.Poly], A structs.Matrix[ring.Poly], mu string, bTilde structs.Vector[ring.Poly], c ring.Poly, roundedDelta structs.Vector[ring.Poly]) bool {
|
||||
return VerifyWithSuite(nil, r, r_xi, r_nu, z, A, mu, bTilde, c, roundedDelta)
|
||||
}
|
||||
|
||||
// VerifyWithSuite is the suite-explicit form of Verify. suite=nil resolves
|
||||
// to the production default (Pulsar-SHA3).
|
||||
// to the production default (Corona-SHA3).
|
||||
func VerifyWithSuite(suite hash.HashSuite, r *ring.Ring, r_xi *ring.Ring, r_nu *ring.Ring, z structs.Vector[ring.Poly], A structs.Matrix[ring.Poly], mu string, bTilde structs.Vector[ring.Poly], c ring.Poly, roundedDelta structs.Vector[ring.Poly]) bool {
|
||||
// Make a copy of z to avoid modifying the input signature
|
||||
zCopy := make(structs.Vector[ring.Poly], len(z))
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
// TestSignProtocolRoundTripUnderBothSuites runs Gen → SignRound1 →
|
||||
// SignRound2Preprocess → SignRound2 → SignFinalize → Verify under each
|
||||
// available HashSuite (Pulsar-SHA3 and Pulsar-BLAKE3) and asserts the
|
||||
// available HashSuite (Corona-SHA3 and Corona-BLAKE3) and asserts the
|
||||
// resulting signature verifies. The Sign and Verify paths must thread the
|
||||
// same suite end-to-end; mixing suites must fail verification.
|
||||
func TestSignProtocolRoundTripUnderBothSuites(t *testing.T) {
|
||||
@@ -31,8 +31,8 @@ func TestSignProtocolRoundTripUnderBothSuites(t *testing.T) {
|
||||
name string
|
||||
suite hash.HashSuite
|
||||
}{
|
||||
{"Pulsar-SHA3", hash.NewPulsarSHA3()},
|
||||
{"Pulsar-BLAKE3", hash.NewPulsarBLAKE3()},
|
||||
{"Corona-SHA3", hash.NewCoronaSHA3()},
|
||||
{"Corona-BLAKE3", hash.NewCoronaBLAKE3()},
|
||||
}
|
||||
|
||||
for _, sc := range suites {
|
||||
@@ -44,13 +44,13 @@ func TestSignProtocolRoundTripUnderBothSuites(t *testing.T) {
|
||||
|
||||
// Cross-suite mixing: Sign under SHA3, Verify under BLAKE3 → must fail.
|
||||
t.Run("CrossSuiteMixingRejected", func(t *testing.T) {
|
||||
sig, A, mu, b, c, delta, r, rXi, rNu := signOnly(t, hash.NewPulsarSHA3())
|
||||
sig, A, mu, b, c, delta, r, rXi, rNu := signOnly(t, hash.NewCoronaSHA3())
|
||||
// Same suite → ok.
|
||||
if !VerifyWithSuite(hash.NewPulsarSHA3(), r, rXi, rNu, sig, A, mu, b, c, delta) {
|
||||
if !VerifyWithSuite(hash.NewCoronaSHA3(), r, rXi, rNu, sig, A, mu, b, c, delta) {
|
||||
t.Fatal("SHA3 self-verify must succeed")
|
||||
}
|
||||
// Different suite → must reject.
|
||||
if VerifyWithSuite(hash.NewPulsarBLAKE3(), r, rXi, rNu, sig, A, mu, b, c, delta) {
|
||||
if VerifyWithSuite(hash.NewCoronaBLAKE3(), r, rXi, rNu, sig, A, mu, b, c, delta) {
|
||||
t.Fatal("cross-suite verify (SHA3 sign / BLAKE3 verify) must reject — F22 separation violated")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Pulsar threshold-kernel wire-format fuzz harnesses.
|
||||
// Corona threshold-kernel wire-format fuzz harnesses.
|
||||
//
|
||||
// Each FuzzPulsar* harness fuzzes one external wire surface of the
|
||||
// pulsar/threshold kernel:
|
||||
// Each FuzzCorona* harness fuzzes one external wire surface of the
|
||||
// corona/threshold kernel:
|
||||
//
|
||||
// - FuzzPulsarSign1Round1Data — Round1Data.D matrix bytes (sign-1)
|
||||
// - FuzzPulsarSign2Round2Data — Round2Data.Z vector bytes (sign-2)
|
||||
// - FuzzPulsarKeyShareSerialize — KeyShare.SkShare wire bytes
|
||||
// - FuzzPulsarGroupKeySerialize — GroupKey.A,BTilde wire bytes
|
||||
// - FuzzCoronaSign1Round1Data — Round1Data.D matrix bytes (sign-1)
|
||||
// - FuzzCoronaSign2Round2Data — Round2Data.Z vector bytes (sign-2)
|
||||
// - FuzzCoronaKeyShareSerialize — KeyShare.SkShare wire bytes
|
||||
// - FuzzCoronaGroupKeySerialize — GroupKey.A,BTilde wire bytes
|
||||
//
|
||||
// Property: the corresponding decoder NEVER panics on arbitrary input.
|
||||
// Companion TestFuzzCorpus_*Replay tests deterministically replay the
|
||||
@@ -33,9 +33,9 @@ import (
|
||||
|
||||
// maxLatticeUintSliceLen mirrors warp/pulsar.MaxLatticeUintSliceLen and
|
||||
// bounds every length field a lattigo wire frame can declare. A
|
||||
// canonical Pulsar Poly has 256 coefficients per level; a Vector/Matrix
|
||||
// canonical Corona Poly has 256 coefficients per level; a Vector/Matrix
|
||||
// has at most M*N = 8*32 = 256 polys. This cap is structural — frames
|
||||
// that declare more are not legitimate Pulsar protocol bytes.
|
||||
// that declare more are not legitimate Corona protocol bytes.
|
||||
//
|
||||
// IMPORTANT: this duplicate cap exists because the upstream lattice
|
||||
// library has TWO DoS surfaces:
|
||||
@@ -45,7 +45,7 @@ import (
|
||||
// NOT addressed by PR #3. A 9-byte input
|
||||
// `\xad\x93\xd8\x5a\x00\x04\x00\x00\\` reads size=0x40005AD893AD
|
||||
// (~70T entries) and OOMs the goroutine before the slice reader
|
||||
// runs. Found by FuzzPulsarSign1Round1Data on 2026-05-04.
|
||||
// runs. Found by FuzzCoronaSign1Round1Data on 2026-05-04.
|
||||
//
|
||||
// The walker below pre-validates the wire frame BEFORE handing it to
|
||||
// lattigo, mirroring warp/pulsar.validateVectorPolyFrame.
|
||||
@@ -102,7 +102,7 @@ func makeEmptyPolyVector(r *ring.Ring, length int) structs.Vector[ring.Poly] {
|
||||
|
||||
// fuzzMaxRawSize bounds the raw input handed to the lattigo decoder.
|
||||
//
|
||||
// We use 1024 bytes — much tighter than warp/pulsar's
|
||||
// We use 1024 bytes — much tighter than warp/corona's
|
||||
// MaxPulseFrameSize=32KB — because Go's recover() cannot catch the
|
||||
// runtime-fatal "goroutine stack exceeds 1000000000-byte limit"
|
||||
// kill that an unpatched lattigo v7.0.1 produces on the
|
||||
@@ -246,10 +246,10 @@ func addSmallSeeds(f *testing.F) {
|
||||
f.Add(append([]byte{0x10, 0x00, 0x00, 0x00}, bytes.Repeat([]byte{0xcc}, 16)...))
|
||||
}
|
||||
|
||||
// FuzzPulsarSign1Round1Data fuzzes the Vector[Poly] decoder used to
|
||||
// FuzzCoronaSign1Round1Data fuzzes the Vector[Poly] decoder used to
|
||||
// reconstruct a peer's Round-1 D matrix row. A panic here corresponds
|
||||
// to a malicious peer being able to take down a Round-1 receiver.
|
||||
func FuzzPulsarSign1Round1Data(f *testing.F) {
|
||||
func FuzzCoronaSign1Round1Data(f *testing.F) {
|
||||
addSmallSeeds(f)
|
||||
|
||||
f.Fuzz(func(t *testing.T, raw []byte) {
|
||||
@@ -264,10 +264,10 @@ func FuzzPulsarSign1Round1Data(f *testing.F) {
|
||||
})
|
||||
}
|
||||
|
||||
// FuzzPulsarSign2Round2Data fuzzes the Vector[Poly] decoder used to
|
||||
// FuzzCoronaSign2Round2Data fuzzes the Vector[Poly] decoder used to
|
||||
// reconstruct a peer's Round-2 Z vector. Matches the Round-1 surface
|
||||
// but exercises the smaller payload.
|
||||
func FuzzPulsarSign2Round2Data(f *testing.F) {
|
||||
func FuzzCoronaSign2Round2Data(f *testing.F) {
|
||||
addSmallSeeds(f)
|
||||
|
||||
f.Fuzz(func(t *testing.T, raw []byte) {
|
||||
@@ -275,10 +275,10 @@ func FuzzPulsarSign2Round2Data(f *testing.F) {
|
||||
})
|
||||
}
|
||||
|
||||
// FuzzPulsarKeyShareSerialize fuzzes the KeyShare.SkShare wire decoder.
|
||||
// FuzzCoronaKeyShareSerialize fuzzes the KeyShare.SkShare wire decoder.
|
||||
// A KeyShare is the persisted output of DKG; corrupted on-disk shares
|
||||
// must surface as errors, not panics.
|
||||
func FuzzPulsarKeyShareSerialize(f *testing.F) {
|
||||
func FuzzCoronaKeyShareSerialize(f *testing.F) {
|
||||
addSmallSeeds(f)
|
||||
|
||||
f.Fuzz(func(t *testing.T, raw []byte) {
|
||||
@@ -286,9 +286,9 @@ func FuzzPulsarKeyShareSerialize(f *testing.F) {
|
||||
})
|
||||
}
|
||||
|
||||
// FuzzPulsarGroupKeySerialize fuzzes the GroupKey.BTilde wire decoder
|
||||
// FuzzCoronaGroupKeySerialize fuzzes the GroupKey.BTilde wire decoder
|
||||
// (the persistent public key portion of a group key).
|
||||
func FuzzPulsarGroupKeySerialize(f *testing.F) {
|
||||
func FuzzCoronaGroupKeySerialize(f *testing.F) {
|
||||
addSmallSeeds(f)
|
||||
|
||||
f.Fuzz(func(t *testing.T, raw []byte) {
|
||||
@@ -296,11 +296,11 @@ func FuzzPulsarGroupKeySerialize(f *testing.F) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestFuzzCorpus_PulsarSign1Replay replays the canonical seed
|
||||
// TestFuzzCorpus_CoronaSign1Replay replays the canonical seed
|
||||
// deterministically without invoking the fuzz engine. The Sign1 seed
|
||||
// is a serialized Matrix[Poly]; reading it into a fresh Matrix[Poly]
|
||||
// must succeed and return a non-zero byte count.
|
||||
func TestFuzzCorpus_PulsarSign1Replay(t *testing.T) {
|
||||
func TestFuzzCorpus_CoronaSign1Replay(t *testing.T) {
|
||||
mustKernelCeremony(t)
|
||||
if len(kSignSeed) == 0 {
|
||||
t.Fatal("empty Sign1 seed")
|
||||
@@ -316,8 +316,8 @@ func TestFuzzCorpus_PulsarSign1Replay(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestFuzzCorpus_PulsarSign2Replay replays the Round-2 seed.
|
||||
func TestFuzzCorpus_PulsarSign2Replay(t *testing.T) {
|
||||
// TestFuzzCorpus_CoronaSign2Replay replays the Round-2 seed.
|
||||
func TestFuzzCorpus_CoronaSign2Replay(t *testing.T) {
|
||||
mustKernelCeremony(t)
|
||||
if len(kRound2) == 0 {
|
||||
t.Fatal("empty Sign2 seed")
|
||||
@@ -332,9 +332,9 @@ func TestFuzzCorpus_PulsarSign2Replay(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestFuzzCorpus_PulsarKeyShareReplay confirms the KeyShare decoder
|
||||
// TestFuzzCorpus_CoronaKeyShareReplay confirms the KeyShare decoder
|
||||
// accepts the canonical share bytes.
|
||||
func TestFuzzCorpus_PulsarKeyShareReplay(t *testing.T) {
|
||||
func TestFuzzCorpus_CoronaKeyShareReplay(t *testing.T) {
|
||||
mustKernelCeremony(t)
|
||||
var b bytes.Buffer
|
||||
if _, err := kShares[0].SkShare.WriteTo(&b); err != nil {
|
||||
@@ -350,9 +350,9 @@ func TestFuzzCorpus_PulsarKeyShareReplay(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestFuzzCorpus_PulsarGroupKeyReplay confirms the GroupKey decoder
|
||||
// TestFuzzCorpus_CoronaGroupKeyReplay confirms the GroupKey decoder
|
||||
// accepts the canonical bytes.
|
||||
func TestFuzzCorpus_PulsarGroupKeyReplay(t *testing.T) {
|
||||
func TestFuzzCorpus_CoronaGroupKeyReplay(t *testing.T) {
|
||||
mustKernelCeremony(t)
|
||||
var b bytes.Buffer
|
||||
if _, err := kGroupKey.BTilde.WriteTo(&b); err != nil {
|
||||
|
||||
+6
-6
@@ -1,16 +1,16 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package wire is pulsar's wire-format hardening boundary.
|
||||
// Package wire is corona's wire-format hardening boundary.
|
||||
//
|
||||
// LP-107 Phase 4: pulsar consumes luxfi/math/codec for bounded
|
||||
// LP-107 Phase 4: corona consumes luxfi/math/codec for bounded
|
||||
// decoding. Untrusted lattice wire data — Vector[Poly] frames from
|
||||
// network peers, threshold-share blobs from disk, KAT replays —
|
||||
// flows through luxfi/math/codec.Reader so the bounded-decode contract
|
||||
// is centralised: no recursion, no hidden growth, no unbounded
|
||||
// allocation.
|
||||
//
|
||||
// Before this package, pulsar had its own validateVectorPolyFrame
|
||||
// Before this package, corona had its own validateVectorPolyFrame
|
||||
// walker in threshold/fuzz_round_test.go (test-only). This package
|
||||
// replaces that with a production-grade equivalent that consumes the
|
||||
// shared luxfi/math/codec substrate.
|
||||
@@ -23,16 +23,16 @@ import (
|
||||
"github.com/luxfi/math/codec"
|
||||
)
|
||||
|
||||
// MaxLatticeUintSliceLen is pulsar's cap on lattigo Vector[Poly] /
|
||||
// MaxLatticeUintSliceLen is corona's cap on lattigo Vector[Poly] /
|
||||
// Poly inner slice lengths — matches the value warp/pulsar.go already
|
||||
// enforces and the cap at threshold/fuzz_round_test.go:52.
|
||||
//
|
||||
// Pulsar canonical N = 256 and Q ≈ 2^48 (one-prime); a reasonable
|
||||
// Corona canonical N = 256 and Q ≈ 2^48 (one-prime); a reasonable
|
||||
// vector cap is K_max * 1 levels * 256 coeffs = bounded under the
|
||||
// math/codec MaxFrameBytes.
|
||||
const MaxLatticeUintSliceLen = 4096
|
||||
|
||||
// LatticeWireLimits is the codec.Limits configuration pulsar uses for
|
||||
// LatticeWireLimits is the codec.Limits configuration corona uses for
|
||||
// every lattice Vector[Poly] frame on the wire.
|
||||
var LatticeWireLimits = codec.Limits{
|
||||
MaxFrameBytes: 16 * 1024 * 1024,
|
||||
|
||||
Reference in New Issue
Block a user