Skip to content

feat(wallet): add Wallet::sign_psbt<K> wrapping bitcoin::Psbt::sign#438

Open
EliteCoder18 wants to merge 3 commits into
bitcoindevkit:masterfrom
EliteCoder18:feat/sign-psbt-wrapper
Open

feat(wallet): add Wallet::sign_psbt<K> wrapping bitcoin::Psbt::sign#438
EliteCoder18 wants to merge 3 commits into
bitcoindevkit:masterfrom
EliteCoder18:feat/sign-psbt-wrapper

Conversation

@EliteCoder18

@EliteCoder18 EliteCoder18 commented Apr 10, 2026

Copy link
Copy Markdown

Description

Related to #331
Incorporates get_keymap() from #334

Adds Wallet::sign_psbt<K>, a thin wrapper around bitcoin::Psbt::sign that passes the wallet's internal secp256k1 context to the caller's GetKey implementor (e.g. Xpriv, a hardware wallet adapter).

Without this, callers driving signing themselves via the bitcoin crate must construct their own Secp256k1<All> — setup the wallet already owns.

Notes to the reviewers

  • sign vs sign_psbt: Wallet::sign uses BDK's internal signer infrastructure (InputSigner/TransactionSigner). sign_psbt delegates entirely to bitcoin::Psbt::sign; callers supply their own GetKey. Both methods coexist without conflict.

Changelog notice

  • Added Wallet::sign_psbt<K> wrapper around bitcoin::Psbt::sign using the wallet's internal secp256k1 context.
  • Added Wallet::get_keymap() to retrieve a combined KeyMap from all wallet signers for use with KeyMapWrapper and Wallet::sign_psbt.

Checklists

All Submissions:

@codecov

codecov Bot commented Apr 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.01%. Comparing base (58fe631) to head (43702e7).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #438      +/-   ##
==========================================
+ Coverage   80.96%   81.01%   +0.04%     
==========================================
  Files          24       24              
  Lines        5489     5503      +14     
  Branches      247      247              
==========================================
+ Hits         4444     4458      +14     
  Misses        968      968              
  Partials       77       77              
Flag Coverage Δ
rust 81.01% <100.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ValuedMammal

Copy link
Copy Markdown
Collaborator

Concept ACK. I've not had an extensive look at the implementation.

Callers must first run wallet.sign()

sign_psbt should be written in a way that assumes the PSBT has been updated and totally independent of wallet.sign(). For the tests you can create the PSBT to sign by parsing it from a string in base64.

@EliteCoder18 EliteCoder18 force-pushed the feat/sign-psbt-wrapper branch from 6694fbb to ce3d189 Compare April 11, 2026 08:34
@EliteCoder18

Copy link
Copy Markdown
Author

@ValuedMammal Thanks for the feedback!

I've updated the tests to use a base64 PSBT with pre-populated bip32_derivation, so sign_psbt is now fully independent of wallet.sign.

Would appreciate a review when you get a chance.

@ValuedMammal ValuedMammal moved this from In Progress to Needs Review in BDK Wallet Apr 14, 2026

@ValuedMammal ValuedMammal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Concept ACK

Comment thread src/wallet/mod.rs Outdated
Comment on lines +1814 to +1815
/// [`bitcoin::Psbt::sign`] uses the `bip32_derivation` fields in each PSBT input to
/// locate the correct child keys. PSBTs received from external coordinator tools or

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the bip32_derivation fields in each PSBT input to locate the correct child keys.

Or tap_key_origins in the case of taproot.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated the doc comment to mention both bip32_derivation (legacy/segwit) and tap_key_origins (taproot). Thanks for catching that!

@oleonardolima oleonardolima left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

cACK ce3d189

@EliteCoder18 EliteCoder18 force-pushed the feat/sign-psbt-wrapper branch from ce3d189 to 0884a8d Compare April 30, 2026 18:52
@notmandatory notmandatory removed this from the Wallet 3.1.0 milestone Jun 2, 2026

@noahjoeris noahjoeris left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

cACK 0884a8d
Thanks for adding this. It will be useful after this is merged: rust-bitcoin/rust-miniscript#872

Comment thread src/wallet/mod.rs
Comment on lines +1805 to +1825
/// Sign a PSBT using an external key provider via [`bitcoin::Psbt::sign`].
///
/// This is a thin wrapper around [`bitcoin::Psbt::sign`] that supplies the wallet's
/// internal [`secp256k1`] context. It lets callers sign with any type that implements
/// [`psbt::GetKey`], such as [`bitcoin::bip32::Xpriv`], without having to manage their
/// own secp256k1 context.
///
/// # BIP32 derivation metadata
///
/// [`bitcoin::Psbt::sign`] uses the `bip32_derivation` fields (for legacy and segwit inputs)
/// or the `tap_key_origins` fields (for taproot inputs) in each PSBT input to locate
/// the correct child keys. PSBTs received from external coordinator tools or
/// hardware wallet flows typically carry this metadata already.
///
/// # Returns
///
/// On success, a [`psbt::SigningKeysMap`] mapping each signed input index to the keys
/// that were used. On failure, a tuple of the partial success map and a
/// [`psbt::SigningErrors`] map of per-input errors.
///
/// [`secp256k1`]: bitcoin::secp256k1

@noahjoeris noahjoeris Jun 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: What do you think of those docs changes? I added that it's used for software signing and pointed to the correct approach for external signing.

Suggested change
/// Sign a PSBT using an external key provider via [`bitcoin::Psbt::sign`].
///
/// This is a thin wrapper around [`bitcoin::Psbt::sign`] that supplies the wallet's
/// internal [`secp256k1`] context. It lets callers sign with any type that implements
/// [`psbt::GetKey`], such as [`bitcoin::bip32::Xpriv`], without having to manage their
/// own secp256k1 context.
///
/// # BIP32 derivation metadata
///
/// [`bitcoin::Psbt::sign`] uses the `bip32_derivation` fields (for legacy and segwit inputs)
/// or the `tap_key_origins` fields (for taproot inputs) in each PSBT input to locate
/// the correct child keys. PSBTs received from external coordinator tools or
/// hardware wallet flows typically carry this metadata already.
///
/// # Returns
///
/// On success, a [`psbt::SigningKeysMap`] mapping each signed input index to the keys
/// that were used. On failure, a tuple of the partial success map and a
/// [`psbt::SigningErrors`] map of per-input errors.
///
/// [`secp256k1`]: bitcoin::secp256k1
/// Sign a PSBT using an external key provider via [`bitcoin::Psbt::sign`].
///
/// This is a thin wrapper around [`bitcoin::Psbt::sign`] that supplies the wallet's
/// internal [`secp256k1`] context. It lets callers sign with any type that implements
/// [`psbt::GetKey`], such as [`bitcoin::bip32::Xpriv`], without having to manage their
/// own secp256k1 context. Use it for software signing.
///
/// # BIP32 derivation metadata
///
/// [`bitcoin::Psbt::sign`] uses the `bip32_derivation` fields (for legacy and segwit inputs)
/// or the `tap_key_origins` fields (for taproot inputs) in each PSBT input to locate
/// the correct child keys. PSBTs constructed from within the wallet, or PSBTs received from
/// external coordinator tools or hardware wallet flows, typically carry this metadata already.
///
/// # Returns
///
/// On success, a [`psbt::SigningKeysMap`] mapping each signed input index to the keys
/// that were used. On failure, a tuple of the partial success map and a
/// [`psbt::SigningErrors`] map of per-input errors.
///
/// [`secp256k1`]: bitcoin::secp256k1
/// [`TransactionSigner`]: crate::signer::TransactionSigner

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure if we should refer to TransactionSigner, as it'll be later removed with signers module.

The hardware signers should handle the PSBT and signing keys on their own, considering that we had archived https://github.com/bitcoindevkit/rust-hwi and it's considered unmaintained.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure if we should refer to TransactionSigner, as it'll be later removed with signers module.

Anyway, we can add it now and remove it when removing the module.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yep thanks, was thinking about it too.
From my understanding we added sign_with_signers as a transition step to keep supporting the bdk signer for some more time while moving to psbt.sign.
Which means yeah agree we shouldn't refer to it.

@oleonardolima oleonardolima added this to the Wallet 3.2.0 milestone Jun 18, 2026

@oleonardolima oleonardolima left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

cACK 0884a8d

I suggest squashing the doc commit into the first one, and also updating the PR description to reflect the current state.

@oleonardolima

Copy link
Copy Markdown
Contributor

nit: I think it's a good idea to also add the helper method get_keymap here, you can cherry-pick it from #334.

As you add the helper method here, you can add an example of how to use in the documentation of sign_psbt.

NOTE: that you'd need to use the KeyMapWrapper, as we're still in miniscript 12.x

@EliteCoder18

Copy link
Copy Markdown
Author

Happy to add get_keymap, but since KeyMapWrapper is a temporary shim until rust-miniscript#872 lands, I'd rather not bake it into the docs here and then need to update it again in 4.0. planning to add get_keymap as a proper follow-up once the clean descriptor-aware path is available. Does that work for you?

nit: I think it's a good idea to also add the helper method get_keymap here, you can cherry-pick it from #334.

As you add the helper method here, you can add an example of how to use in the documentation of sign_psbt.

NOTE: that you'd need to use the KeyMapWrapper, as we're still in miniscript 12.x

@EliteCoder18 EliteCoder18 force-pushed the feat/sign-psbt-wrapper branch from 0884a8d to 0b62d13 Compare June 19, 2026 21:33
@oleonardolima

oleonardolima commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Happy to add get_keymap, but since KeyMapWrapper is a temporary shim until rust-miniscript#872 lands, I'd rather not bake it into the docs here and then need to update it again in 4.0. planning to add get_keymap as a proper follow-up once the clean descriptor-aware path is available. Does that work for you?

This PR is aimed to 3.x.x release, so it'll require the KeyMapWrapper anyway, either here or in the user side. It's best if we have all the ergonomic and convenience methods to guide the users onto how to use the new API, as per #340

@EliteCoder18

Copy link
Copy Markdown
Author

Thanks for the suggestion. I've added get_keymap() along with a test, and updated the sign_psbt docs with an example showing the get_keymapKeyMapWrapper flow.

I left out a KeyMapWrapper re-export for now to keep the public API surface minimal, since it can already be imported directly from miniscript.

This PR is aimed to 3.x.x release, so it'll require the KeyMapWrapper anyway, either here or in the user side. It's best if we have all the ergonomic and convenience methods to guide the users onto how to use the new API, as per #340

Add a thin wrapper method `sign_psbt` that delegates to
`bitcoin::Psbt::sign`, passing the wallet's internal secp256k1 context.

This allows users to sign PSBTs with their own `GetKey` implementor
(e.g., `Xpriv` or hardware wallet adapters) without managing a
separate secp256k1 context.

docs(wallet): document Wallet::sign_psbt usage and requirement
Add tests covering signing with a matching and non-matching `Xpriv`.

- `test_sign_psbt_with_xpriv`: verifies that a matching key signs at least one input
- `test_sign_psbt_with_wrong_key_signs_nothing`: verifies that an unrelated key
  returns Ok but produces no signatures
@EliteCoder18 EliteCoder18 force-pushed the feat/sign-psbt-wrapper branch 3 times, most recently from bed270d to 40a8cac Compare June 20, 2026 22:29

@oleonardolima oleonardolima left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ACK 40a8cac

Comment thread tests/psbt.rs
"expected no partial signatures when signing with an unrelated key"
);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: you could also add a test that uses the get_keymap and sign with Wallet keys here.

@oleonardolima

Copy link
Copy Markdown
Contributor

cc'in @thunderbiscuit as you're interested in the new signing flow.

@EliteCoder18 EliteCoder18 force-pushed the feat/sign-psbt-wrapper branch from 40a8cac to 43702e7 Compare June 23, 2026 19:49
@noahjoeris

Copy link
Copy Markdown
Contributor

Why do we want get_keymap if we plan to remove wallet-owned signers?
This is adding functionality that is already planned to be deprecated soon.

@notmandatory

Copy link
Copy Markdown
Member

Why do we want get_keymap if we plan to remove wallet-owned signers? This is adding functionality that is already planned to be deprecated soon.

This is a good point. If this is something we can do without and will have to later remove then we shouldn't do it at all. Based on the docs in bitcoindevkit/book-of-bdk#155 it looks like we don't need this helper.

@oleonardolima

Copy link
Copy Markdown
Contributor

Why do we want get_keymap if we plan to remove wallet-owned signers? This is adding functionality that is already planned to be deprecated soon.

Yes, it'll be removed in the next breaking change when removing the signers, though it's a good helper (not a MUST) method for current users to use the Wallet::sign_psbt<K> at current state, otherwise they would need to handle that themselves.

@oleonardolima

Copy link
Copy Markdown
Contributor

Why do we want get_keymap if we plan to remove wallet-owned signers? This is adding functionality that is already planned to be deprecated soon.

This is a good point. If this is something we can do without and will have to later remove then we shouldn't do it at all. Based on the docs in bitcoindevkit/book-of-bdk#155 it looks like we don't need this helper.

Won't the SignersContainers in the example there also be removed in the future ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new feature New feature or request

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

5 participants