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