docs(tbtc/signer): Phase 7 interactive-session spec freeze#4049
Merged
mswilkison merged 3 commits intoJun 12, 2026
Conversation
Specifies the hardened interactive two-round FROST signing session - the production signing path - per the 2026-06-12 Decision Log: t-of-included-native finalize (decision 5), no transitional retrofit (decision 6), sidecar-shaped API contract (decision 2). The load-bearing design change: secret nonce custody moves inside the engine. Today's stateless primitives return serialized SigningNonces to the Go host and accept them back at sign_share, so nonces cross the FFI twice, live in host memory between rounds, and single-use is caller discipline only. The session layer keeps nonces in session-scoped engine memory behind an opaque handle, consumes atomically (durable marker before share release), never persists them - making restart/clone nonce reuse structurally impossible and removing all secret signing material from the FFI boundary. Also specified: the session API and registry semantics carried over from the coarse path's hardening inventory; t-of-included subset verification at Round2 (membership, subset-of-included, size t) so safety never depends on coordinator honesty; signed-body package envelopes extending the #4044 equivocation-evidence retention; the precise deletion trigger for the frozen transitional deterministic-nonce path (nonce.rs freeze marker now points here); reserved room for bounded n-t+1 concurrency; PR-sized phasing 7.0-7.6; and four open questions with proposed defaults for the freeze sign-off. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Four findings from the adversarial self-review pass: Round2 gains check (f): the member's own commitment entry in the signing package must be byte-identical to its Round1 output. Without it a malicious coordinator substitutes an honest member's commitment, the member's correctly-computed share fails verification at aggregation, and the blame machinery manufactures re-checkable evidence against an honest party - the same missing-field class as the F1 nonce-binding finding. Section 6 now states that share-verification blame is sound only because of (f), and that blame re-checking must run against the retained package envelope, never a reconstruction. Verification-before-consumption is now explicit: an invalid package leaves the nonce handle live. Live-state bounds added to section 5: open sessions and unconsumed handles are secret-bearing engine memory and get the registry discipline - hard cap, fail closed at capacity, TTL sweep that aborts and zeroizes abandoned sessions. The section 4 FFI claim is scoped to the signing path: dkg_part1/2 still hand secret round packages to the host (verified in frost_ops.rs), DKG custody is a named follow-up, and the audit scope must describe the DKG boundary as-is. Phasing 7.5 notes the manifest's V1-migration verification is an independent flip condition. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Owner sign-off 2026-06-12 with review converged (adversarial-pass findings applied in 73dc594; Codex and Gemini clean). The four forced questions are decided and recorded as Decision Log entry 8: dedicated operator-key-signed topic for signing packages, members-to-coordinator round-1 transport, strict first-t responsive subset, markers-only durability. Spec status flips Proposed -> FROZEN; implementation starts at Phase 7.1 against this contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mswilkison
added a commit
that referenced
this pull request
Jun 13, 2026
) ## What Phase 7.1 of the frozen spec ([#4049](#4049), `docs/phase-7-interactive-session-spec-freeze.md` §§4–5): the engine session layer for the interactive two-round signing path, with **engine-held nonce custody** as the defining property. ## The custody contract (spec §4) - Round-1 nonces: OS randomness, in-memory only, bound to `(session_id, attempt_id)`, zeroized on consumption/abort/expiry/replacement, **never** in any request, response, or persisted state. - **Consumption-before-release**: the durable per-attempt marker persists BEFORE the share leaves the engine. Persist failure → marker rolled back, nonces stay live, no share escaped (pinned by a persist-fault-injection test). Marker-without-share → attempt dead, fail closed. - Restart can never yield a second share under one nonce pair; the cloned-state nonce-reuse class is structurally eliminated. Pinned by a restart test: the marker survives reload and rejects the consumed attempt at every entry point while a fresh attempt proceeds. ## The API (spec §5, strict-mode only) | Call | Contract highlights | |---|---| | `InteractiveSessionOpen` | Key package once per session (validated against member); idempotent by fingerprint; conflicting reopen fails closed; **newer attempt implicitly aborts the prior live one**; consumed attempts rejected | | `InteractiveRound1` | Fresh nonces + commitments; idempotent until consumed | | `InteractiveRound2` | **All verification precedes consumption**: message binding, subset ⊆ included set, exactly-`t` size, own membership, and check (f) — own-commitment byte-identity, defeating coordinator framing of honest members | | `InteractiveSessionAbort` | Idempotent; destroys nonces without a marker (never-consumed attempts may reopen with fresh nonces) | Live-state bounds per the spec: fail-closed capacity cap (`TBTC_SIGNER_MAX_LIVE_INTERACTIVE_SESSIONS`, default 64) + lazy TTL sweep with abort semantics (`TBTC_SIGNER_INTERACTIVE_SESSION_TTL_SECONDS`, default 3600); both knobs ride the init-config surface. New structured error `consumed_nonce_replay`. Telemetry: call/success counters ×4, latency for the two cryptographic rounds. The four `frost_tbtc_interactive_*` FFI exports ship additively per the established pattern (Go adoption is Phase 7.3; nothing breaks until the host calls them). ## Verification - **10 engine tests**: e2e round trip with one member through the session API and one through the stateless primitive, aggregating to a **verified BIP-340 signature** (custody changed, cryptography didn't); framing-attack rejection then honest-package acceptance (verify-before-consume); package-shape rejections (outside-set, message mismatch, oversized, self-missing); round-1 idempotency → consumed replay; restart-marker durability; persist-fault rollback; open lifecycle (idempotent/conflict/replacement); abort; TTL expiry; capacity fail-closed. - **1 FFI dispatch smoke test** across all four exports. - Full suite **255 passed / 1 ignored** (baseline 244+1 plus the 11 new), `clippy -D warnings` clean on all targets incl. the bench shape, **Phase 5 chaos suite green**, persisted-state schema additive (existing state files load unchanged). ## Scope boundary `InteractiveAggregate` + package-envelope evidence + cross-language vectors are Phase 7.2 by the frozen phasing; Go orchestration is 7.3. Out of scope per the freeze: DKG secret-package custody (named follow-up). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Phase 7.0: the spec-freeze candidate for the hardened interactive two-round FROST signing session — the production signing path — assembling the already-settled decisions (t-of-included-native per decisions 5/6, sidecar-shaped API per decision 2, OS-randomness-only production signing) into a precise contract, plus the deletion plan for the frozen transitional deterministic-nonce path.
The load-bearing design change: nonce custody moves inside the engine
Verified current state: the stateless primitives (
frost_ops.rs) return serializedSigningNoncesto the Go host and accept them back atsign_share— secret nonces cross the FFI twice, live in host memory between rounds, and single-use is enforced by caller discipline only. Callingsign_sharetwice with one nonce pair is the canonical FROST key-extraction failure and nothing prevents it today.The session layer (spec §4): nonces are generated and held in session-scoped engine memory behind an opaque handle, consumed atomically (durable consumption marker before the share leaves the engine), zeroized on use, and never persisted and never exported. Restart loses in-flight nonces by construction (attempt fails safe); the cloned-state nonce-reuse class becomes structurally impossible; and after Phase 7 no secret signing material transits the FFI in either direction — which is also the audit story for that boundary.
Also specified
InteractiveSessionOpen/Round1/Round2/Aggregate/Abort, idempotent-or-fail-closed, strict-mode attempt contexts only, consumed-registry semantics carried from the coarse path, transport-agnostic for the dlopen→sidecar swap.nonce.rsfreeze marker now points at this section — the only code change in this PR, comment-only on the frozen file.Freeze process
Status is Proposed; it freezes on signer + keep-core owner sign-off per §11, with the §10 defaults ratified or overridden in the gates-doc Decision Log. The audit scope statement should reference this document and name the §5 API as in-scope (decision 1 interaction).
🤖 Generated with Claude Code