Skip to content

feat(ffi): expose sign_digest for arbitrary 32-byte digest signing - #71

Merged
notmandatory merged 2 commits into
bitcoindevkit:masterfrom
r1b2ns:feat/public-sign
Sep 30, 2026
Merged

notmandatory merged 2 commits into
bitcoindevkit:masterfrom
r1b2ns:feat/public-sign

Conversation

@r1b2ns

@r1b2ns r1b2ns commented May 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Expose sign_digest on TapSigner and through the UniFFI surface so Swift/Kotlin consumers can sign arbitrary 32-byte digests (BIP-137 "Bitcoin Signed Message", proof-of-key challenges, generic attestations) without going through sign_psbt.
  • Return SignedDigest { signature, pubkey, rec_id } — the recovery id is computed at the FFI boundary so callers can verify locally or build a BIP-137 header byte without an extra round-trip.

Closes #70.

Changes

  • lib/src/tap_signer.rs — public sign_digest(digest, sub_path, cvc) wrapper around the existing crate-private TapSignerShared::sign.
  • lib/src/lib.rs — re-export bitcoin so downstream FFI code can use rust_cktap::bitcoin::secp256k1.
  • cktap-ffi/src/tap_signer.rs — UniFFI entry point + SignedDigest record + brute-force recovery-id derivation against the returned compressed pubkey.
  • cktap-ffi/src/error.rs — new SignDigestError with CkTap, InvalidDigestLength { len: u32 }, and RecoveryId { msg } variants.

Test plan

  • cargo build --all-features --all-targets (workspace)
  • cargo test — cktap-ffi recovery-id coverage (derive_recovery_id_matches_signer_rec_id, derive_recovery_id_errors_when_pubkey_does_not_match, derive_recovery_id_covers_both_compressed_ids) passes
  • cargo test -p rust-cktap --features emulator -- --nocapture against the coinkite emulator
  • Manual: from Swift, call signDigest(digest:subPath:cvc:) and verify the returned signature against the returned pubkey using the rec_id

@r1b2ns
r1b2ns requested review from notmandatory and reez as code owners May 23, 2026 13:44
@reez

reez commented May 26, 2026

Copy link
Copy Markdown
Contributor

Should we drop the regenerated Swift bindings from this PR? They expect the new FFI symbols but Package.swift still points at the released v0.2.2 xcframework so root swift build breaks. (The release flow can regenerate bindings with the matching framework)

@r1b2ns

r1b2ns commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Should we drop the regenerated Swift bindings from this PR? They expect the new FFI symbols but Package.swift still points at the released v0.2.2 xcframework so root swift build breaks. (The release flow can regenerate bindings with the matching framework)

Done, I’ve removed the Swift bindings from this PR

@notmandatory

Copy link
Copy Markdown
Member

I did a quick review and can give this a concept ACK but need to try testing locally and give it a deeper look.

@notmandatory notmandatory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK b84ba25

You'll probably have to rebase this if #74 get's merged first.

@r1b2ns

r1b2ns commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

utACK b84ba25

You'll probably have to rebase this if #74 get's merged first.

tks @notmandatory, ok I keep watching #74

@notmandatory notmandatory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks very good. I have one comment that is an easy fix.

You should rebase and also cleanup your commit history to not include the ffi generation and removal. Could be just two commits, one with the feature and another with the tests, the rest is noise. Thanks!

Comment thread lib/src/lib.rs Outdated
@notmandatory

Copy link
Copy Markdown
Member

Also need to figure out why these emulator tests are failing: https://github.com/bitcoindevkit/rust-cktap/actions/runs/35165745575/job/107004613825

@r1b2ns
r1b2ns marked this pull request as draft September 30, 2026 19:04
Adds a public `sign_digest` wrapper on `TapSigner` and an FFI entry
point so Swift/Kotlin consumers can sign arbitrary digests (BIP-137
"Bitcoin Signed Message", proof-of-key challenges, generic
attestations) without going through `sign_psbt`.

The FFI returns `SignedDigest { signature, pubkey, rec_id }` — the
recovery id is computed at the boundary so callers can verify locally
or build a BIP-137 header byte without an extra round-trip. As with the
other FFI entry points, the CVC is validated locally before the card is
contacted, and `SignDigestError` carries a `Cvc` variant for it.

`rust_cktap` now re-exports `secp256k1` so the FFI crate can derive the
recovery id without depending on `bitcoin` directly.

Closes bitcoindevkit#70
FFI unit tests (no card required):
- `derive_recovery_id_matches_signer_rec_id`,
  `derive_recovery_id_covers_both_compressed_ids` and
  `derive_recovery_id_errors_when_pubkey_does_not_match` exercise the
  recovery-id derivation, including both ids reachable with
  compressed-pubkey ECDSA.
- `sign_digest_rejects_invalid_cvc_before_contacting_card`,
  `sign_digest_rejects_wrong_digest_length_before_contacting_card` and
  `sign_digest_reports_cvc_error_before_digest_length_error` use a
  counting transport double to assert invalid input never reaches the
  card; `sign_digest_with_valid_inputs_reaches_card` proves the double
  does observe card traffic.

Emulator test:
- `test_tap_signer_sign_digest` checks, for sub_paths `[]` and
  `[0, 5]`, that the signing key matches the card's account xpub
  derived at that sub_path and that the signature verifies only over
  the digest that was sent.
@r1b2ns
r1b2ns marked this pull request as ready for review September 30, 2026 19:33
@notmandatory
notmandatory merged commit 09f2b91 into bitcoindevkit:master Sep 30, 2026
10 checks passed
@notmandatory

Copy link
Copy Markdown
Member

Thanks @r1b2ns !

This branch is waiting to be deployed

1 waiting deployment
release — 09f2b91c Waiting Sep 30, 2026 by notmandatory via Publish Rust Release #8
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.

Expose digest signing via FFI to enable Bitcoin Signed Message support

3 participants