Skip to content

fix(signer): match key origins by path components - #91

Closed
artofbitcoin wants to merge 1 commit into
bitcoindevkit:masterfrom
artofbitcoin:fix/signer-origin-path-prefix
Closed

artofbitcoin wants to merge 1 commit into
bitcoindevkit:masterfrom
artofbitcoin:fix/signer-origin-path-prefix

Conversation

@artofbitcoin

Copy link
Copy Markdown

Description

Fixes #90.

Signer::get_key decided whether a key origin (fingerprint, path) covers a KeyRequest::Bip32 derivation with derivation.to_string().starts_with(&path.to_string()). That compares characters rather than path components, so:

  • origin m/1 covered a request for m/10;
  • origin 84h/1h/0h/1 covered 84h/1h/0h/10 and 84h/1h/0h/12/0;
  • an unhardened step 0 covered its hardened counterpart 0h.

When both paths had the same length, skip(path.len()) left an empty suffix and the signer returned the key at the origin path for a request it did not cover.

The check now uses derivation.as_ref().starts_with(path.as_ref()) on the ChildNumber slices. A request equal to the origin path, or extending it, still matches, as covered by the existing get_key_bip32 cases.

Notes to the reviewers

  • New test get_key_bip32_origin_is_not_a_string_prefix builds a descriptor with each origin and checks that the non-covered requests return Ok(None). It fails on master and passes with this change.
  • alloc::string::ToString is no longer used in the non-test code, so its import moved to the test module.
  • Checks run locally with Rust stable: cargo test (31 passed), cargo check --no-default-features --features miniscript/no-std, cargo clippy --all-targets --all-features -- -Dwarnings, cargo fmt --all -- --check.
  • This change and its description were written with Claude Code (Claude Opus 5.5); I reviewed the diff and ran the checks above. The commit is signed by GitHub (web commit).

Changelog notice

Fixed: Signer no longer returns a key for a BIP32 request whose derivation only shares a string prefix with the key origin path.

Before submitting

Signer::get_key decided whether a key origin covers a Bip32 key request
by comparing the two derivation paths as strings. That matches on
character boundaries, so an origin m/1 covered a request for m/10, and
an unhardened step matched its hardened counterpart. When both paths had
the same length, the signer then returned the key at the origin path for
a request it did not cover.

Compare the paths component by component instead, and add a test with
the false positives from the issue.

Fixes bitcoindevkit#90

Co-authored-by: Claude <noreply@anthropic.com>
@noahjoeris

Copy link
Copy Markdown
Contributor

Duplicate of #81

@noahjoeris noahjoeris marked this as a duplicate of #81 Oct 12, 2026
@noahjoeris noahjoeris closed this Oct 12, 2026
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.

Signer::get_key matches BIP32 origin by string prefix instead of path components

2 participants