pulsar: decomplect names to domain (NonceCert/Partial/Aggregate/ConsensusCert)

Rename mitigation-named public types to domain names (BCC* dropped; boundary
clearance / carry elimination kept as implementation profile in docs/spec):
  BoundaryNonceCert -> NonceCert        NonceCertVote      -> NonceVote
  NonceMPCTranscript -> NonceTranscript  MPCTranscriptRoot -> NonceTranscriptRoot
  BCCZPartial -> Partial                 ZAggregate        -> Aggregate
  BCCSignature -> Signature (folded into existing type)   ConsensusCert (kept)
Files: threshold_bcc*.go/nonce_mpc.go/z_partial.go/tree_aggregate.go/
  dkg_wellformed.go -> boundary.go/consensus.go/proof.go/nonce.go/partial.go/
  aggregate.go/dkg_wellformed.go (+tests). Recover clobbered dkg.go from HEAD.
All tests green, vet clean.
This commit is contained in:
zooqueen
2026-06-21 01:18:31 -07:00
parent ba30df5102
commit 89c25d9675
12 changed files with 318 additions and 68 deletions
+65
View File
@@ -0,0 +1,65 @@
package pulsar
import "errors"
// Tree aggregation for ~1000-validator committees. Tree nodes carry only
// z-sums, bitmaps, and proof roots — never hint material, w, or residual.
var (
ErrDuplicateSigner = errors.New("pulsar: duplicate signer in tree aggregate")
ErrWrongSessionAgg = errors.New("pulsar: aggregate session/nonce mismatch")
)
func bitmapsOverlap(a, b []byte) bool {
n := len(a)
if len(b) < n {
n = len(b)
}
for i := 0; i < n; i++ {
if a[i]&b[i] != 0 {
return true
}
}
return false
}
func bitmapUnion(a, b []byte) []byte {
n := len(a)
if len(b) > n {
n = len(b)
}
out := make([]byte, n)
for i := 0; i < n; i++ {
var x, y byte
if i < len(a) {
x = a[i]
}
if i < len(b) {
y = b[i]
}
out[i] = x | y
}
return out
}
// MergeAggregates merges child Aggregates: identical session+nonce, DISJOINT
// bitmaps (no duplicate signer), z summed mod q.
func MergeAggregates(children []Aggregate, l int) (Aggregate, error) {
if len(children) == 0 {
return Aggregate{}, nil
}
sid, nid := children[0].SessionID, children[0].NonceID
z := make(polyVec, l)
var bitmap []byte
for _, ch := range children {
if ch.SessionID != sid || ch.NonceID != nid {
return Aggregate{}, ErrWrongSessionAgg
}
if bitmapsOverlap(bitmap, ch.SignerBitmap) {
return Aggregate{}, ErrDuplicateSigner
}
bitmap = bitmapUnion(bitmap, ch.SignerBitmap)
sumZ(z, unpackPolyVec(ch.ZSum, l), l)
}
return Aggregate{SessionID: sid, NonceID: nid, SignerBitmap: bitmap, ZSum: packPolyVec(z)}, nil
}
@@ -15,14 +15,14 @@ import (
// ---- Wire types (no hint-secret fields, no full w) ----
// BoundaryNonceCert is a boundary-cleared nonce certificate. It carries only
// NonceCert is a boundary-cleared nonce certificate. It carries only
// w1 = HighBits(w) (public, used in the challenge), a hiding commitment to w,
// and a validator-run NonceMPC clearance certificate (MPCTranscriptRoot +
// and a validator-run NonceMPC clearance certificate (NonceTranscriptRoot +
// ClearanceQC) — NEVER full w, w_i shares, LowBits(w), boundary distances, or
// hint-secret material (PULSAR-V13-W-LEAK). The production clearance backend
// is the quorum-certified validator transcript; ClearanceProof is retained
// only for an optional pluggable ZK backend.
type BoundaryNonceCert struct {
type NonceCert struct {
NonceID [32]byte
PKEpoch uint64
CommitteeID [32]byte
@@ -32,14 +32,14 @@ type BoundaryNonceCert struct {
Margin uint32
RegionRoot [32]byte
CommitRoot [32]byte
MPCTranscriptRoot [32]byte // root of the validator NonceMPC transcript
NonceTranscriptRoot [32]byte // root of the validator NonceMPC transcript
ClearanceQC QuorumCert // quorum of validators that verified the transcript
ClearanceProof []byte // optional pluggable ZK backend (unused for the NonceMPC path)
Consumed bool
}
// BCCZPartial is a proof-carrying z-share. No CS2/CT0/r0/LowBits/hint fields.
type BCCZPartial struct {
// Partial is a proof-carrying z-share. No CS2/CT0/r0/LowBits/hint fields.
type Partial struct {
PartyID uint32
NonceID [32]byte
SessionID [32]byte
@@ -48,12 +48,6 @@ type BCCZPartial struct {
MAC []byte
}
// BCCSignature is a final, unmodified FIPS 204 ML-DSA signature.
type BCCSignature struct {
Mode Mode
Bytes []byte // sigEncode(c, z, h)
}
// ConsensusCert is the two-certificate consensus artifact: the ML-DSA
// signature proves the joint key signed; the bitmap + transcript root prove
// which validators participated (BLS-like accountability).
@@ -65,7 +59,7 @@ type ConsensusCert struct {
JointPKID [32]byte
SignerBitmap []byte
TranscriptRoot [32]byte
Signature BCCSignature
Signature Signature
}
// ---- Canonical, non-grindable nonce selection ----
@@ -197,13 +191,12 @@ func (c *ConsensusCert) VerifyStructure(quorum, validatorSetSize int) error {
func productionWireTypes() []reflect.Type {
return []reflect.Type{
reflect.TypeOf(BoundaryNonceCert{}),
reflect.TypeOf(BCCZPartial{}),
reflect.TypeOf(BCCSignature{}),
reflect.TypeOf(NonceCert{}),
reflect.TypeOf(Partial{}),
reflect.TypeOf(ConsensusCert{}),
reflect.TypeOf(QuorumCert{}),
reflect.TypeOf(NonceCertVote{}),
reflect.TypeOf(ZAggregate{}),
reflect.TypeOf(NonceVote{}),
reflect.TypeOf(Aggregate{}),
}
}
+28
View File
@@ -0,0 +1,28 @@
package pulsar
import "reflect"
// DKG public output: joint public key (t1) + share commitments + transcript
// root + well-formedness QC. It NEVER carries t0, s2 hint material, the full
// secret t, or any master secret (PULSAR-V13). Online signing needs only
// s1_i, y_i (held locally) and public t1.
type DKGShareCommitment struct {
PartyID uint32
Commit []byte
}
type DKGPublicOutput struct {
PKEpoch uint64
JointPublicKey []byte // packed ML-DSA public key (rho + t1)
ShareCommitments []DKGShareCommitment
DKGTranscriptRoot [32]byte
WellFormednessQC QuorumCert
}
func productionDKGTypes() []reflect.Type {
return []reflect.Type{
reflect.TypeOf(DKGPublicOutput{}),
reflect.TypeOf(DKGShareCommitment{}),
}
}
@@ -9,7 +9,7 @@ import (
// NonceMPC is a validator-run consensus subprotocol (not a separate service):
// validators jointly compute w1 = HighBits(w) and BoundaryClear(w, 2β) over
// their hidden w = A·y shares and sign a NonceCertVote; a quorum forms the
// their hidden w = A·y shares and sign a NonceVote; a quorum forms the
// ClearanceQC. Full w is never opened. Public chain verifiers check the QC,
// never the hidden w (PULSAR-V13-W-LEAK).
@@ -26,9 +26,9 @@ func (qc QuorumCert) IsEmpty() bool {
return len(qc.SignerBitmap) == 0 && len(qc.Signatures) == 0 && qc.PayloadRoot == [32]byte{}
}
// NonceCertVote is one validator's signed attestation that the NonceMPC
// NonceVote is one validator's signed attestation that the NonceMPC
// transcript proves a boundary-clear hidden w with the given w1.
type NonceCertVote struct {
type NonceVote struct {
Epoch uint64
CommitteeID [32]byte
NonceID [32]byte
@@ -36,12 +36,12 @@ type NonceCertVote struct {
Margin uint32
CommitRoot [32]byte
RegionRoot [32]byte
MPCTranscriptRoot [32]byte
NonceTranscriptRoot [32]byte
Signature []byte
}
// ZAggregate is a tree-aggregation node: z-sums + bitmaps + proof roots only.
type ZAggregate struct {
// Aggregate is a tree-aggregation node: z-sums + bitmaps + proof roots only.
type Aggregate struct {
SessionID [32]byte
NonceID [32]byte
SignerBitmap []byte
@@ -62,7 +62,7 @@ var (
// nonce cert. Any mutation changes the root, so a QC over the old root no
// longer matches — the cert is tamper-evident. (Full w is NOT bound; it is
// never available to public verifiers.)
func nonceCertPayloadRoot(cert *BoundaryNonceCert) [32]byte {
func nonceCertPayloadRoot(cert *NonceCert) [32]byte {
h := sha3.NewShake256()
_, _ = h.Write([]byte("PULSAR-BCC-CEF/nonce-cert/v1"))
_, _ = h.Write(cert.NonceID[:])
@@ -76,18 +76,18 @@ func nonceCertPayloadRoot(cert *BoundaryNonceCert) [32]byte {
_, _ = h.Write(u[:4])
_, _ = h.Write(cert.CommitRoot[:])
_, _ = h.Write(cert.RegionRoot[:])
_, _ = h.Write(cert.MPCTranscriptRoot[:])
_, _ = h.Write(cert.NonceTranscriptRoot[:])
var out [32]byte
_, _ = h.Read(out[:])
return out
}
// VerifyBoundaryNonceCert performs the structural consensus check: the
// VerifyNonceCert performs the structural consensus check: the
// clearance QC is present, binds the cert payload, meets quorum, and selects
// only validators. The per-validator QC signatures are verified by the
// consensus layer's registered validator-set verifier (out of this module's
// structural scope). Without a valid QC there is no signing — fail closed.
func VerifyBoundaryNonceCert(cert *BoundaryNonceCert, quorum, validatorSetSize int) error {
func VerifyNonceCert(cert *NonceCert, quorum, validatorSetSize int) error {
if cert.ClearanceQC.IsEmpty() {
return ErrMissingClearanceQC
}
@@ -107,9 +107,9 @@ func VerifyBoundaryNonceCert(cert *BoundaryNonceCert, quorum, validatorSetSize i
// ---- NonceMPC transcript + voting (debug-oracle compute path) ----
// NonceMPCTranscript models the validator NonceMPC output. debugFullW is
// NonceTranscript models the validator NonceMPC output. debugFullW is
// TEST-ONLY and never enters the public view or the transcript root.
type NonceMPCTranscript struct {
type NonceTranscript struct {
debugFullW polyVec // DEBUG ONLY — never serialized, never bound
Epoch uint64
CommitteeID [32]byte
@@ -122,7 +122,7 @@ type NonceMPCTranscript struct {
}
// Root binds only the public outputs (w1, clear, margin, roots) — never w.
func (tr *NonceMPCTranscript) Root() [32]byte {
func (tr *NonceTranscript) Root() [32]byte {
h := sha3.NewShake256()
_, _ = h.Write([]byte("PULSAR-BCC-CEF/nonce-mpc/v1"))
_, _ = h.Write(tr.NonceID[:])
@@ -144,7 +144,7 @@ func (tr *NonceMPCTranscript) Root() [32]byte {
// PublicView returns only the public outputs (w1 + clear + root). It NEVER
// contains full w or its low bits.
func (tr *NonceMPCTranscript) PublicView() []byte {
func (tr *NonceTranscript) PublicView() []byte {
out := append([]byte{}, tr.W1...)
if tr.Clear {
out = append(out, 1)
@@ -160,22 +160,22 @@ func (tr *NonceMPCTranscript) PublicView() []byte {
// validator MPC would, producing a cert with a quorum-signed bound payload.
// The public view never reveals w. (A production NonceMPC replaces the direct
// w with secret-shared MPC; the public API is identical.)
func RunNonceMPCDebug(w polyVec, mode Mode, nonceID [32]byte) (*BoundaryNonceCert, *NonceMPCTranscript) {
func RunNonceMPCDebug(w polyVec, mode Mode, nonceID [32]byte) (*NonceCert, *NonceTranscript) {
gamma2, beta, _, _ := bccParams(mode)
tr := &NonceMPCTranscript{
tr := &NonceTranscript{
debugFullW: w,
NonceID: nonceID,
W1: packPolyVec(highBitsVec(w, gamma2)),
Margin: 2 * beta,
Clear: BoundaryClear(w, gamma2, beta),
}
cert := &BoundaryNonceCert{
cert := &NonceCert{
NonceID: nonceID,
W1: tr.W1,
Margin: tr.Margin,
CommitRoot: tr.CommitRoot,
RegionRoot: tr.RegionRoot,
MPCTranscriptRoot: tr.Root(),
NonceTranscriptRoot: tr.Root(),
}
payload := nonceCertPayloadRoot(cert)
cert.ClearanceQC = QuorumCert{
@@ -189,14 +189,14 @@ func RunNonceMPCDebug(w polyVec, mode Mode, nonceID [32]byte) (*BoundaryNonceCer
// ValidateAndVoteNonceCert is the validator voting rule: refuse to vote unless
// the transcript outputs ONLY w1 + a clear bit (never full w) and the nonce is
// boundary-clear.
func ValidateAndVoteNonceCert(tr *NonceMPCTranscript) (*NonceCertVote, error) {
func ValidateAndVoteNonceCert(tr *NonceTranscript) (*NonceVote, error) {
if len(tr.W1) == 0 {
return nil, ErrBadNonceMPCOutput
}
if !tr.Clear {
return nil, ErrNonceNotBoundaryClear
}
return &NonceCertVote{
return &NonceVote{
Epoch: tr.Epoch,
CommitteeID: tr.CommitteeID,
NonceID: tr.NonceID,
@@ -204,7 +204,7 @@ func ValidateAndVoteNonceCert(tr *NonceMPCTranscript) (*NonceCertVote, error) {
Margin: tr.Margin,
CommitRoot: tr.CommitRoot,
RegionRoot: tr.RegionRoot,
MPCTranscriptRoot: tr.Root(),
NonceTranscriptRoot: tr.Root(),
}, nil
}
@@ -26,54 +26,54 @@ func boundaryClearW(rng *rand.Rand, k int, gamma2, beta uint32) polyVec {
}
// The production cert carries w1 + clearance QC, never full w.
func TestBoundaryNonceCertHasNoFullW(t *testing.T) {
typ := reflect.TypeOf(BoundaryNonceCert{})
func TestNonceCertHasNoFullW(t *testing.T) {
typ := reflect.TypeOf(NonceCert{})
for _, f := range []string{"W", "FullW", "WPolyVec", "LowBitsW", "BoundaryDistance", "R0", "CS2", "CT0", "D2", "D0"} {
if hasFieldNamed(typ, f) {
t.Fatalf("BoundaryNonceCert contains forbidden field %q", f)
t.Fatalf("NonceCert contains forbidden field %q", f)
}
}
for _, f := range []string{"W1", "CommitRoot", "RegionRoot", "MPCTranscriptRoot", "ClearanceQC"} {
for _, f := range []string{"W1", "CommitRoot", "RegionRoot", "NonceTranscriptRoot", "ClearanceQC"} {
if !hasFieldNamed(typ, f) {
t.Fatalf("BoundaryNonceCert missing required field %q", f)
t.Fatalf("NonceCert missing required field %q", f)
}
}
}
func TestBoundaryNonceCertRequiresClearanceQC(t *testing.T) {
func TestNonceCertRequiresClearanceQC(t *testing.T) {
gamma2, beta, _, _ := bccParams(ModeP65)
k, _, _ := modeShape(ModeP65)
w := boundaryClearW(rand.New(rand.NewSource(11)), k, gamma2, beta)
cert, _ := RunNonceMPCDebug(w, ModeP65, [32]byte{1})
cert.ClearanceQC = QuorumCert{} // strip the QC
if err := VerifyBoundaryNonceCert(cert, 5, 8); err != ErrMissingClearanceQC {
if err := VerifyNonceCert(cert, 5, 8); err != ErrMissingClearanceQC {
t.Fatalf("expected missing-QC, got %v", err)
}
}
func TestBoundaryNonceCertBindsAllConsensusFields(t *testing.T) {
func TestNonceCertBindsAllConsensusFields(t *testing.T) {
gamma2, beta, _, _ := bccParams(ModeP65)
k, _, _ := modeShape(ModeP65)
w := boundaryClearW(rand.New(rand.NewSource(12)), k, gamma2, beta)
base, _ := RunNonceMPCDebug(w, ModeP65, [32]byte{2})
if err := VerifyBoundaryNonceCert(base, 5, 8); err != nil {
if err := VerifyNonceCert(base, 5, 8); err != nil {
t.Fatalf("base cert should verify, got %v", err)
}
mutations := []func(*BoundaryNonceCert){
func(c *BoundaryNonceCert) { c.PKEpoch++ },
func(c *BoundaryNonceCert) { c.CommitteeID[0] ^= 1 },
func(c *BoundaryNonceCert) { c.SignerSetRoot[0] ^= 1 },
func(c *BoundaryNonceCert) { c.CommitRoot[0] ^= 1 },
func(c *BoundaryNonceCert) { c.RegionRoot[0] ^= 1 },
func(c *BoundaryNonceCert) { c.MPCTranscriptRoot[0] ^= 1 },
func(c *BoundaryNonceCert) { c.Margin++ },
func(c *BoundaryNonceCert) { c.W1[0] ^= 1 },
mutations := []func(*NonceCert){
func(c *NonceCert) { c.PKEpoch++ },
func(c *NonceCert) { c.CommitteeID[0] ^= 1 },
func(c *NonceCert) { c.SignerSetRoot[0] ^= 1 },
func(c *NonceCert) { c.CommitRoot[0] ^= 1 },
func(c *NonceCert) { c.RegionRoot[0] ^= 1 },
func(c *NonceCert) { c.NonceTranscriptRoot[0] ^= 1 },
func(c *NonceCert) { c.Margin++ },
func(c *NonceCert) { c.W1[0] ^= 1 },
}
for i, mut := range mutations {
bad := *base
bad.W1 = append([]byte{}, base.W1...)
mut(&bad)
if err := VerifyBoundaryNonceCert(&bad, 5, 8); err == nil {
if err := VerifyNonceCert(&bad, 5, 8); err == nil {
t.Fatalf("mutation %d not detected (QC must bind all fields)", i)
}
}
@@ -142,18 +142,18 @@ func TestNonceCertQCVerifies(t *testing.T) {
k, _, _ := modeShape(ModeP65)
w := boundaryClearW(rand.New(rand.NewSource(16)), k, gamma2, beta)
cert, _ := RunNonceMPCDebug(w, ModeP65, [32]byte{7})
if err := VerifyBoundaryNonceCert(cert, 5, 8); err != nil {
if err := VerifyNonceCert(cert, 5, 8); err != nil {
t.Fatalf("valid NonceMPC cert should verify, got %v", err)
}
}
func TestBadNonceMPCTranscriptRootRejected(t *testing.T) {
func TestBadNonceTranscriptRootRejected(t *testing.T) {
gamma2, beta, _, _ := bccParams(ModeP65)
k, _, _ := modeShape(ModeP65)
w := boundaryClearW(rand.New(rand.NewSource(17)), k, gamma2, beta)
cert, _ := RunNonceMPCDebug(w, ModeP65, [32]byte{8})
cert.MPCTranscriptRoot[0] ^= 1 // tamper → payload root no longer matches the QC
if err := VerifyBoundaryNonceCert(cert, 5, 8); err != ErrBadClearanceQC {
cert.NonceTranscriptRoot[0] ^= 1 // tamper → payload root no longer matches the QC
if err := VerifyNonceCert(cert, 5, 8); err != ErrBadClearanceQC {
t.Fatalf("tampered transcript root must be rejected, got %v", err)
}
}
+49
View File
@@ -0,0 +1,49 @@
package pulsar
import (
"bytes"
"errors"
)
// Partial-z correctness (PULSAR-V13-PARTIAL-Z-PROOF). Each z-share proves
// z_i = λ_i·y_i + c·λ_i·s1_i bound to (session, nonce, party, challenge, DKG
// share commitment, nonce commitment) WITHOUT revealing y_i or s1_i. The
// public bindings below are enforced unconditionally; the zero-knowledge
// correctness check is delegated to the registered (fail-closed)
// PartialZVerifier. Do not tree-aggregate until a sound verifier is registered.
type ZPartialPublicInput struct {
PartyID uint32
Lambda []byte
Challenge []byte
SessionID [32]byte
NonceID [32]byte
DKGCommitment []byte
NonceCommitment []byte
ZShare []byte
}
var (
ErrWrongParty = errors.New("pulsar: z-partial party mismatch")
ErrWrongSession = errors.New("pulsar: z-partial session mismatch")
ErrWrongNonce = errors.New("pulsar: z-partial nonce mismatch")
ErrBadZPartialProof = errors.New("pulsar: z-partial correctness proof failed")
)
// VerifyZPartial enforces the public bindings, then delegates the ZK
// correctness check to the registered PartialZVerifier (fail-closed default).
func VerifyZPartial(p *Partial, in ZPartialPublicInput) error {
if p.PartyID != in.PartyID {
return ErrWrongParty
}
if p.SessionID != in.SessionID {
return ErrWrongSession
}
if p.NonceID != in.NonceID {
return ErrWrongNonce
}
if !bytes.Equal(p.ZShare, in.ZShare) {
return ErrBadZPartialProof
}
return registeredPartialZVerifier.VerifyPartial(p, in.Challenge, in.DKGCommitment, in.NonceCommitment)
}
+115
View File
@@ -0,0 +1,115 @@
package pulsar
import (
"bytes"
"math/rand"
"testing"
)
type acceptAllPartialZ struct{}
func (acceptAllPartialZ) VerifyPartial(*Partial, []byte, []byte, []byte) error { return nil }
func validZPartial() (*Partial, ZPartialPublicInput) {
z := []byte{1, 2, 3, 4}
p := &Partial{PartyID: 3, SessionID: [32]byte{9}, NonceID: [32]byte{7}, ZShare: z}
in := ZPartialPublicInput{PartyID: 3, SessionID: [32]byte{9}, NonceID: [32]byte{7},
Challenge: []byte{0xc}, DKGCommitment: []byte{0xd}, NonceCommitment: []byte{0xe}, ZShare: z}
return p, in
}
func TestValidZPartialProofAccepted(t *testing.T) {
old := registeredPartialZVerifier
RegisterPartialZVerifier(acceptAllPartialZ{})
defer func() { registeredPartialZVerifier = old }()
p, in := validZPartial()
if err := VerifyZPartial(p, in); err != nil {
t.Fatalf("valid partial rejected: %v", err)
}
}
func TestBadZShareRejected(t *testing.T) {
old := registeredPartialZVerifier
RegisterPartialZVerifier(acceptAllPartialZ{})
defer func() { registeredPartialZVerifier = old }()
p, in := validZPartial()
p.ZShare = []byte{9, 9, 9, 9} // corrupted
if err := VerifyZPartial(p, in); err != ErrBadZPartialProof {
t.Fatalf("corrupt z-share must be rejected, got %v", err)
}
}
func TestZPartialBindsSessionNoncePartyChallenge(t *testing.T) {
old := registeredPartialZVerifier
RegisterPartialZVerifier(acceptAllPartialZ{})
defer func() { registeredPartialZVerifier = old }()
for i, mut := range []func(*ZPartialPublicInput){
func(in *ZPartialPublicInput) { in.SessionID[0] ^= 1 },
func(in *ZPartialPublicInput) { in.NonceID[0] ^= 1 },
func(in *ZPartialPublicInput) { in.PartyID++ },
} {
p, in := validZPartial()
mut(&in)
if err := VerifyZPartial(p, in); err == nil {
t.Fatalf("binding mutation %d not detected", i)
}
}
}
func TestPartialZFailClosedByDefault(t *testing.T) {
p, in := validZPartial()
if err := VerifyZPartial(p, in); err != ErrPartialZProofUnsound {
t.Fatalf("default partial-z verifier must fail closed, got %v", err)
}
}
func aggWith(sid, nid [32]byte, bitmap []byte, z polyVec) Aggregate {
return Aggregate{SessionID: sid, NonceID: nid, SignerBitmap: bitmap, ZSum: packPolyVec(z)}
}
func TestMergeAggregatesDuplicateAndSession(t *testing.T) {
_, l, _ := modeShape(ModeP65)
rng := rand.New(rand.NewSource(21))
sid, nid := [32]byte{1}, [32]byte{2}
a := aggWith(sid, nid, []byte{0b0001}, randPolyVec(rng, l))
b := aggWith(sid, nid, []byte{0b0010}, randPolyVec(rng, l))
if _, err := MergeAggregates([]Aggregate{a, b}, l); err != nil {
t.Fatalf("disjoint merge should succeed: %v", err)
}
dup := aggWith(sid, nid, []byte{0b0001}, randPolyVec(rng, l)) // overlaps a
if _, err := MergeAggregates([]Aggregate{a, dup}, l); err != ErrDuplicateSigner {
t.Fatalf("duplicate signer must be rejected, got %v", err)
}
wrong := aggWith([32]byte{9}, nid, []byte{0b0100}, randPolyVec(rng, l))
if _, err := MergeAggregates([]Aggregate{a, wrong}, l); err != ErrWrongSessionAgg {
t.Fatalf("wrong session must be rejected, got %v", err)
}
}
func TestMergeAggregatesSumsZ(t *testing.T) {
_, l, _ := modeShape(ModeP65)
rng := rand.New(rand.NewSource(22))
sid, nid := [32]byte{3}, [32]byte{4}
z1, z2 := randPolyVec(rng, l), randPolyVec(rng, l)
merged, err := MergeAggregates([]Aggregate{
aggWith(sid, nid, []byte{0b0001}, z1),
aggWith(sid, nid, []byte{0b0010}, z2),
}, l)
if err != nil {
t.Fatal(err)
}
want := FlatAggregateZ([]polyVec{z1, z2}, l)
if !bytes.Equal(merged.ZSum, packPolyVec(want)) {
t.Fatal("merged ZSum != flat sum")
}
}
func TestNoT0InProductionDKGTypes(t *testing.T) {
for _, typ := range productionDKGTypes() {
for _, f := range []string{"T0", "T0Share", "S2", "S2Share", "S2Correction", "FullT", "MasterSecret", "S1Master", "S2Master", "T0Master"} {
if hasFieldNamed(typ, f) {
t.Fatalf("%s has forbidden DKG field %q", typ.Name(), f)
}
}
}
}
@@ -40,24 +40,24 @@ var (
// ClearanceVerifier verifies a boundary-clearance proof on a nonce cert
// without learning the hidden w.
type ClearanceVerifier interface {
VerifyClearance(cert *BoundaryNonceCert) error
VerifyClearance(cert *NonceCert) error
}
// PartialZVerifier verifies a z-partial's correctness proof without learning
// y_i or s1_i.
type PartialZVerifier interface {
VerifyPartial(p *BCCZPartial, challenge []byte, dkgShareCommit, nonceCommit []byte) error
VerifyPartial(p *Partial, challenge []byte, dkgShareCommit, nonceCommit []byte) error
}
// failClosedClearance is the default: no sound proof exists yet.
type failClosedClearance struct{}
func (failClosedClearance) VerifyClearance(*BoundaryNonceCert) error { return ErrClearanceProofUnsound }
func (failClosedClearance) VerifyClearance(*NonceCert) error { return ErrClearanceProofUnsound }
// failClosedPartialZ is the default: no sound proof exists yet.
type failClosedPartialZ struct{}
func (failClosedPartialZ) VerifyPartial(*BCCZPartial, []byte, []byte, []byte) error {
func (failClosedPartialZ) VerifyPartial(*Partial, []byte, []byte, []byte) error {
return ErrPartialZProofUnsound
}
@@ -9,10 +9,10 @@ func TestProductionBCCSigningDisabledUntilSoundZK(t *testing.T) {
if ProductionBCCSigningReady() {
t.Fatal("production BCC signing must be disabled by default (no sound ZK proofs)")
}
if err := registeredClearanceVerifier.VerifyClearance(&BoundaryNonceCert{}); err != ErrClearanceProofUnsound {
if err := registeredClearanceVerifier.VerifyClearance(&NonceCert{}); err != ErrClearanceProofUnsound {
t.Fatalf("clearance verifier must fail closed, got %v", err)
}
if err := registeredPartialZVerifier.VerifyPartial(&BCCZPartial{}, nil, nil, nil); err != ErrPartialZProofUnsound {
if err := registeredPartialZVerifier.VerifyPartial(&Partial{}, nil, nil, nil); err != ErrPartialZProofUnsound {
t.Fatalf("partial-z verifier must fail closed, got %v", err)
}
}