Skip to content

feat(tbtc/signer): add offline-authorized FROST share repair - #4240

Open
mswilkison wants to merge 5 commits into
fix/anchor-validator-parityfrom
codex/share-repair-rust-2026-08-10
Open

feat(tbtc/signer): add offline-authorized FROST share repair#4240
mswilkison wants to merge 5 commits into
fix/anchor-validator-parityfrom
codex/share-repair-rust-2026-08-10

Conversation

@mswilkison

@mswilkison mswilkison commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add offline-authorized FROST share repair for one lost tBTC signing seat
  • require an exact threshold-sized, sorted helper set and exclude the target
  • bind every phase to the wallet, key group, public package, old/new stores,
    recovery epoch, endpoints, expiry, authorization nonce, and a separately
    authority-signed helper-then-target transport roster
  • derive authorization-, seat-, role-, and store-bound transport keys inside
    Rust; expose only public keys and fixed-size authenticated ciphertexts
  • implement repair arithmetic with owned zeroizing native scalars and
    stream-combine deltas/sigmas so complete scalar sets and raw signing shares
    never cross the ABI
  • expose Begin/Finish, Part1, Part2, and Install as the ABI 5.0 native-custody
    share-repair contract
  • persist recovered-seat evidence in inventory schema v2 so older binaries
    fail closed and activation remains blocked after restart

Safety properties

The base authorization and exact transport roster are independently signed by
the offline authority already pinned by the state-anchor trust configuration.
Every scalar endpoint independently revalidates the authorization, roster,
current durable store, current state-root-derived transport key, exact sender
set, and fixed envelope shape before native arithmetic. Initial install requires
the signed replacement-store fingerprint. An exact durable replay is idempotent
for crash recovery; later recovery requires a strictly newer epoch.

Plaintext repair values use non-Copy, zeroizing byte-backed ownership. Native
arithmetic scalars are narrowly scoped zeroizing temporaries, and both envelope
encryption/decryption and aggregation stay inside Rust. The host receives no
plaintext Delta/Sigma collection or transport private key.

Roster issuance is a trusted incident ceremony. The authority must collect
native/KMS or independently workload-attested preflight evidence and must not
sign caller-invented public keys. This in-process FFI minimizes host-language
secret residency; it is not a hardware isolation boundary against arbitrary
same-address-space code.

The existing Go state-anchor barrier wraps install, so repaired state is not
reported as installed until the independent anchor acknowledges the durable
mutation. This remains disaster recovery only; it does not increase signer
capacity, group size, threshold, or anchor-history capacity.

Verification

  • cargo fmt --all -- --check
  • cargo test --all-targets
  • cargo clippy --all-targets -- -D warnings
  • focused share-repair transport, roster, zeroization, restart/rotation,
    malformed-input, replay, and production-scale tests
  • direct resolution of all five share-repair symbols and ABI 5.0 readback
  • pkg/tbtc/signer/scripts/formal/run_tla_models.sh against the pinned official
    TLC release (all five configurations pass)

Stack

This PR is intentionally stacked on #4227 (fix/anchor-validator-parity). The
companion Go orchestration and activation PR is #4241 and pins this PR's exact
commit. Review/merge this PR before #4241.

@coderabbitai

coderabbitai Bot commented Aug 10, 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: 4494be90-a795-46ac-97bc-c6dcab554e7a

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.

@mswilkison

mswilkison commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Resolved in 98357a9926e44e8f6477b852a7a55e43dfb96fd4.

The failed job stopped before running any model because the v1.8.0 release
asset had changed. Before updating the pin I confirmed that GitHub's official
TLA+ release API publishes SHA-256
e22f8ffb4bacdea0a871f444dd94fe5fb0d8013b3388ae39e82e26f852c735d5,
downloaded that exact asset from the official release URL, verified the JAR
structure and manifest (TLA+ v1.8.0, upstream revision 30cc360), and ran all
five local TLC model configurations successfully. The companion Go PR #4241
now pins this new exact Rust head.

@mswilkison

Copy link
Copy Markdown
Contributor Author

Addressed the latest security-review findings in 35259de9aeb2daa25429a2630f1e8290240e500f.

  • Complete RTS delta/sigma sets no longer cross the FFI. Rust performs Part1,
    Part2 aggregation, sigma aggregation, target verification, and install using
    streamed native values; the host sees only fixed 105-byte AEAD envelopes.
  • The upstream Copy repair wrappers are no longer used for secret repair
    state. Persistent values use a non-Copy, byte-backed SecretRepairScalar
    with zeroize-on-drop; native arithmetic scalars are narrowly scoped
    zeroizing temporaries.
  • ABI 5.0 adds Begin/Finish and replaces plaintext data_hex with
    transport_public_key_hex, a signed exact store-bound transport roster, and
    opaque payload_hex envelopes.
  • Envelope KDF/AAD binds authorization, roster, sender, recipient, kind, and
    both static endpoints; sender-static plus per-envelope ephemeral ECDH rejects
    caller-forged helper frames.
  • Each scalar endpoint revalidates the signed authorization/roster and the
    current state-root-derived key before and after native work.

Verification: full cargo test --quiet (377 passed, 4 intentional ignores),
cargo clippy --all-targets -- -D warnings, fmt/diff checks, explicit 51-of-100
launch gate, dynamic symbol/header/Go-schema cross-check, and a hash-bound
full-file security audit all pass.

The runbook now states the remaining trust boundary explicitly: roster entries
must be collected directly from native/KMS or through independently attested
workloads. An in-process FFI minimizes Go secret residency; it is not hardware
isolation from arbitrary same-address-space code, and production remains gated
if trusted preflight provenance is unavailable.

@mswilkison

mswilkison commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the latest restart-mixing review finding in 87492db35f12566f83429fb2a2f751811b5afd95.

The finding was valid: Part1 previously generated a fresh random repair row for every call, while the wire namespace remained the same authorization digest. A helper restarting before installation could therefore combine a new self-delta with peers' old retransmissions, and the target's final public-share check would reject the mixed transcript.

The fix makes the plaintext Part1 transcript deterministic for the exact signed bundle and frozen transport version. Each non-residual delta is independently HKDF/rejection-sampled inside native custody from the root/store/provider-bound transport secret and binds the authorization digest, signed roster digest, sender, recipient, derivation version, and counter. The final slot remains the canonical residual. ECIES stays freshly randomized, so no deterministic ciphertext/nonces were introduced. Any future derivation change must bump the transport/AAD/ABI version and use a fresh authorization/roster.

Regression coverage now proves:

  • Part1 -> Finish -> simulated process restart yields byte-different ciphertexts but identical slot plaintexts;
  • old and restarted generations can be mixed and still install the exact authorized share;
  • a fresh authorization produces an independent transcript;
  • an authenticated but algebraically altered delta is rejected by the final public-share check before persistence;
  • a frozen derivation KAT catches accidental drift.

Validation: full Rust suite (379 passed, 4 ignored), clippy with warnings denied, fmt/diff checks, and the explicit 51-of-100 production-scale launch gate all pass.

CI follow-up: c0d08c0c0c831c54ff765c9520be674789b12efa retains the fail-closed TLA checksum gate and repins it to the replacement digest published in GitHub's official release metadata after upstream rebuilt its rolling v1.8.0 asset.

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