style: gofmt -s across cmd/ + reshare/ + dkg2/ + keyera/ + threshold/

Whitespace-only struct-tag alignment changes. Closes the CI Lint gate
that was failing across multiple recent commits on otherwise-green
build+test runs.
This commit is contained in:
2026-05-10 17:23:40 -07:00
parent b70f60dc31
commit 666e6b8950
21 changed files with 234 additions and 232 deletions
+22 -22
View File
@@ -180,7 +180,7 @@ func emitGaussian(outDir string) error {
out := struct {
Description string `json:"description"`
Modulus uint64 `json:"modulus"`
Modulus uint64 `json:"modulus"`
N int `json:"n"`
Entries []gaussianEntry `json:"entries"`
}{
@@ -362,23 +362,23 @@ func emitMontgomeryNTT(outDir string) error {
sub := r.SubRings[0]
out := struct {
Description string `json:"description"`
Modulus uint64 `json:"modulus"`
N int `json:"n"`
BRedConstant [2]uint64 `json:"bred_constant"`
MRedConstant uint64 `json:"mred_constant"`
Mask uint64 `json:"mask"`
PrimitiveRoot uint64 `json:"primitive_root"`
NthRoot uint64 `json:"nth_root"`
NInvMontgomery uint64 `json:"n_inv_montgomery"`
RootsForward string `json:"roots_forward_hex"`
RootsBackward string `json:"roots_backward_hex"`
MForm []mformEntry `json:"mform"`
IMForm []mformEntry `json:"imform"`
NTT []mformEntry `json:"ntt"`
INTT []mformEntry `json:"intt"`
Mul []mulEntry `json:"mul_coeffs_montgomery"`
MulAdd []mulAccumEntry `json:"mul_coeffs_montgomery_then_add"`
Description string `json:"description"`
Modulus uint64 `json:"modulus"`
N int `json:"n"`
BRedConstant [2]uint64 `json:"bred_constant"`
MRedConstant uint64 `json:"mred_constant"`
Mask uint64 `json:"mask"`
PrimitiveRoot uint64 `json:"primitive_root"`
NthRoot uint64 `json:"nth_root"`
NInvMontgomery uint64 `json:"n_inv_montgomery"`
RootsForward string `json:"roots_forward_hex"`
RootsBackward string `json:"roots_backward_hex"`
MForm []mformEntry `json:"mform"`
IMForm []mformEntry `json:"imform"`
NTT []mformEntry `json:"ntt"`
INTT []mformEntry `json:"intt"`
Mul []mulEntry `json:"mul_coeffs_montgomery"`
MulAdd []mulAccumEntry `json:"mul_coeffs_montgomery_then_add"`
}{
Description: "Lattigo Ring at Q=0x1000000004A01, N=256. Constants and " +
"forward/inverse NTT + Montgomery transform KATs. Inputs are uint64 " +
@@ -488,10 +488,10 @@ func emitMatrixWire(outDir string) error {
// ---------- KAT 5: BLAKE3 transcripts (Hash, LowNormHash, MAC, GaussianHash, PRF) ----------
type transcriptEntry struct {
Name string `json:"name"`
InputDesc string `json:"input_desc"`
OutputHex string `json:"output_hex"`
Extras []kvHex `json:"extras,omitempty"`
Name string `json:"name"`
InputDesc string `json:"input_desc"`
OutputHex string `json:"output_hex"`
Extras []kvHex `json:"extras,omitempty"`
}
func emitTranscripts(outDir string) error {
+4 -4
View File
@@ -8,8 +8,8 @@
// the C++ test is the "Go → C++" direction of the gate.
//
// Reverse direction (C++ → Go) is handled by:
// * luxcpp/crypto/pulsar/cmd/cross_runtime_oracle/ (C++ writer)
// * lux/pulsar/cmd/cross_runtime_verify/ (Go reader)
// - luxcpp/crypto/pulsar/cmd/cross_runtime_oracle/ (C++ writer)
// - lux/pulsar/cmd/cross_runtime_verify/ (Go reader)
//
// Determinism is required. Two runs with the same MasterSeed produce
// byte-equal manifests AND byte-equal individual KAT JSON files.
@@ -76,9 +76,9 @@ func main() {
// reshare_oracle and dkg2_oracle don't expose --out; they write to
// hardcoded locations in luxcpp/crypto. For the cross-runtime gate
// 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"
dkg2Path := "/Users/z/work/luxcpp/crypto/pulsar/dkg2/test/kat/dkg2_kat.json"
dkg2Path := "/Users/z/work/luxcpp/crypto/pulsar/dkg2/test/kat/dkg2_kat.json"
files := []struct {
name string
+22 -22
View File
@@ -6,27 +6,27 @@
//
// For each (t, n) configuration in the catalogue, the oracle:
//
// 1. Derives a master 32-byte seed from MasterSeed | tag(n,t) via BLAKE3.
// 2. From that master seed, derives one 32-byte sub-seed per party
// (BLAKE3(master || "party" || BE32(i))).
// 3. Constructs n DKGSessions (party 0..n-1) — they all share the same
// deterministic A and B matrices (derived from b"pulsar.dkg2.A.v1" /
// b"pulsar.dkg2.B.v1" via BLAKE3-XOF).
// 4. Each party calls Round1WithSeed(party_seed[i]) to produce its
// Commits, Shares (per recipient), and Blinds (per recipient).
// 5. Each party calls Round2 with the assembled shares/blinds/commits.
// 1. Derives a master 32-byte seed from MasterSeed | tag(n,t) via BLAKE3.
// 2. From that master seed, derives one 32-byte sub-seed per party
// (BLAKE3(master || "party" || BE32(i))).
// 3. Constructs n DKGSessions (party 0..n-1) — they all share the same
// deterministic A and B matrices (derived from b"pulsar.dkg2.A.v1" /
// b"pulsar.dkg2.B.v1" via BLAKE3-XOF).
// 4. Each party calls Round1WithSeed(party_seed[i]) to produce its
// Commits, Shares (per recipient), and Blinds (per recipient).
// 5. Each party calls Round2 with the assembled shares/blinds/commits.
//
// The KAT pins, per entry:
// - For each party i: seed_hex[i] (sub-seed)
// - For each party i: SHA-256(Commits[k].WriteTo) × t (commitment-vector hash)
// - For each party i: BLAKE3(serialize(Commits)) (Round 1.5 digest)
// - For each party i, each recipient j:
// SHA-256(Shares[j].WriteTo)
// SHA-256(Blinds[j].WriteTo)
// SHA-256(Shares[j].WriteTo)
// SHA-256(Blinds[j].WriteTo)
// - For each party j (the recipient running Round2):
// SHA-256(s_j.WriteTo)
// SHA-256(u_j.WriteTo)
// SHA-256(b_ped_j.WriteTo)
// SHA-256(s_j.WriteTo)
// SHA-256(u_j.WriteTo)
// SHA-256(b_ped_j.WriteTo)
//
// Replay invariant: every party agrees on b_ped, so all b_ped hashes inside
// one entry must be identical. The KAT records all n of them so the C++
@@ -105,14 +105,14 @@ func hashMatrix(m structs.Matrix[ring.Poly]) string {
// PartyEntry is one party's contribution to the KAT.
type PartyEntry struct {
PartyID int `json:"party_id"`
SeedHex string `json:"seed_hex"` // Round1WithSeed input
CommitsHashHex []string `json:"commits_hash_hex"` // length t
CommitDigestHex string `json:"commit_digest_hex"` // BLAKE3 over serialized commits
SharesHashHex []string `json:"shares_hash_hex"` // length n; index j = share to party j
BlindsHashHex []string `json:"blinds_hash_hex"` // length n; index j = blind to party j
SecretShareHash string `json:"secret_share_hash"` // SHA-256(s_j) after Round2
BlindShareHash string `json:"blind_share_hash"` // SHA-256(u_j) after Round2
BPedHash string `json:"b_ped_hash"` // SHA-256(b_ped) after Round2
SeedHex string `json:"seed_hex"` // Round1WithSeed input
CommitsHashHex []string `json:"commits_hash_hex"` // length t
CommitDigestHex string `json:"commit_digest_hex"` // BLAKE3 over serialized commits
SharesHashHex []string `json:"shares_hash_hex"` // length n; index j = share to party j
BlindsHashHex []string `json:"blinds_hash_hex"` // length n; index j = blind to party j
SecretShareHash string `json:"secret_share_hash"` // SHA-256(s_j) after Round2
BlindShareHash string `json:"blind_share_hash"` // SHA-256(u_j) after Round2
BPedHash string `json:"b_ped_hash"` // SHA-256(b_ped) after Round2
}
type Entry struct {
+20 -20
View File
@@ -6,14 +6,14 @@
//
// For each (t, n) configuration in the catalogue, the oracle:
//
// 1. Derives a master 32-byte seed from MasterSeed | tag(n,t) via BLAKE3.
// 2. From that master seed, derives one 32-byte sub-seed per party
// (BLAKE3(master || "party" || BE32(i))).
// 3. Constructs n DKGSessions (party 0..n-1) — they all share the same
// deterministic A matrix (seedKey = zeros, mirroring dkg.go:99-105).
// 4. Each party calls Round1WithSeed(party_seed[i]) to produce its
// Commits and per-recipient Shares.
// 5. Each party calls Round2 with the assembled shares/commits.
// 1. Derives a master 32-byte seed from MasterSeed | tag(n,t) via BLAKE3.
// 2. From that master seed, derives one 32-byte sub-seed per party
// (BLAKE3(master || "party" || BE32(i))).
// 3. Constructs n DKGSessions (party 0..n-1) — they all share the same
// deterministic A matrix (seedKey = zeros, mirroring dkg.go:99-105).
// 4. Each party calls Round1WithSeed(party_seed[i]) to produce its
// Commits and per-recipient Shares.
// 5. Each party calls Round2 with the assembled shares/commits.
//
// The KAT pins, per entry:
// - For each party i: seed_hex[i] (sub-seed)
@@ -21,8 +21,8 @@
// - For each party i, each recipient j: SHA-256(Shares[j].WriteTo) — the
// share that party i computed for party j.
// - For each party j (the recipient running Round2):
// SHA-256(secret_share_j.WriteTo)
// SHA-256(bTilde_j.WriteTo)
// SHA-256(secret_share_j.WriteTo)
// SHA-256(bTilde_j.WriteTo)
//
// Replay invariant: every party agrees on bTilde, so all bTilde hashes
// inside one entry must be identical. The KAT records all n of them so
@@ -91,19 +91,19 @@ func hashVectorBytes(r *ring.Ring, v structs.Vector[ring.Poly]) (string, []byte)
// PartyEntry is one party's contribution to the KAT.
type PartyEntry struct {
PartyID int `json:"party_id"`
SeedHex string `json:"seed_hex"` // Round1WithSeed input
CommitsHashHex []string `json:"commits_hash_hex"` // length t
SharesHashHex []string `json:"shares_hash_hex"` // length n; index j = share for party j
SecretShareHash string `json:"secret_share_hash"` // SHA-256 of secret_share_j (after Round2)
BTildeHash string `json:"btilde_hash"` // SHA-256 of bTilde_j (after Round2)
SeedHex string `json:"seed_hex"` // Round1WithSeed input
CommitsHashHex []string `json:"commits_hash_hex"` // length t
SharesHashHex []string `json:"shares_hash_hex"` // length n; index j = share for party j
SecretShareHash string `json:"secret_share_hash"` // SHA-256 of secret_share_j (after Round2)
BTildeHash string `json:"btilde_hash"` // SHA-256 of bTilde_j (after Round2)
}
type Entry struct {
T int `json:"t"`
N int `json:"n"`
MasterSeedHex string `json:"master_seed_hex"`
AHashHex string `json:"a_hash_hex"` // SHA-256(A.WriteTo) — same for all parties
Parties []PartyEntry `json:"parties"`
T int `json:"t"`
N int `json:"n"`
MasterSeedHex string `json:"master_seed_hex"`
AHashHex string `json:"a_hash_hex"` // SHA-256(A.WriteTo) — same for all parties
Parties []PartyEntry `json:"parties"`
}
type OracleOut struct {
+20 -20
View File
@@ -31,8 +31,8 @@ import (
)
const (
Q uint64 = 0x1000000004A01
BsquareDecStr = "184960669042442604975662780477"
Q uint64 = 0x1000000004A01
BsquareDecStr = "184960669042442604975662780477"
)
func qBig() *big.Int { return new(big.Int).SetUint64(Q) }
@@ -181,18 +181,18 @@ type L2Entry struct {
}
type L2OracleOut struct {
Description string `json:"description"`
Q uint64 `json:"q"`
BsquareDec string `json:"bsquare_dec"`
Entries []L2Entry `json:"entries"`
Description string `json:"description"`
Q uint64 `json:"q"`
BsquareDec string `json:"bsquare_dec"`
Entries []L2Entry `json:"entries"`
}
type FrcEntry struct {
Name string `json:"name"`
Rows int `json:"rows"`
Cols int `json:"cols"`
Matrix [][]uint64 `json:"matrix"`
FullRank bool `json:"full_rank"`
Name string `json:"name"`
Rows int `json:"rows"`
Cols int `json:"cols"`
Matrix [][]uint64 `json:"matrix"`
FullRank bool `json:"full_rank"`
}
type FrcOracleOut struct {
@@ -216,16 +216,16 @@ func emitL2Norm(outDir string) error {
}{
{"empty", 0, 0},
{"single_zero", 1, 0},
{"small_random", 64, 0}, // pure deterministic-random
{"small_random", 64, 0}, // pure deterministic-random
{"medium_random", 256, 0},
{"large_random", 1024, 0},
{"under_bound_64", 64, 1 << 30}, // ~ 64 * 2^60 = 2^66, well under
{"under_bound_256", 256, 1 << 30}, // ~ 2^68, still well under
{"under_bound_3840", 3840, 1 << 25}, // typical Sign vector size; under
{"near_bound", 3840, 1 << 31}, // 3840 * 2^62 = 2^74, way under still
{"over_bound_small", 4, Q / 2}, // each squared ~Q²/4, 4 of them ≈ Q²
{"under_bound_64", 64, 1 << 30}, // ~ 64 * 2^60 = 2^66, well under
{"under_bound_256", 256, 1 << 30}, // ~ 2^68, still well under
{"under_bound_3840", 3840, 1 << 25}, // typical Sign vector size; under
{"near_bound", 3840, 1 << 31}, // 3840 * 2^62 = 2^74, way under still
{"over_bound_small", 4, Q / 2}, // each squared ~Q²/4, 4 of them ≈ Q²
{"over_bound", 1024, Q / 2},
{"max_centered", 8, Q / 2}, // edge: 8 * (Q/2)^2 ≈ 2^97, near bound
{"max_centered", 8, Q / 2}, // edge: 8 * (Q/2)^2 ≈ 2^97, near bound
}
for _, c := range cases {
@@ -265,11 +265,11 @@ func emitFullRankCheck(outDir string) error {
{"square_5x5", 5, 5, false},
{"square_8x8", 8, 8, false},
{"wide_5x9", 5, 9, false},
{"wide_8x47", 8, 47, false}, // typical Corona shape (M × Dbar, but ignore col-0 for total cols)
{"wide_8x47", 8, 47, false}, // typical Corona shape (M × Dbar, but ignore col-0 for total cols)
{"wide_8x12", 8, 12, false},
{"deficient_5x5", 5, 5, true},
{"deficient_8x10", 8, 10, true},
{"tall_3x2", 3, 2, false}, // taller than wide — likely deficient by Q-rank
{"tall_3x2", 3, 2, false}, // taller than wide — likely deficient by Q-rank
{"identity_4", 4, 4, false},
}
+8 -8
View File
@@ -50,17 +50,17 @@ import (
// ReadOp is one (n) read from the precomputed buffer; the captured bytes
// are the expected output.
type ReadOp struct {
N int `json:"n"` // number of bytes pulled
OutHex string `json:"out_hex"` // 2*n hex chars
N int `json:"n"` // number of bytes pulled
OutHex string `json:"out_hex"` // 2*n hex chars
}
type Entry struct {
Name string `json:"name"`
KeyHex string `json:"key_hex"` // BLAKE3 input bytes
BufferSize int `json:"buffer_size"` // total precomputed bytes
BufferHex string `json:"buffer_hex"` // full buffer (sanity-check)
Reads []ReadOp `json:"reads"`
FinalIndex int `json:"final_index"` // sum of all n_i (must == buffer cursor at end)
Name string `json:"name"`
KeyHex string `json:"key_hex"` // BLAKE3 input bytes
BufferSize int `json:"buffer_size"` // total precomputed bytes
BufferHex string `json:"buffer_hex"` // full buffer (sanity-check)
Reads []ReadOp `json:"reads"`
FinalIndex int `json:"final_index"` // sum of all n_i (must == buffer cursor at end)
}
type OracleOut struct {
+2 -2
View File
@@ -50,8 +50,8 @@ import (
)
const (
Q uint64 = 0x1000000004A01
N = 256
Q uint64 = 0x1000000004A01
N = 256
// QByteLen is the number of bytes returned by big.Int.Bytes() for Q.
// Q = 0x1000000004A01 has 7 bytes (high byte 0x01).
QByteLen = 7
+6 -6
View File
@@ -35,10 +35,10 @@ const (
)
type Entry struct {
Name string `json:"name"`
SeedHex string `json:"seed_hex"`
NumPolys int `json:"num_polys"`
PolysHex []string `json:"polys_hex"`
Name string `json:"name"`
SeedHex string `json:"seed_hex"`
NumPolys int `json:"num_polys"`
PolysHex []string `json:"polys_hex"`
}
type OracleOut struct {
@@ -87,10 +87,10 @@ func main() {
{"single_poly_beta", "beta", 1},
{"two_polys_a", "two_a", 2},
{"two_polys_b", "two_b", 2},
{"five_polys", "five", 5}, // exercises buffer refill (5 * 256 * 8 = 10240B > 1024B buffer)
{"five_polys", "five", 5}, // exercises buffer refill (5 * 256 * 8 = 10240B > 1024B buffer)
{"eight_polys", "eight", 8},
{"thirteen_polys", "thirteen", 13}, // larger refill count
{"matrix_7x8", "matrix_7x8", 56}, // M*N_vec scale
{"matrix_7x8", "matrix_7x8", 56}, // M*N_vec scale
}
for _, c := range cases {
+23 -23
View File
@@ -12,14 +12,14 @@
// - t_old, n_old, t_new, n_new : protocol parameters
// - old_set, new_set : 1-indexed party IDs
// - secret_polys_hex [poly_count] : the master secret s before sharing
// (4096 hex chars = 256 uint64 BE per poly)
// (4096 hex chars = 256 uint64 BE per poly)
// - old_shamir_seed_hex : deterministic seed for the
// old Shamir random a_1..a_{t_old-1}
// (BLAKE3 XOF, q_byte_len bytes per draw)
// old Shamir random a_1..a_{t_old-1}
// (BLAKE3 XOF, q_byte_len bytes per draw)
// - reshare_rng_seed_hex : deterministic seed for the
// reshare random higher-degree
// coefficients (SHA-256 counter
// mode, see counterRand)
// reshare random higher-degree
// coefficients (SHA-256 counter
// mode, see counterRand)
// - old_shares_hex [n_old] : Shamir shares of s for old set
// - new_shares_hex [n_new] : reshared shares for new set
// - old_share_sha256_hex [n_old] : sha256 of each old party's full share
@@ -118,21 +118,21 @@ func (c *counterRand) Read(p []byte) (int, error) {
var _ io.Reader = (*counterRand)(nil)
type Entry struct {
Label string `json:"label"`
Variant string `json:"variant"` // "reshare" or "refresh"
TOld int `json:"t_old"`
NOld int `json:"n_old"`
TNew int `json:"t_new"`
NNew int `json:"n_new"`
OldSet []int `json:"old_set"`
NewSet []int `json:"new_set"`
SecretPolysHex []string `json:"secret_polys_hex"`
OldShamirSeedHex string `json:"old_shamir_seed_hex"`
ReshareRngSeedHex string `json:"reshare_rng_seed_hex"`
OldSharesHex []string `json:"old_shares_hex"`
NewSharesHex []string `json:"new_shares_hex"`
OldShareSHA256Hex []string `json:"old_share_sha256_hex"`
NewShareSHA256Hex []string `json:"new_share_sha256_hex"`
Label string `json:"label"`
Variant string `json:"variant"` // "reshare" or "refresh"
TOld int `json:"t_old"`
NOld int `json:"n_old"`
TNew int `json:"t_new"`
NNew int `json:"n_new"`
OldSet []int `json:"old_set"`
NewSet []int `json:"new_set"`
SecretPolysHex []string `json:"secret_polys_hex"`
OldShamirSeedHex string `json:"old_shamir_seed_hex"`
ReshareRngSeedHex string `json:"reshare_rng_seed_hex"`
OldSharesHex []string `json:"old_shares_hex"`
NewSharesHex []string `json:"new_shares_hex"`
OldShareSHA256Hex []string `json:"old_share_sha256_hex"`
NewShareSHA256Hex []string `json:"new_share_sha256_hex"`
}
type OracleOut struct {
@@ -156,8 +156,8 @@ type config struct {
variant string // "reshare" (default) or "refresh"
tOld int
nOld int
tNew int // ignored for refresh (= tOld)
newSet []int // 1-indexed party IDs (for refresh, this is the unchanged committee)
tNew int // ignored for refresh (= tOld)
newSet []int // 1-indexed party IDs (for refresh, this is the unchanged committee)
}
func main() {
+2 -2
View File
@@ -126,8 +126,8 @@ func (r ComplaintReason) String() string {
// malformed structure.
type Complaint struct {
TranscriptHash [32]byte
SenderID int // 0-indexed party ID of the misbehaving dealer
ComplainerID int // 0-indexed party ID of the complainer
SenderID int // 0-indexed party ID of the misbehaving dealer
ComplainerID int // 0-indexed party ID of the complainer
Reason ComplaintReason
Evidence []byte // canonical-serialized evidence (see above)
Signature []byte // Ed25519 signature over Bytes() under ComplainerID's wire key
+5 -5
View File
@@ -373,11 +373,11 @@ func (d *DKGSession) Round1() (*Round1Output, error) {
//
// Sampling order (BYTE-PINNED — must match the C++ port):
//
// 1. KeyedPRNG(seed) → Gaussian sampler (σ_E, β_E).
// 2. For k = 0..t-1: sample c_{i,k} (Nvec polys, standard form).
// 3. For k = 0..t-1: sample r_{i,k} (Nvec polys, standard form).
// 4. Compute commits[k] = A*NTT(c_k) + B*NTT(r_k).
// 5. Compute shares[j], blinds[j] via Horner over the (j+1) point.
// 1. KeyedPRNG(seed) → Gaussian sampler (σ_E, β_E).
// 2. For k = 0..t-1: sample c_{i,k} (Nvec polys, standard form).
// 3. For k = 0..t-1: sample r_{i,k} (Nvec polys, standard form).
// 4. Compute commits[k] = A*NTT(c_k) + B*NTT(r_k).
// 5. Compute shares[j], blinds[j] via Horner over the (j+1) point.
//
// The two-pass sample order (all c's first, then all r's) matters for byte
// equality across the Go reference and the C++ port.
+14 -14
View File
@@ -7,23 +7,23 @@
// These tests pin the era-pinning property of the HashSuite (per
// proofs/pulsar/hash-suite-separation.tex Remark on era-pinning):
//
// A. Bootstrap pins suite. The HashSuiteID recorded on a freshly
// bootstrapped era equals exactly the ID of the supplied
// HashSuite, and the field is read-only on the returned KeyEra.
// B. Reshare cannot change suite. The Reshare API does not accept a
// HashSuite parameter (verified at the type level by reading
// reshare API), and the propagated state's HashSuiteID equals the
// era's HashSuiteID byte-for-byte.
// C. Reanchor MAY change suite. ReanchorWithSuite opens a new era
// with a fresh GroupKey and a fresh HashSuiteID; the prior era's
// HashSuiteID is unchanged.
// A. Bootstrap pins suite. The HashSuiteID recorded on a freshly
// bootstrapped era equals exactly the ID of the supplied
// HashSuite, and the field is read-only on the returned KeyEra.
// B. Reshare cannot change suite. The Reshare API does not accept a
// HashSuite parameter (verified at the type level by reading
// reshare API), and the propagated state's HashSuiteID equals the
// era's HashSuiteID byte-for-byte.
// C. Reanchor MAY change suite. ReanchorWithSuite opens a new era
// with a fresh GroupKey and a fresh HashSuiteID; the prior era's
// HashSuiteID is unchanged.
//
// Citations (canonical proof bucket):
//
// proofs/definitions/transcript-binding.tex
// Definition ref:pulsar-transcript
// proofs/pulsar/hash-suite-separation.tex
// Theorem ref:hash-suite-separation
// proofs/definitions/transcript-binding.tex
// Definition ref:pulsar-transcript
// proofs/pulsar/hash-suite-separation.tex
// Theorem ref:hash-suite-separation
package keyera
import (
+1 -1
View File
@@ -47,7 +47,7 @@ func TestReshareTranscriptHashStable(t *testing.T) {
1: {0x11},
5: {0x55},
},
ComplaintHashes: [][32]byte{{0x99}, {0x88}},
ComplaintHashes: [][32]byte{{0x99}, {0x88}},
DisqualifiedSenders: []int{4, 2},
QualifiedQuorum: []int{1, 3, 5},
}
+4 -4
View File
@@ -123,11 +123,11 @@ func (r ComplaintReason) String() string {
// - For ComplaintMalformedCommit: the malformed commit vector.
type Complaint struct {
TranscriptHash [32]byte
SenderID int // 1-indexed party ID of the misbehaving dealer
ComplainerID int // 1-indexed party ID of the complainer
SenderID int // 1-indexed party ID of the misbehaving dealer
ComplainerID int // 1-indexed party ID of the complainer
Reason ComplaintReason
Evidence []byte // canonical-serialized evidence (see above)
Signature []byte // Ed25519 signature over Bytes() under ComplainerID's wire key
Evidence []byte // canonical-serialized evidence (see above)
Signature []byte // Ed25519 signature over Bytes() under ComplainerID's wire key
ComplainerKey ed25519.PublicKey // public key for verification
}
+15 -15
View File
@@ -23,19 +23,19 @@ import (
// TestFullReshareThenKeyShareThenSign — production-relevance integration
// test that exercises Correction 3:
//
// 1. Genesis: create (A, s, e, b̃) and standard-Shamir shares for an
// OLD committee.
// 2. The OLD committee runs sign once with bTilde to confirm the
// genesis is sound.
// 3. Resharing: rotate the share distribution onto a NEW committee
// via Reshare. NO trusted dealer.
// 4. The NEW committee establishes pairwise KEX → DeriveSeeds /
// DeriveMACKeys and constructs full PartyKeyShare instances via
// PartyKeyShareFromShare (Lambda + Seeds + MACKeys + GroupKey
// pointer all populated).
// 5. The NEW committee drives sign.SignRound1/Round2/Finalize,
// and the resulting signature MUST verify against the
// UNCHANGED bTilde from step 1.
// 1. Genesis: create (A, s, e, b̃) and standard-Shamir shares for an
// OLD committee.
// 2. The OLD committee runs sign once with bTilde to confirm the
// genesis is sound.
// 3. Resharing: rotate the share distribution onto a NEW committee
// via Reshare. NO trusted dealer.
// 4. The NEW committee establishes pairwise KEX → DeriveSeeds /
// DeriveMACKeys and constructs full PartyKeyShare instances via
// PartyKeyShareFromShare (Lambda + Seeds + MACKeys + GroupKey
// pointer all populated).
// 5. The NEW committee drives sign.SignRound1/Round2/Finalize,
// and the resulting signature MUST verify against the
// UNCHANGED bTilde from step 1.
//
// This is the test that proves the brief's Correction 3 is satisfied:
// resharing only SkShare is not enough — Lambda, Seeds, MACKeys, and
@@ -122,8 +122,8 @@ func TestFullReshareThenKeyShareThenSign(t *testing.T) {
wireKeys[i] = wirePub[i]
}
tIn := TranscriptInputs{
ChainID: []byte("lux-mainnet-test"),
GroupID: []byte("quasar-pq-test"),
ChainID: []byte("lux-mainnet-test"),
GroupID: []byte("quasar-pq-test"),
OldEpochID: 1, NewEpochID: 2,
OldSetHash: ValidatorSetHash(wireKeys, nil), NewSetHash: ValidatorSetHash(wireKeys, nil),
ThresholdOld: tOld, ThresholdNew: tNew,
+10 -10
View File
@@ -17,12 +17,12 @@
//
// The 17 fields covered are:
//
// chain_id network_id group_id
// key_era_id old_generation new_generation
// old_epoch_id new_epoch_id old_set_hash
// new_set_hash threshold_old threshold_new
// group_public_key_hash nebula_root hash_suite_id
// implementation_version variant
// chain_id network_id group_id
// key_era_id old_generation new_generation
// old_epoch_id new_epoch_id old_set_hash
// new_set_hash threshold_old threshold_new
// group_public_key_hash nebula_root hash_suite_id
// implementation_version variant
//
// The "unchanged GroupKey" is modelled by a fixed test-side oracle
// that, given the original activation bytes, returns true for the
@@ -31,10 +31,10 @@
//
// Citations (canonical proof bucket):
//
// proofs/definitions/transcript-binding.tex
// Definition ref:pulsar-transcript
// proofs/pulsar/hash-suite-separation.tex
// Theorem ref:hash-suite-separation
// proofs/definitions/transcript-binding.tex
// Definition ref:pulsar-transcript
// proofs/pulsar/hash-suite-separation.tex
// Theorem ref:hash-suite-separation
package reshare
import (
+15 -15
View File
@@ -74,13 +74,13 @@ package reshare
// - Threshold may differ from previous epoch (Reshare allows
// t_old → t_new transitions). Refresh keeps t unchanged.
type EpochShareState struct {
Epoch uint64
ValidatorSet []string // ordered validator wire-identity strings
Threshold int // new threshold t_new (= t_old for Refresh)
Shares map[string]*PartyKeyShare // validator-ID → share
GroupKey *PartyGroupKey // POINTER to persistent group key
TranscriptHash [32]byte // bind to TranscriptInputs.Hash()
ActivationCert *ActivationCert // proof the chain accepted this epoch
Epoch uint64
ValidatorSet []string // ordered validator wire-identity strings
Threshold int // new threshold t_new (= t_old for Refresh)
Shares map[string]*PartyKeyShare // validator-ID → share
GroupKey *PartyGroupKey // POINTER to persistent group key
TranscriptHash [32]byte // bind to TranscriptInputs.Hash()
ActivationCert *ActivationCert // proof the chain accepted this epoch
}
// ReshareEpochInputs is the parameter bundle for one resharing round.
@@ -112,14 +112,14 @@ type ReshareEpochInputs struct {
// Refresh round. The committee and threshold are unchanged, only the
// share distribution rotates.
type RefreshEpochInputs struct {
ChainID []byte
GroupID []byte
OldEpochID uint64
NewEpochID uint64
Validators []string // unchanged committee
Shares map[int]Share // 1-indexed party ID → share
Threshold int // unchanged threshold
GroupKeyHash [32]byte
ChainID []byte
GroupID []byte
OldEpochID uint64
NewEpochID uint64
Validators []string // unchanged committee
Shares map[int]Share // 1-indexed party ID → share
Threshold int // unchanged threshold
GroupKeyHash [32]byte
}
// BuildTranscript converts ReshareEpochInputs into the
+5 -5
View File
@@ -14,11 +14,11 @@ import (
// We build (t, n)-Shamir shares of a planted secret, run Refresh, and
// verify:
//
// 1. Old shares interpolate to the planted secret.
// 2. New shares interpolate to the SAME planted secret.
// 3. New shares ≠ old shares (Hamming/byte distance positive — the
// probability all coordinates collide is < 2^-48 per coordinate
// times nVec*N coordinates ≈ 2^-48 * 7 * 256 ≈ negligible).
// 1. Old shares interpolate to the planted secret.
// 2. New shares interpolate to the SAME planted secret.
// 3. New shares ≠ old shares (Hamming/byte distance positive — the
// probability all coordinates collide is < 2^-48 per coordinate
// times nVec*N coordinates ≈ 2^-48 * 7 * 256 ≈ negligible).
//
// This is the canonical test that proves Refresh implements the
// HJKY97 zero-polynomial pattern correctly.
+26 -24
View File
@@ -25,13 +25,13 @@
// and privately delivers g_i(β_j) to each new party j ∈ N. Each
// new party j computes
//
// s'_j = Σ_{i ∈ Q} λ^Q_i · g_i(β_j)
// s'_j = Σ_{i ∈ Q} λ^Q_i · g_i(β_j)
//
// where λ^Q_i are the Lagrange coefficients for the quorum Q
// evaluated at 0. Define G(X) = Σ_{i ∈ Q} λ^Q_i · g_i(X). Then
// deg(G) = t_new − 1 with overwhelming probability, and
//
// G(0) = Σ_{i ∈ Q} λ^Q_i · g_i(0) = Σ_{i ∈ Q} λ^Q_i · s_i = s
// G(0) = Σ_{i ∈ Q} λ^Q_i · g_i(0) = Σ_{i ∈ Q} λ^Q_i · s_i = s
//
// by Lagrange interpolation over Q at X=0. The {G(β_j) : j ∈ N}
// are therefore valid (t_new, |N|)-Shamir shares of the SAME master
@@ -61,27 +61,27 @@
// files in this package:
//
// - commit.go — Pedersen-style polynomial commitments to f_i
// (Refresh) and g_i (Reshare); recipients verify
// each share against the committed polynomial.
// (Refresh) and g_i (Reshare); recipients verify
// each share against the committed polynomial.
// - transcript.go — Domain-separated transcript binding all
// resharing messages, complaints, and the
// (chain_id, epoch, group_id, sets, thresholds,
// group_pk_hash) tuple.
// resharing messages, complaints, and the
// (chain_id, epoch, group_id, sets, thresholds,
// group_pk_hash) tuple.
// - complaint.go — Complaint format, signed evidence, complaint
// quorum logic, deterministic disqualification of
// misbehaving senders.
// quorum logic, deterministic disqualification of
// misbehaving senders.
// - keyshare.go — Wraps reshared SkShare values into complete
// Corona/Pulsar `KeyShare` instances by
// regenerating Lambda, Seeds, MACKeys, and
// attaching the unchanged GroupKey pointer.
// Corona/Pulsar `KeyShare` instances by
// regenerating Lambda, Seeds, MACKeys, and
// attaching the unchanged GroupKey pointer.
// - pairwise.go — Authenticated pairwise KEX (X25519 / ML-KEM
// hybrid) → KDF derivation of new Seeds, MACKeys
// under domain-separated tags.
// hybrid) → KDF derivation of new Seeds, MACKeys
// under domain-separated tags.
// - activation.go — Post-reshare activation certificate: the new
// committee threshold-signs the resharing
// transcript hash under the unchanged GroupKey.
// The chain accepts the new epoch only when this
// activation cert verifies.
// committee threshold-signs the resharing
// transcript hash under the unchanged GroupKey.
// The chain accepts the new epoch only when this
// activation cert verifies.
//
// All of the above MUST be wired together at the Quasar consensus
// layer (`protocol/quasar/epoch.go`) — see `quasar_integration.go` for
@@ -307,12 +307,14 @@ func Reshare(
// 1. Each party i samples a random degree-(t-1) polynomial z_i(X)
// over R_q^{Nvec} with z_i(0) = 0 (i.e. z_i has no constant term;
// coefficients of degree 1..t-1 are uniformly random).
//
// 2. Each party i privately delivers z_i(α_j) to every party j in the
// committee, where α_j is the Shamir evaluation point of party j
// (here α_j = j, the 1-indexed party ID).
//
// 3. Each party j updates its share:
//
// s'_j = s_j + Σ_i z_i(α_j) (mod q)
// s'_j = s_j + Σ_i z_i(α_j) (mod q)
//
// Correctness. Define Z(X) = Σ_i z_i(X). Z is a degree-(t-1) random
// polynomial over R_q with Z(0) = 0. The new shares are evaluations of
@@ -327,13 +329,13 @@ func Reshare(
//
// - r — the canonical R_q ring (must match sign.Gen).
// - shares — partyID → share, the current committee's shares.
// The map MUST contain every party in the committee
// (refresh has no quorum semantics; ALL parties must
// participate, since the zero-polynomial deltas must
// be computed at every evaluation point).
// The map MUST contain every party in the committee
// (refresh has no quorum semantics; ALL parties must
// participate, since the zero-polynomial deltas must
// be computed at every evaluation point).
// - threshold — the unchanged reconstruction threshold t.
// - randSource — randomness source for the fresh polynomial
// coefficients (nil → crypto/rand.Reader).
// coefficients (nil → crypto/rand.Reader).
//
// Returns the refreshed share map, keyed by the SAME party IDs as the
// input. The output shares are valid (threshold, |committee|)-Shamir
+3 -3
View File
@@ -19,9 +19,9 @@ import (
)
const (
testLogN = 8
testQ = sign.Q
testNVec = 3
testLogN = 8
testQ = sign.Q
testNVec = 3
)
// fakeRand is a deterministic PRNG built from a SHA-256 counter mode.
+7 -7
View File
@@ -39,13 +39,13 @@ import (
//
// IMPORTANT: this duplicate cap exists because the upstream lattice
// library has TWO DoS surfaces:
// 1. ReadUintNSlice unbounded recursion — fixed by luxfi/lattice#3
// (open as of 2026-05-04).
// 2. Vector.ReadFrom calling make([]T, size) before any bound check —
// NOT addressed by PR #3. A 9-byte input
// `\xad\x93\xd8\x5a\x00\x04\x00\x00\\` reads size=0x40005AD893AD
// (~70T entries) and OOMs the goroutine before the slice reader
// runs. Found by FuzzPulsarSign1Round1Data on 2026-05-04.
// 1. ReadUintNSlice unbounded recursion — fixed by luxfi/lattice#3
// (open as of 2026-05-04).
// 2. Vector.ReadFrom calling make([]T, size) before any bound check —
// NOT addressed by PR #3. A 9-byte input
// `\xad\x93\xd8\x5a\x00\x04\x00\x00\\` reads size=0x40005AD893AD
// (~70T entries) and OOMs the goroutine before the slice reader
// runs. Found by FuzzPulsarSign1Round1Data on 2026-05-04.
//
// The walker below pre-validates the wire frame BEFORE handing it to
// lattigo, mirroring warp/pulsar.validateVectorPolyFrame.