Skip to content

feat(frost): no-coarse-fallback mode for coarse-path retirement (default off)#4101

Merged
mswilkison merged 4 commits into
feat/frost-schnorr-migration-scaffoldfrom
feat/frost-coarse-retirement-flag
Jun 21, 2026
Merged

feat(frost): no-coarse-fallback mode for coarse-path retirement (default off)#4101
mswilkison merged 4 commits into
feat/frost-schnorr-migration-scaffoldfrom
feat/frost-coarse-retirement-flag

Conversation

@mswilkison

Copy link
Copy Markdown
Contributor

What

The reversible, un-gated first half of coarse-path retirement (RFC-21 Phase 7.3). Adds a default-off KEEP_CORE_FROST_INTERACTIVE_SIGNING_ONLY gate; when set, the executor refuses to fall through to the coarse signing primitive — if interactive signing did not run (its audit gate off, or no engine registered), the attempt fails closed rather than silently signing over the retired coarse path.

Only the (nil signature, nil error) "interactive not enabled → coarse" fall-through becomes a refusal; the hard-fail on a committed interactive failure is unchanged.

Safety / sequencing

  • Default off → production unchanged. Coarse stays the path until an operator flips this on.
  • Flipping it on is the tECDSA→FROST cutover for that node (no coarse fallback), so it stays off until the frost-secp256k1-tr external audit clears and the recovery-leaf decision lands.
  • The irreversible part — deleting the transitional coarse FFI primitive + the deterministic-nonce path — is the deliberate follow-up, not in this PR.

Tests

  • TestEntry_InteractiveOnly_RefusesCoarseFallback — orchestration active + interactive audit gate off + this flag on → the executor returns a refusal naming the env var, no signature.
  • TestEntry_InteractiveSigningOnlyEnabled_ParsesFlag — flag parsing.
  • Existing static-fallback executor tests unchanged (flag defaults off). Builds clean across tag combos; vet + gofmt clean.

🤖 Generated with Claude Code

…ult off)

The reversible, un-gated half of coarse-path retirement (RFC-21 Phase 7.3). Adds a
default-OFF KEEP_CORE_FROST_INTERACTIVE_SIGNING_ONLY gate; when set, the executor
REFUSES to fall through to the coarse signing primitive: if interactive signing did
not run (its audit gate off, or no engine), the attempt fails CLOSED rather than
silently signing over the retired coarse path. The hard-fail on a committed
interactive failure is unchanged; this only converts the (nil signature, nil error)
"interactive not enabled -> coarse" fall-through into a refusal.

Default off, so production is unchanged: coarse stays the path until an operator
flips this on. Flipping it on IS the tECDSA->FROST cutover for that node (the coarse
fallback is gone), so it stays off until the frost-secp256k1-tr external audit clears
and the recovery-leaf decision lands - the actual code deletion of the transitional
coarse primitive is the irreversible follow-up, deliberately deferred.

Tests: TestEntry_InteractiveOnly_RefusesCoarseFallback (orchestration active +
interactive audit gate off + this flag on -> the executor returns a refusal naming
the env var, no signature) and TestEntry_InteractiveSigningOnlyEnabled_ParsesFlag.
Existing static-fallback executor tests unchanged (the flag defaults off). Builds
clean across the tag combos; cgo vet + gofmt clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 20, 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: 8fb8249c-72c3-479e-b93a-2a90fc4e5649

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-coarse-retirement-flag

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 and others added 3 commits June 20, 2026 19:37
…n point

Codex review (PR #4101 P2): the interactive-only check sat in
attemptRoastRetryOrchestrationFromRequest, AFTER the drive - so the earlier
static-fallback returns (readiness gate off, no coordinator registered, unsupported
signer material) returned (nil, nil, nil) before the check ran, and the adapter then
proceeded to the coarse primitive. KEEP_CORE_FROST_INTERACTIVE_SIGNING_ONLY was
bypassed on exactly the misconfigured/cutover paths it was meant to protect.

Move the enforcement to native_ffi_executor_adapter, the SINGLE point where the coarse
primitive is invoked: when the orchestration yields no interactive signature for ANY
reason (audit gate off, no engine, or any static fallback) and interactive-only mode
is on, the adapter fails CLOSED before nefea.primitive.Sign instead of falling
through. Revert the partial executor-entry check.

Test moves to the adapter level: TestNativeExecutionFFIExecutorAdapter_Execute_
InteractiveOnlyRefusesCoarse runs in the DEFAULT build, where the orchestration helper
is a no-op (nil,nil,nil) - the ultimate static fallback - and asserts the adapter
returns a refusal AND the coarse primitive's signCalls == 0 (never invoked). Plus the
flag-parsing test. Builds across all tag combos; existing adapter + executor-entry
tests unchanged; gofmt clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fold of two Codex #4101 P2 findings:

- P2-1 (suppress outer fallbacks): the interactive-only guard lived only inside the
  FFI adapter, so when the native FFI path was unavailable (ErrNativeCryptographyUnavailable
  before the adapter's guard) the OUTER buildTaggedNativeExecutionBridge/Adapter still
  delegated to the legacy backend, because nativeExecutionFallbackAllowed() stayed true.
  Gate that single function on the flag: interactive-only now returns false there,
  closing every outer legacy/coarse fallback (the bridge + adapter consult it before
  delegating). New backend test asserts the suppression.

- P2-2 (terminal classification): the adapter's refusal returned a plain error, so the
  tBTC signingRetryLoop (which only aborts on ErrTerminalSigningFailure) treated this
  deterministic configuration failure as retryable and spun to timeout. Wrap the
  refusal with %w ErrTerminalSigningFailure; the adapter test now asserts errors.Is.

Also folds my own review's scope notes into the gate doc: interactive-only is
format-agnostic (refuses coarse for every signer format the native executor handles),
closes both the inner FFI primitive and the outer fallbacks, and fails all native
signing closed in a build without the interactive engine - so enable it only on a
frost_native node with the audit gate on.

Builds across all tag combos; full default + frost_native/frost_roast_retry suites
pass; gofmt clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…coarse flag

Fold of a third round of Codex #4101 P2 findings - the flag's fail-closed behavior
still had two holes, both now enforced at the backend Execute (the action) so they
cannot be bypassed by a caller:

- The DEFAULT backend fails OPEN. KEEP_CORE_FROST_INTERACTIVE_SIGNING_ONLY was checked
  only in nativeExecutionFallbackAllowed + the native FFI adapter. A node left at the
  documented default (""/legacy) signs straight through legacyExecutionBackend.Execute
  (the tECDSA/coarse signer), never touching those guards - so the safety switch failed
  open under the default config. legacyExecutionBackend.Execute now refuses with a
  terminal error when the flag is on.

- Outer native refusals were retryable. When the native path is unavailable before the
  FFI adapter's terminal refusal can run (no FFI executor, or the bridge returns
  ErrNativeCryptographyUnavailable with the fallback suppressed), the bridge/adapter
  return a bare ErrNativeCryptographyUnavailable; the tBTC signingRetryLoop only aborts
  on ErrTerminalSigningFailure, so it retried this deterministic failure to timeout.
  nativeExecutionBackend.Execute now promotes that unavailable error to terminal when
  the flag is on (and leaves it untouched when off).

Tests: legacy terminal refusal; native unavailable->terminal promotion plus a flag-off
pass-through (no regression). Builds across all tag combos; full default +
frost_native/frost_roast_retry suites pass; gofmt clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mswilkison mswilkison merged commit d1589fa into feat/frost-schnorr-migration-scaffold Jun 21, 2026
15 of 16 checks passed
@mswilkison mswilkison deleted the feat/frost-coarse-retirement-flag branch June 21, 2026 00:26
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