pulsar: origin-auth safe-by-default — fail-closed on nil verifier (RED MEDIUM nit #1)

AggregateBCC/AggregateBCCWithBlame previously aggregated WITHOUT origin
authentication when auth==nil (SetIdentity opt-in), so a production caller that
forgot to wire the verifier silently reverted to the exclude-honest-victim
footgun. Now:

  - auth == nil  => ErrOriginAuthRequired (FAIL-CLOSED), matching the nonce
    ledger's no-fail-open posture.
  - the unauthenticated path is reachable ONLY via the EXPLICIT opt-out sentinel
    UnauthenticatedAggregation (trusted-channel / test). The sentinel panics if
    ever invoked as a real verifier (fail loud, never silent).
  - DistributedBCCSigner.FinalizeWithBlame forwards d.idVerify, so a signer with
    no SetIdentity refuses FAIL-CLOSED too; opt out with
    SetIdentity(nil, UnauthenticatedAggregation).

Test (origin_auth_gate_test.go): the DEFAULT (nil verifier) free-fn AND signer
paths refuse with ErrOriginAuthRequired; the explicit opt-out and a real verifier
both aggregate to a FIPS-valid signature; the sentinel is not a usable verifier.
Trusted in-memory ceremonies (runBCCCeremony, talus, sub-quorum, sound-proof,
no-reconstruct behavioural) opt out explicitly. GATE B / RED MEDIUM flip /
capstone / no-reconstruct all stay green.
This commit is contained in:
zeekay
2026-06-27 23:34:20 -07:00
parent 31d3501d84
commit e010980b70
6 changed files with 264 additions and 25 deletions
+8 -6
View File
@@ -322,12 +322,14 @@ func TestRED_PoC_MEDIUM_CannotFrameOrExcludeHonestVictim(t *testing.T) {
t.Fatalf("RED MEDIUM (impersonation): honest victim blamed off a forged victim-authored partial")
}
// (3) CONTRAST — the verifier is load-bearing. With NO identity verifier the
// same front-run EXCLUDES the victim (the forged malformed partial occupies
// the slot; the real one is dropped as a duplicate) -> t-1 valid ->
// ErrInsufficientSigners. No blame is emitted either way (blame is gated on
// auth), but exclusion-resistance REQUIRES the verifier.
_, _, blamesNoAuth, errNoAuth := AggregateBCCWithBlame(f.params, f.setup, agg.evalPoints, quorum, 0, nil,
// (3) CONTRAST — the verifier is load-bearing. On the EXPLICIT unauthenticated
// opt-out (UnauthenticatedAggregation) the same front-run EXCLUDES the
// victim (the forged malformed partial occupies the slot; the real one is
// dropped as a duplicate) -> t-1 valid -> ErrInsufficientSigners. No blame
// is emitted either way (blame is gated on auth), but exclusion-resistance
// REQUIRES a real verifier. (A bare nil verifier is refused FAIL-CLOSED —
// see TestGATE_OriginAuth_*; here we deliberately drive the opt-out path.)
_, _, blamesNoAuth, errNoAuth := AggregateBCCWithBlame(f.params, f.setup, agg.evalPoints, quorum, 0, UnauthenticatedAggregation,
nil, msg, agg.c, &agg.cHat, agg.w1, sid, partials[victim].NonceID, threshold, input)
if !errors.Is(errNoAuth, ErrInsufficientSigners) {
t.Fatalf("contrast: expected the un-authenticated path to drop to t-1 (front-run exclusion), got %v", errNoAuth)
+76 -19
View File
@@ -494,8 +494,14 @@ type IdentitySigner interface {
// partial to its true producer; verifier authenticates OTHER nodes' partials
// when this node aggregates, so a forged partial stamped with a victim's PartyID
// is dropped (never blamed/excluded). Both must be set for authenticated blame;
// production wires the validator's identity key + the validator-set verifier
// here. Returns the signer for chaining.
// production wires the validator's identity key + the validator-set verifier here.
//
// SAFE BY DEFAULT (RED MEDIUM). A signer that NEVER calls SetIdentity has
// idVerify == nil, and FinalizeWithBlame then refuses to aggregate FAIL-CLOSED
// (ErrOriginAuthRequired) — a forgotten verifier can no longer silently revert to
// the unauthenticated exclude-honest-victim footgun. A trusted-channel ceremony
// that deliberately wants the unauthenticated path opts OUT explicitly with
// SetIdentity(nil, UnauthenticatedAggregation). Returns the signer for chaining.
func (d *DistributedBCCSigner) SetIdentity(signer IdentitySigner, verifier AbortSignatureVerifier) *DistributedBCCSigner {
d.idSigner = signer
d.idVerify = verifier
@@ -698,6 +704,10 @@ func (d *DistributedBCCSigner) Finalize(r1 SignRound1, partials []Partial) (Aggr
// empty for the caller's identity layer (TranscriptForComplaint gives the
// to-be-signed bytes). A VALID-sigma WRONG-z is NOT attributed here (BDLOP
// residual — share_commit.go); it remains a liveness fault, never a forgery.
//
// ORIGIN-AUTH SAFE-BY-DEFAULT (RED MEDIUM): it forwards this signer's idVerify,
// so a signer with no SetIdentity (idVerify == nil) is refused FAIL-CLOSED with
// ErrOriginAuthRequired (see AggregateBCCWithBlame / SetIdentity).
func (d *DistributedBCCSigner) FinalizeWithBlame(r1 SignRound1, partials []Partial) (Aggregate, ConsensusCert, []AbortEvidence, error) {
if !d.IsAggregator() {
return Aggregate{}, ConsensusCert{}, nil, ErrAlgNotAggregator
@@ -793,6 +803,38 @@ func authenticatePartial(p Partial, expectedAuthor NodeID, epoch uint64, sid, no
return VerifySignedProtocolMessage(&m, v)
}
// ErrOriginAuthRequired is returned by AggregateBCC / AggregateBCCWithBlame when
// no origin-authentication verifier is wired (auth == nil). It is the safe-by-
// default refusal (RED MEDIUM): a caller that simply FORGOT to wire the verifier
// must NOT silently fall back to unauthenticated aggregation (the exclude-honest-
// victim footgun) — it fails CLOSED, matching the nonce ledger's no-fail-open
// posture. A caller that genuinely wants the unauthenticated path opts OUT
// explicitly with UnauthenticatedAggregation.
var ErrOriginAuthRequired = errors.New("pulsar: aggregation requires an origin-authentication verifier — pass an AbortSignatureVerifier (DistributedBCCSigner.SetIdentity), or UnauthenticatedAggregation to explicitly opt out (trusted-channel / test only)")
// unauthenticatedAggregation is the concrete type of the UnauthenticatedAggregation
// opt-out sentinel. It is NEVER invoked as a verifier: AggregateBCCWithBlame
// branches on identity equality (auth == UnauthenticatedAggregation) and takes
// the no-origin-auth path WITHOUT calling any method on it. Its method therefore
// PANICS — reaching it means the sentinel was mistaken for a real verifier, and a
// security primitive must fail LOUD rather than silently accept or drop partials.
// The type is unexported, so the ONLY way to obtain it is the exported var below.
type unauthenticatedAggregation struct{}
func (unauthenticatedAggregation) VerifyAbortSignature(NodeID, []byte, []byte) bool {
panic("pulsar: UnauthenticatedAggregation is an opt-out sentinel, not a usable verifier")
}
// UnauthenticatedAggregation is the EXPLICIT opt-OUT for the AggregateBCC /
// AggregateBCCWithBlame origin-authentication gate. Pass it as auth to
// ACKNOWLEDGE that partials are aggregated WITHOUT origin authentication — no
// blame is produced and a forged partial stamped with a victim's slot cannot be
// distinguished from the victim's own. It exists ONLY for trusted-channel test
// ceremonies and the structural no-reconstruct proofs; PRODUCTION wires a real
// AbortSignatureVerifier (via DistributedBCCSigner.SetIdentity). A bare nil auth
// is refused FAIL-CLOSED (ErrOriginAuthRequired) — opting out must be deliberate.
var UnauthenticatedAggregation AbortSignatureVerifier = unauthenticatedAggregation{}
// AggregateBCC is the free-function aggregation surface: ANY process
// holding the public setup, the challenge c, the public target w1, and the
// collected z-partials can produce the FIPS 204 signature WITHOUT any
@@ -818,16 +860,30 @@ func AggregateBCC(params *Params, setup *AlgSetup, evalPoints []uint32, quorum [
// fewer than t DISTINCT signers. Malformed ZShares are rejected without
// panicking (unpackPolyVecChecked). It holds NO share, sk, or seed.
//
// ORIGIN AUTHENTICATION (RED MEDIUM). quorum maps PartyID → expected validator
// NodeID; auth is the identity-layer verifier; epoch binds the round. When auth
// is non-nil, a partial is ACCEPTED only if it carries a valid identity
// signature whose author IS quorum[PartyID] — a forged partial stamped with a
// victim's slot is dropped, so an attacker can neither blame nor front-run-
// exclude an honest victim. Blame is emitted ONLY for authenticated partials;
// it is NEVER produced off a raw unauthenticated PartyID (with auth == nil no
// blame is produced at all — attribution fails closed). The networked transport
// that authenticates DELIVERY remains a flagged residual.
// ORIGIN AUTHENTICATION (RED MEDIUM), SAFE BY DEFAULT. quorum maps PartyID →
// expected validator NodeID; auth is the identity-layer verifier; epoch binds the
// round. auth == nil is REFUSED FAIL-CLOSED (ErrOriginAuthRequired): a caller
// that forgot to wire the verifier must NOT silently revert to unauthenticated
// aggregation. With a REAL verifier a partial is ACCEPTED only if it carries a
// valid identity signature whose author IS quorum[PartyID] — a forged partial
// stamped with a victim's slot is dropped, so an attacker can neither blame nor
// front-run-exclude an honest victim, and blame is emitted ONLY for authenticated
// partials. The unauthenticated path (no origin check, no blame) is reachable
// ONLY by the EXPLICIT opt-out auth == UnauthenticatedAggregation (trusted-channel
// / test). The networked transport that authenticates DELIVERY remains a flagged
// residual.
func AggregateBCCWithBlame(params *Params, setup *AlgSetup, evalPoints []uint32, quorum []NodeID, epoch uint64, auth AbortSignatureVerifier, ctx, msg []byte, c poly, cHat *poly, w1 polyVec, sid, nonceID [32]byte, threshold int, partials []Partial) (Aggregate, ConsensusCert, []PartialBlame, error) {
// ORIGIN-AUTH SAFE-BY-DEFAULT (RED MEDIUM). A nil verifier is a caller that
// FORGOT to wire origin authentication — refuse FAIL-CLOSED rather than
// silently revert to the unauthenticated exclude-honest-victim footgun (the
// same no-fail-open posture the nonce ledger has). A caller that genuinely
// wants the unauthenticated path (trusted-channel test ceremony / structural
// no-reconstruct proof) opts OUT explicitly with UnauthenticatedAggregation.
if auth == nil {
return Aggregate{}, ConsensusCert{}, nil, ErrOriginAuthRequired
}
authenticated := auth != UnauthenticatedAggregation
gamma2, beta, omega, ok := bccParams(params.Mode)
if !ok {
return Aggregate{}, ConsensusCert{}, nil, ErrBCCParamSet
@@ -844,9 +900,10 @@ func AggregateBCCWithBlame(params *Params, setup *AlgSetup, evalPoints []uint32,
// unauthenticated / wrong-slot / forged partial HERE, with NO blame against
// the slot's honest owner, BEFORE the first-per-PartyID and duplicate logic
// (so a forgery cannot occupy the victim's slot or evict the victim's real
// partial). With auth == nil the identity layer is not wired: the round still
// aggregates sigma-checked partials but produces NO blame (see below).
if auth != nil {
// partial). On the explicit UnauthenticatedAggregation opt-out the identity
// layer is intentionally not wired: the round still aggregates sigma-checked
// partials but produces NO blame (see below).
if authenticated {
authed := make([]Partial, 0, len(partials))
for i := range partials {
p := partials[i]
@@ -870,11 +927,11 @@ func AggregateBCCWithBlame(params *Params, setup *AlgSetup, evalPoints []uint32,
seen := make(map[uint32]bool, len(partials))
var blames []PartialBlame
blame := func(party uint32, reason BlameReason) {
// Never emit blame off an unauthenticated PartyID (RED MEDIUM): with no
// identity verifier wired, attribution is not sound, so produce none.
// With auth != nil only authenticated partials reach here, so the PartyID
// is the genuine slot owner and the blame is sound.
if auth == nil {
// Never emit blame off an unauthenticated PartyID (RED MEDIUM): on the
// UnauthenticatedAggregation opt-out attribution is not sound, so produce
// none. When authenticated, only origin-checked partials reach here, so the
// PartyID is the genuine slot owner and the blame is sound.
if !authenticated {
return
}
blames = append(blames, PartialBlame{PartyID: party, Reason: reason, SessionID: sid, NonceID: nonceID})
+14
View File
@@ -158,6 +158,12 @@ func runBCCCeremony(t *testing.T, f *bccFixture, q int, sid [32]byte, ctx, msg [
if err != nil {
return nil, nil, err
}
// This helper exercises the no-reconstruct FIPS-validity math over a trusted
// in-memory bus; origin authentication is orthogonal and is exercised by the
// blame/capstone suite (bccRound wires a REAL identity set). Opt OUT of the
// origin-auth gate EXPLICITLY so aggregation is allowed (a bare nil verifier
// is now refused FAIL-CLOSED — ErrOriginAuthRequired).
nd.SetIdentity(nil, UnauthenticatedAggregation)
nodes[i] = nd
}
@@ -350,6 +356,10 @@ func TestDistributedBCC_SubQuorumCannotSign(t *testing.T) {
if err != nil {
t.Fatalf("signer %d: %v", i, err)
}
// Trusted in-memory bus: opt OUT of origin-auth explicitly (a bare nil
// verifier is now refused FAIL-CLOSED). The threshold bound under test is
// orthogonal to origin authentication.
nd.SetIdentity(nil, UnauthenticatedAggregation)
nodes[i] = nd
if err := nd.SetNonceShare(nonceID, deal.YShares[quorum[i]]); err != nil {
t.Fatalf("set nonce share %d: %v", i, err)
@@ -408,6 +418,10 @@ func TestDistributedBCC_SoundPartialZProofRejectsForgery(t *testing.T) {
if err != nil {
t.Fatalf("signer %d: %v", i, err)
}
// Trusted in-memory bus: opt OUT of origin-auth explicitly (a bare nil
// verifier is refused FAIL-CLOSED). This test probes the SOUND sigma proof
// catching a forged z, orthogonal to origin authentication.
nd.SetIdentity(nil, UnauthenticatedAggregation)
nodes[i] = nd
_ = nd.SetNonceShare(nonceID, deal.YShares[quorum[i]])
r1, _ := nd.Round1(sid, nonceID, deal.Cert)
@@ -244,6 +244,10 @@ func TestCommittee_NoReconstruct_Invariant_CombinerSeesNoSecret(t *testing.T) {
if err != nil {
t.Fatalf("signer %d: %v", i, err)
}
// Trusted in-memory bus: opt OUT of origin-auth explicitly (a bare nil
// verifier is refused FAIL-CLOSED). The no-reconstruct / threshold bound
// under test is orthogonal to origin authentication.
nd.SetIdentity(nil, UnauthenticatedAggregation)
nodes[i] = nd
if err := nd.SetNonceShare(nonceID, deal.YShares[quorum[i]]); err != nil {
t.Fatalf("set nonce share %d: %v", i, err)
+155
View File
@@ -0,0 +1,155 @@
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package pulsar
// origin_auth_gate_test.go — GATE (RED MEDIUM, post-merge nit #1): origin
// authentication is SAFE BY DEFAULT. The aggregation surface no longer silently
// aggregates unauthenticated partials when the verifier is missing — a caller
// that FORGOT to wire it is refused FAIL-CLOSED (ErrOriginAuthRequired), so it
// cannot revert to the exclude-honest-victim footgun. The unauthenticated path
// is reachable ONLY by the EXPLICIT UnauthenticatedAggregation opt-out (test /
// trusted-channel). This is the same no-fail-open posture the nonce ledger has.
//
// Two faces, one gate:
// - the free function AggregateBCC / AggregateBCCWithBlame, and
// - the DistributedBCCSigner.Finalize / FinalizeWithBlame entrypoint.
import (
"crypto/rand"
"errors"
"testing"
)
// GATE (free fn) — nil verifier REFUSES; explicit opt-out and a real verifier
// both aggregate to a FIPS-valid signature. Driven on real (bccRound-signed)
// partials over a boundary-clear nonce, so the success directions are
// deterministic.
func TestGATE_OriginAuth_FreeFn_DefaultRefuses(t *testing.T) {
const n, threshold = 5, 3
f := newBCCFixture(t, ModeP65, n, threshold)
var sid [32]byte
copy(sid[:], []byte("origin-auth-freefn"))
msg := []byte("safe-by-default origin authentication")
partials, agg, _, quorum, evalPoints := bccRound(t, f, msg, sid)
nonceID := partials[0].NonceID
// (1) DEFAULT (nil verifier) is REFUSED FAIL-CLOSED — both the blame surface
// and the back-compat wrapper.
if _, _, _, err := AggregateBCCWithBlame(f.params, f.setup, evalPoints, quorum, 0, nil,
nil, msg, agg.c, &agg.cHat, agg.w1, sid, nonceID, threshold, partials); !errors.Is(err, ErrOriginAuthRequired) {
t.Fatalf("GATE FAILED: nil-verifier AggregateBCCWithBlame returned %v, want ErrOriginAuthRequired (must fail closed)", err)
}
if _, _, err := AggregateBCC(f.params, f.setup, evalPoints, quorum, 0, nil,
nil, msg, agg.c, &agg.cHat, agg.w1, sid, nonceID, threshold, partials); !errors.Is(err, ErrOriginAuthRequired) {
t.Fatalf("GATE FAILED: nil-verifier AggregateBCC returned %v, want ErrOriginAuthRequired", err)
}
// (2) EXPLICIT opt-out (UnauthenticatedAggregation) aggregates and yields a
// FIPS-valid signature — the deliberate trusted-channel path.
_, certOptOut, _, err := AggregateBCCWithBlame(f.params, f.setup, evalPoints, quorum, 0, UnauthenticatedAggregation,
nil, msg, agg.c, &agg.cHat, agg.w1, sid, nonceID, threshold, partials)
if err != nil {
t.Fatalf("explicit UnauthenticatedAggregation opt-out must aggregate, got %v", err)
}
if !fipsVerify(t, f.setup, msg, &certOptOut.Signature) {
t.Fatalf("opt-out aggregate failed unmodified FIPS 204 verify")
}
// (3) A REAL verifier authenticates the (bccRound-signed) partials and yields a
// FIPS-valid signature too — the production path.
_, certAuth, _, err := AggregateBCCWithBlame(f.params, f.setup, evalPoints, quorum, 0, f.idset,
nil, msg, agg.c, &agg.cHat, agg.w1, sid, nonceID, threshold, partials)
if err != nil {
t.Fatalf("authenticated aggregation must succeed on signed partials, got %v", err)
}
if !fipsVerify(t, f.setup, msg, &certAuth.Signature) {
t.Fatalf("authenticated aggregate failed unmodified FIPS 204 verify")
}
t.Logf("GATE PASS (origin-auth safe-by-default, free fn): nil verifier => ErrOriginAuthRequired (fail-closed); UnauthenticatedAggregation opt-out AND a real verifier both aggregate to a FIPS-valid signature")
}
// GATE (signer) — a DistributedBCCSigner that never called SetIdentity (idVerify
// == nil, the forgot-to-wire path) refuses Finalize / FinalizeWithBlame
// FAIL-CLOSED; the explicit opt-out SetIdentity(nil, UnauthenticatedAggregation)
// is the only lever that lifts the refusal.
func TestGATE_OriginAuth_Signer_DefaultRefuses(t *testing.T) {
const n, threshold = 5, 3
f := newBCCFixture(t, ModeP65, n, threshold)
quorum, evalPoints, qshares := f.quorum(threshold)
var sid [32]byte
copy(sid[:], []byte("origin-auth-signer"))
msg := []byte("signer fails closed without identity")
var nonceID [32]byte
nonceID[0] = 0x4d
deal, err := DealNonceMPCDebug(f.setup, quorum, evalPoints, threshold, nonceID, rand.Reader)
if err != nil {
t.Fatalf("nonce deal: %v", err)
}
// Drive a full-threshold ceremony with NO SetIdentity (the forgot-to-wire path).
nodes := make([]*DistributedBCCSigner, threshold)
partials := make([]Partial, 0, threshold)
var aggR1 SignRound1
for i := 0; i < threshold; i++ {
nd, e := NewDistributedBCCSigner(f.params, f.setup, qshares[i], quorum, evalPoints, sid, nil, msg, rand.Reader)
if e != nil {
t.Fatalf("signer %d: %v", i, e)
}
nodes[i] = nd
if e := nd.SetNonceShare(nonceID, deal.YShares[quorum[i]]); e != nil {
t.Fatalf("set nonce share %d: %v", i, e)
}
r1, e := nd.Round1(sid, nonceID, deal.Cert)
if e != nil {
t.Fatalf("round1 %d: %v", i, e)
}
if nd.IsAggregator() {
aggR1 = r1
}
p, e := nd.Round2(r1, PartialInput{})
if e != nil {
t.Fatalf("round2 %d: %v", i, e)
}
partials = append(partials, p)
}
var agg *DistributedBCCSigner
for _, nd := range nodes {
if nd.IsAggregator() {
agg = nd
}
}
if agg == nil {
t.Fatal("no aggregator")
}
// DEFAULT (no SetIdentity): both entrypoints refuse FAIL-CLOSED.
if _, _, _, ferr := agg.FinalizeWithBlame(aggR1, partials); !errors.Is(ferr, ErrOriginAuthRequired) {
t.Fatalf("GATE FAILED: default signer FinalizeWithBlame returned %v, want ErrOriginAuthRequired", ferr)
}
if _, _, ferr := agg.Finalize(aggR1, partials); !errors.Is(ferr, ErrOriginAuthRequired) {
t.Fatalf("GATE FAILED: default signer Finalize returned %v, want ErrOriginAuthRequired", ferr)
}
// EXPLICIT opt-out lifts the refusal (the auth gate no longer fires). The FIPS
// hint outcome is orthogonal here; assert only that the origin-auth refusal is
// gone — the free-fn gate above proves the opt-out actually produces a valid sig.
agg.SetIdentity(nil, UnauthenticatedAggregation)
if _, _, _, ferr := agg.FinalizeWithBlame(aggR1, partials); errors.Is(ferr, ErrOriginAuthRequired) {
t.Fatalf("GATE FAILED: explicit UnauthenticatedAggregation opt-out still refused with ErrOriginAuthRequired")
}
t.Logf("GATE PASS (origin-auth safe-by-default, signer): a signer with no SetIdentity refuses Finalize/FinalizeWithBlame FAIL-CLOSED (ErrOriginAuthRequired); SetIdentity(nil, UnauthenticatedAggregation) is the explicit lever that lifts it")
}
// Sentinel hygiene — the UnauthenticatedAggregation opt-out must NEVER be used as
// a real verifier (it has no key material); if mistaken for one it must fail LOUD
// (panic), never silently accept/drop. The gate branches on identity before any
// method call, so this path is unreachable in correct flow — assert it stays loud.
func TestGATE_OriginAuth_SentinelIsNotAVerifier(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatalf("GATE FAILED: UnauthenticatedAggregation.VerifyAbortSignature did not panic — a sentinel must not silently act as a verifier")
}
}()
_ = UnauthenticatedAggregation.VerifyAbortSignature(NodeID{}, nil, nil)
}
+7
View File
@@ -431,6 +431,10 @@ func runTalusMPCCeremony(t *testing.T, f *bccFixture, q int, sid [32]byte, ctx,
if err != nil {
return nil, err
}
// Trusted in-memory bus: opt OUT of origin-auth explicitly (a bare nil
// verifier is refused FAIL-CLOSED). TALUS leak-free CSCP is orthogonal
// to origin authentication.
nd.SetIdentity(nil, UnauthenticatedAggregation)
if err := nd.SetNonceShare(nonceID, yShares[quorum[i]]); err != nil {
return nil, err
}
@@ -566,6 +570,9 @@ func TestTalus_MPC_SubQuorumCannotSign(t *testing.T) {
var aggR1 SignRound1
for i := 0; i < threshold; i++ {
nd, _ := NewDistributedBCCSigner(f.params, f.setup, qshares[i], quorum, evalPoints, sid, nil, msg, rand.Reader)
// Trusted in-memory bus: opt OUT of origin-auth explicitly (a bare nil
// verifier is refused FAIL-CLOSED); the threshold bound is orthogonal.
nd.SetIdentity(nil, UnauthenticatedAggregation)
_ = nd.SetNonceShare(nonceID, yShares[quorum[i]])
r1, _ := nd.Round1(sid, nonceID, *cert)
if nd.IsAggregator() {