Skip to content

fix(tbtc/signer): require a genesis floor in the anchor descendant validator - #4227

Open
mswilkison wants to merge 2 commits into
codex/signer-store-identity-abifrom
fix/anchor-validator-parity
Open

fix(tbtc/signer): require a genesis floor in the anchor descendant validator#4227
mswilkison wants to merge 2 commits into
codex/signer-store-identity-abifrom
fix/anchor-validator-parity

Conversation

@mswilkison

Copy link
Copy Markdown
Contributor

Stacked on #4198. Pairs with the mirrored Go-side change in #4226.

The divergence

The Go and Rust anchor-trust descendant validators disagree about which references are admissible, in both directions:

check Go Rust
certified floor must be revision 1 of its service epoch ❌ (this PR)
checkpoint store fingerprint fixed across generations ❌ (#4226)

Neither gap is reachable today. Bootstrap and rotation endpoints both force a revision-1 To reference, so every call site already passes one — the full suite passes unchanged with the check added, which is the evidence for that claim rather than an assumption about it.

Why fix a latent gap

A divergence between the trees is not a cosmetic asymmetry. Whichever tree is more permissive would accept a certificate chain the other refuses — and the refusal happens on every store open, since the Rust trust journal is replayed and re-validated fail-closed each time. There is no truncation, rebase, or compaction path back from that state.

So the failure mode is a deterministic brick of a permissioned signer store, recoverable only by re-provisioning onto a fresh store identity — which abandons every DKG key package bound to the old one. Fail-closed is the safe direction, and it is still a dead store. The two validators should not be able to drift apart again.

The fix

Assert the union of the two rule sets rather than the intersection. Do not relax Go: its floor-revision rule is load-bearing at startup, where the floor arrives as external data from the Rust readback and the check is genuine defense in depth.

This PR adds Go's rule to Rust. The mirrored fingerprint check lands on the Go side in #4226.

Validation tightening only — no wire format, encoding, or ABI change, so no ABI revision. ci/frost-signer-pin.env is deliberately not bumped here: the pin must not point at an unmerged side branch. It should move to this commit once this merges into codex/signer-store-identity-abi.

Verification

cargo fmt -- --check
cargo clippy --all-targets -- -D warnings
TBTC_SIGNER_STATE_PATH=/tmp/... cargo test     # 369 passed, 0 failed

Two new tests pin both halves of the parity so a future edit to either validator has to confront the other: one asserts a non-genesis floor is refused while a revision-1 floor still admits the same descendant (so the rule rejects the floor shape, not ordinary descendants), and one asserts a store-fingerprint change across generations is refused.

🤖 Generated with Claude Code

The Go and Rust descendant validators disagreed about which references
are admissible, in both directions. Go alone required the certified floor
to be revision 1 of its service epoch; Rust alone required the checkpoint
store fingerprint to stay fixed across generations.

Neither gap is reachable today - bootstrap and rotation endpoints both
force a revision-1 To reference, so every call site here already passes
one, and the full suite passes unchanged with the check added. But a
divergence between the trees is not a latent nicety: whichever tree is
more permissive would accept a chain the other refuses on every store
open, and there is no truncation or rebase path back from that. It is a
fail-closed brick, which is the safe direction and still a dead store.

Take the union rather than the intersection. This adds Go's rule here;
the mirrored fingerprint check lands on the Go side separately.

Validation tightening only - no wire, encoding or ABI change.

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

coderabbitai Bot commented Aug 6, 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: 252e8f41-5751-4b32-a11f-5e1ecbeab8ae

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

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.

Two claims a maintainer or operator would act on.

The interactive header credited the durable consumption markers with
preventing a second share under the same nonces. That guarantee is real
but comes from somewhere else: nonces live only in memory, are zeroized
at first use, and are never restored on load, so no restart and no
durable-state rollback can hand a process a usable nonce at all. The
markers give at-most-once re-execution of an attempt, and evidence of
release once acknowledged. Crediting them with nonce-reuse prevention
invites a future change to treat the marker as the load-bearing defense
when it is not.

The README advertised TBTC_SIGNER_ENABLE_AUTO_QUARANTINE and its
threshold and penalty knobs without saying that nothing at runtime ever
writes a fault score or a quarantine entry. Enforcement reads both
wherever they appear and honors persisted state, but an operator
enabling the flag gets validated configuration and a silent no-op. The
failure direction is safe - no operator can be falsely quarantined - but
the advertisement is not. The hardening RFC's P1-M2 exit criterion has
the same problem and now carries a status note.

Comment and documentation only; no ABI or behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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