Skip to content

feat(frost): conclude the signing done check on the t-subset (7.3 t-of-included PR3/3)#4094

Merged
mswilkison merged 1 commit into
feat/frost-schnorr-migration-scaffoldfrom
feat/frost-7.3-done-check-threshold
Jun 19, 2026
Merged

feat(frost): conclude the signing done check on the t-subset (7.3 t-of-included PR3/3)#4094
mswilkison merged 1 commit into
feat/frost-schnorr-migration-scaffoldfrom
feat/frost-7.3-done-check-threshold

Conversation

@mswilkison

@mswilkison mswilkison commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

What

Completes RFC-21 Phase 7.3 t-of-included finalize. With PR2 (#4093) an interactive signing attempt finalizes over the first t responsive committers of an (optionally) oversized included set. The non-subset and offline members never broadcast a signing done check, so the outer signingDoneCheck — which required a confirmation from every attempt member — would hang an otherwise-successful attempt to its timeout and force a needless retry.

This concludes the done check on a deterministic threshold quorum, keeping the legacy path byte-for-byte unchanged.

Design (locked via a Codex + Gemini consult)

My first cut completed on "the first t arrivals," which is network-order-dependent: the resulting latestEndBlock (a per-node max over a network-order-dependent subset) differs across honest nodes, and that value feeds batch scheduling (signBatch: signingStartBlock = prev endBlock + interlude) → the batch desyncs. The consult locked this design:

  • Non-oversized (included == honestThreshold; today's selector output and the whole coarse path): the legacy all-members rule is UNCHANGED — wait for every attempt member, require all signatures equal, return max(endBlock).
  • Oversized (included > honestThreshold): bucket done checks by signature, conclude once a bucket holds >= honestThreshold distinct senders (the minimum that proves a valid threshold signature). Minority buckets (divergent / adversarial signatures) are ignored, never fatal — one bad done message can't fracture the group. The end block is the deterministic attemptTimeoutBlock (every honest node computes it identically), not a max over done messages.

With honest majority (t > groupSize/2) at most one signature bucket can reach t — even under coordinator equivocation (two disjoint t-subsets can't coexist when 2t > n) — so the quorum is unique. That means no body-hash / proto change is needed (bucket by the existing signature field), and no signBatch change (the oversized path feeds attemptTimeoutBlock through the existing return). The >1-quorum branch is unreachable and intentionally non-fatal (no noisy post-success failure).

Inert until oversizing

included == honestThreshold today, so the legacy branch is taken and behavior is identical to before; the quorum path only activates once selection oversizes the set (MacLane's policy knob).

Tests (signing_done_test.go)

  • Legacy TestSigningDoneCheck{,_MissingConfirmation,_AnotherSignature} — unchanged, still green (they use an attempt-member set of size honestThreshold).
  • TestSigningDoneCheck_ThresholdSubsetConcludes — oversized; reporters carry different end blocks but the result is the deterministic attemptTimeoutBlock.
  • TestSigningDoneCheck_OversizedIgnoresMinorityDivergentSignaturet correct + 1 divergent → concludes on the quorum, ignores the minority (no error).
  • TestSigningDoneCheck_OversizedSplitBelowQuorumTimesOut — 2+2 split, no bucket reaches t → times out.

Validated: build+vet across default / frost_roast_retry / frost_native frost_roast_retry / CGO; the done-check + signing-loop + roast-transition tests with -race; gofmt clean. The signingDoneCheckStrategy interface + mockSigningDoneCheck are untouched (only the constructor signature changed).

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a8b6e4e-882b-43c3-992f-713aeac33efa

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/frost-7.3-done-check-threshold

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mswilkison mswilkison force-pushed the feat/frost-7.3-done-check-threshold branch from c0202f8 to 8f01156 Compare June 19, 2026 22:36
…uorum (7.3 t-of-included PR3/3)

Under RFC-21 Phase 7.3 t-of-included finalize, an interactive signing attempt is
signed by the first t responsive committers of an (optionally) oversized included
set; the non-subset and offline members never broadcast a signing done check, so
the outer signingDoneCheck - which required a confirmation from EVERY attempt
member - would hang an otherwise-successful attempt to its timeout and force a
needless retry.

Conclude the done check on a deterministic threshold quorum instead, keeping the
legacy (non-oversized) path byte-for-byte unchanged:

- non-oversized (included == honestThreshold; today's selector output and the
  whole coarse path): the legacy all-members rule is UNCHANGED - wait for every
  attempt member, require all signatures equal, return max(endBlock).
- oversized (included > honestThreshold): bucket done checks by signature and
  conclude once a bucket holds >= honestThreshold distinct senders (the minimum
  that proves a valid threshold signature). Minority buckets (divergent or
  adversarial signatures) are IGNORED, never fatal, so one bad done message
  cannot fracture the group. The end block is the DETERMINISTIC attempt timeout
  block, not a network-order-dependent max over done messages.

The deterministic end block is the crux: batch scheduling derives the next
signature's start as signingStartBlock = prev endBlock + interlude, so a per-node
max over a network-order-dependent subset would desync the batch. Returning
attemptTimeoutBlock (which every honest node computes identically) fixes it. With
honest majority (t > groupSize/2) at most one signature bucket can reach t - even
under coordinator equivocation - so the quorum is unique and no body-hash / proto
change is needed; the >1-quorum branch is unreachable and intentionally non-fatal.

Design locked via a Codex+Gemini consult after the initial first-t-arrivals
approach was shown to be cross-node non-deterministic (it fed an order-dependent
end block into batch scheduling).

Tests: legacy done-check tests unchanged; added oversized conclude-on-quorum (with
a deterministic end block), minority-divergent-ignored, and
split-below-quorum-times-out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mswilkison mswilkison force-pushed the feat/frost-7.3-done-check-threshold branch from 8f01156 to 0671d99 Compare June 19, 2026 22:51
@mswilkison mswilkison merged commit 79ea845 into feat/frost-schnorr-migration-scaffold Jun 19, 2026
15 of 16 checks passed
@mswilkison mswilkison deleted the feat/frost-7.3-done-check-threshold branch June 19, 2026 23:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant