Commit 9a2d113
authored
feat(frost): finalize interactive signing over the first t responsive committers (7.3 t-of-included PR2/3) (#4093)
## What
RFC-21 Phase 7.3, the **runner subset** of t-of-included finalize. Today
the interactive ROAST signing runner
(`pkg/frost/signing/roast_runner_frost_native.go`) waits for **all**
included members in round 1 (`collectCommitments`) and round 2
(`collectShares`), so one slow or offline member stalls the attempt to
its ctx deadline → fail → ROAST retry. This makes an attempt finalize
over the **first `t` responsive committers**.
### Roles (decided by the package's signed `signer_ids`, PR1's field
#4092)
- **Coordinator** — `collectCommitments` collects until **exactly `t`**
(its own commitment seeded), proceeds the instant `t` have arrived, and
builds the FROST package over that `t`-subset. It sets `signer_ids`
(ascending, distinct) in `signSigningPackage` to the chosen members. If
fewer than `t` ever commit, the ctx deadline fires and the run fails
into the existing retry path.
- **Non-coordinator signer** (in `signer_ids`) — runs round 2 and
collects round-2 shares from the **package's signer set**, not the full
included set.
- **Observer** (committed in round 1 but **not** in `signer_ids`) — does
**not** run round 2. It aggregates the subset's broadcast shares
publicly, `MarkSucceeded`, and returns the signature — and **still
aborts the engine session on success** to drop its unconsumed round-1
nonces. The success path previously suppressed the abort, which leaked
an observer's nonces; fixed via a `signedRound2` flag.
A round-2-silent member among the chosen `t` still fails the attempt →
retry (existing path). Only the elected coordinator builds the package,
so there is no honest divergence (receivers already filter
`sender==elected` + attempt hash). The FROST `SigningPackageBytes`
remains the cryptographic source of truth, so a coordinator that lies in
`signer_ids` causes only a liveness failure (aggregate fails closed),
never a wrong signature or false blame.
## Inert until oversizing (MacLane's policy knob)
t-of-included is **dormant** until participant selection oversizes the
included set past the threshold — it currently trims to exactly
`honestThreshold` (`signing_loop_legacy_selector.go:91`), so the chosen
subset equals the full included set and every member signs (today's
behavior). The machinery is harmless and inert until then.
## Hardening
The runner constructor now rejects `threshold > len(includedSet)` — the
new round-1 loop's termination invariant (a well-formed attempt always
selects ≥ threshold members; fail fast rather than silently degrade into
timeout-driven retries). Found by the inline adversarial review.
## Tests (all `frost_native`, behind pre-prod tags)
- Extended `buildInteractiveSigningHarness` with `runMembers` to drive
**silence** (a non-responsive member).
- `FinalizesOverResponsiveThresholdSubset` — a non-coordinator offline
member is excluded; the two responsive members finalize over `t`; the
coordinator's package omits the offline member.
- `OversizedAllOnline_FinalizesOverThreshold` — all online; exactly `t`
sign (round-2 count), `n−t` observe and abort their nonces, all reach
Succeeded; the broadcast package carries exactly `t` ascending signer
ids.
- `ObserverAggregatesAndAbortsWithoutSigning` — focused observer:
aggregates, Succeeded, **0** round-2 calls, **1** abort.
- Existing `UsesEngineDerivedFrostIdentifiers` retargeted to a
full-included (`n==t`) attempt for determinism.
Validated: build+vet+test across the 5 tag combos (default /
`frost_native` / `frost_roast_retry` / `frost_native frost_roast_retry`
/ `frost_native frost_tbtc_signer` w/ CGO), `-race` on
`pkg/frost/signing`, the `frost_roast_retry` drive tests, repo-wide
`frost_native` vet, `pkg/tbtc` build, and gofmt — all clean.
## Follow-up (PR3, lands with/right after this)
`signingDoneCheck` (`pkg/tbtc/signing_done.go:93`, called from
`signing_loop.go:458`) sets `expectedSignersCount =
len(attemptMembersIndexes)`; once oversizing is enabled, a successful
attempt that omitted an offline member would hang the outer done-check.
PR3 makes it threshold/package-set aware.
🤖 Generated with [Claude Code](https://claude.com/claude-code)3 files changed
Lines changed: 597 additions & 89 deletions
File tree
- pkg/frost/signing
Lines changed: 25 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
45 | 46 | | |
46 | 47 | | |
47 | 48 | | |
| |||
93 | 94 | | |
94 | 95 | | |
95 | 96 | | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
96 | 111 | | |
97 | 112 | | |
98 | 113 | | |
| |||
153 | 168 | | |
154 | 169 | | |
155 | 170 | | |
| 171 | + | |
156 | 172 | | |
157 | 173 | | |
158 | 174 | | |
| |||
0 commit comments