Wallet::sign_with_signers API¶
Overview
- Lead Developer: @noahjoeris
- Pull Request: #490
- Feature Type: Non-Breaking
Overview¶
Wallet::sign_with_signers is a new method that accepts an explicit list of SignersContainer objects and signs a PSBT using them in the order provided. The existing Wallet::sign method now simply delegates to sign_with_signers, passing the wallet's own internal signer containers, so there is no change to existing callers.
Why Do This?¶
Previously, the only way to sign a PSBT with BDK was through Wallet::sign, which always used the signers that were embedded in the wallet at construction time. This made it difficult to:
- Sign with external hardware signers or air-gapped devices whose keys are not stored inside the wallet
- Compose multiple signing devices in a custom order
- Keep the wallet itself key-free (watch-only) while still driving the signing flow
sign_with_signers decouples the signing logic from wallet-internal key storage, and is a step toward eventually allowing Wallet to be fully signer-agnostic.
New Method on Wallet¶
pub fn sign_with_signers(
&self,
psbt: &mut Psbt,
signers: &[&SignersContainer],
sign_options: SignOptions,
) -> Result<bool, SignerError>
Signer containers are processed in the order provided. Within each container, signers are ordered by their [SignerOrdering]. The method returns true if the PSBT was finalized, or false if further signatures are still needed.
Example: Signing with an External Signer¶
The following example builds a SignersContainer from a raw descriptor containing a private key (simulating an external signer), then passes it to sign_with_signers:
use bdk_wallet::*;
use bdk_wallet::bitcoin::*;
use bdk_wallet::bitcoin::{NetworkKind, secp256k1::Secp256k1};
use bdk_wallet::descriptor::IntoWalletDescriptor;
use bdk_wallet::signer::SignersContainer;
// Descriptor with private key material — represents an external signer
let signer_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)";
let secp = Secp256k1::new();
let (_, keymap) = signer_descriptor
.into_wallet_descriptor(&secp, NetworkKind::Test)
.unwrap();
// Build a SignersContainer from the keymap
let external_signers = SignersContainer::build(
keymap,
wallet.public_descriptor(KeychainKind::External),
wallet.secp_ctx(),
);
// Build and sign a transaction
let to_address = wallet.next_unused_address(KeychainKind::External).address;
let mut psbt = {
let mut builder = wallet.build_tx();
builder.drain_to(to_address.script_pubkey()).drain_wallet();
builder.finish()?
};
let finalized = wallet.sign_with_signers(
&mut psbt,
&[&external_signers],
SignOptions::default(),
)?;
assert!(finalized);
Relationship to Wallet::sign¶
Wallet::sign is unchanged and continues to work exactly as before. Internally it now calls:
self.sign_with_signers(
psbt,
&[self.signers.as_ref(), self.change_signers.as_ref()],
sign_options,
)
Callers who already use Wallet::sign do not need to change anything.