Skip to content

Commit 31cd70f

Browse files
committed
Merge branch 'master' into feature/cpfp_support
# Conflicts: # examples/common.rs
2 parents e986959 + ac547ce commit 31cd70f

File tree

5 files changed

+128
-45
lines changed

5 files changed

+128
-45
lines changed

examples/common.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ use bdk_bitcoind_rpc::{Emitter, NO_EXPECTED_MEMPOOL_TXIDS};
66
use bdk_chain::{
77
bdk_core, Anchor, Balance, CanonicalizationParams, ChainPosition, ConfirmationBlockTime,
88
};
9+
use bdk_coin_select::DrainWeights;
910
use bdk_testenv::{bitcoincore_rpc::RpcApi, TestEnv};
1011
use bdk_tx::{
1112
CanonicalUnspents, CpfpParams, Input, InputCandidates, RbfParams, ScriptSource, Selection,
1213
TxStatus, TxWithStatus,
1314
};
14-
use bitcoin::{absolute, Address, Amount, BlockHash, FeeRate, OutPoint, Transaction, Txid, Weight};
15+
use bitcoin::{
16+
absolute, Address, Amount, BlockHash, FeeRate, OutPoint, Transaction, TxOut, Txid, Weight,
17+
};
1518
use miniscript::{
1619
plan::{Assets, Plan},
1720
Descriptor, DescriptorPublicKey, ForEachKey,
@@ -137,6 +140,30 @@ impl Wallet {
137140
.map(|c_tx| (c_tx.tx_node.tx, status_from_position(c_tx.chain_position)))
138141
}
139142

143+
/// Computes an upper bound on the weight of a change output plus the future weight to spend it.
144+
pub fn change_weight(&self) -> DrainWeights {
145+
let desc = self
146+
.graph
147+
.index
148+
.get_descriptor(INTERNAL)
149+
.unwrap()
150+
.at_derivation_index(0)
151+
.unwrap();
152+
let output_weight = TxOut {
153+
script_pubkey: desc.script_pubkey(),
154+
value: Amount::ZERO,
155+
}
156+
.weight()
157+
.to_wu();
158+
let spend_weight = desc.max_weight_to_satisfy().unwrap().to_wu();
159+
160+
DrainWeights {
161+
output_weight,
162+
spend_weight,
163+
n_outputs: 1,
164+
}
165+
}
166+
140167
pub fn all_candidates(&self) -> bdk_tx::InputCandidates {
141168
let index = &self.graph.index;
142169
let assets = self.assets();

examples/synopsis.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use bdk_testenv::{bitcoincore_rpc::RpcApi, TestEnv};
22
use bdk_tx::{
33
filter_unspendable_now, group_by_spk, selection_algorithm_lowest_fee_bnb, ChangePolicyType,
4-
Output, PsbtParams, SelectorParams, Signer,
4+
Output, PsbtParams, ScriptSource, SelectorParams, Signer,
55
};
66
use bitcoin::{key::Secp256k1, Amount, FeeRate, Sequence};
77
use miniscript::Descriptor;
@@ -60,8 +60,9 @@ fn main() -> anyhow::Result<()> {
6060
recipient_addr.script_pubkey(),
6161
Amount::from_sat(21_000_000),
6262
)],
63-
internal.at_derivation_index(0)?,
64-
bdk_tx::ChangePolicyType::NoDustAndLeastWaste { longterm_feerate },
63+
ScriptSource::Descriptor(Box::new(internal.at_derivation_index(0)?)),
64+
ChangePolicyType::NoDustAndLeastWaste { longterm_feerate },
65+
wallet.change_weight(),
6566
),
6667
)?;
6768

@@ -134,8 +135,11 @@ fn main() -> anyhow::Result<()> {
134135
// be less wasteful to have no output, however that will not be a valid tx).
135136
// If you only want to fee bump, put the original txs' recipients here.
136137
target_outputs: vec![],
137-
change_descriptor: internal.at_derivation_index(1)?,
138+
change_script: ScriptSource::Descriptor(Box::new(
139+
internal.at_derivation_index(1)?,
140+
)),
138141
change_policy: ChangePolicyType::NoDustAndLeastWaste { longterm_feerate },
142+
change_weight: wallet.change_weight(),
139143
// This ensures that we satisfy mempool-replacement policy rules 4 and 6.
140144
replace: Some(rbf_params),
141145
},

src/output.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ impl From<(DefiniteDescriptor, Amount)> for Output {
7474
}
7575
}
7676

77+
impl From<(ScriptSource, Amount)> for Output {
78+
fn from((src, value): (ScriptSource, Amount)) -> Self {
79+
match src {
80+
ScriptSource::Descriptor(desc) => Self::with_descriptor(*desc, value),
81+
ScriptSource::Script(s) => Self::with_script(s, value),
82+
}
83+
}
84+
}
85+
7786
impl Output {
7887
/// From script
7988
pub fn with_script(script: ScriptBuf, value: Amount) -> Self {

src/selector.rs

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use bdk_coin_select::{
22
ChangePolicy, DrainWeights, InsufficientFunds, Replace, Target, TargetFee, TargetOutputs,
33
};
4-
use bitcoin::{Amount, FeeRate, Transaction, TxOut, Weight};
4+
use bitcoin::{Amount, FeeRate, Transaction, Weight};
55
use miniscript::bitcoin;
66

7-
use crate::{cs_feerate, DefiniteDescriptor, InputCandidates, InputGroup, Output, Selection};
7+
use crate::{cs_feerate, InputCandidates, InputGroup, Output, ScriptSource, Selection};
88
use alloc::vec::Vec;
99
use core::fmt;
1010

@@ -15,7 +15,7 @@ pub struct Selector<'c> {
1515
target_outputs: Vec<Output>,
1616
target: Target,
1717
change_policy: bdk_coin_select::ChangePolicy,
18-
change_descriptor: DefiniteDescriptor,
18+
change_script: ScriptSource,
1919
inner: bdk_coin_select::CoinSelector<'c>,
2020
}
2121

@@ -42,11 +42,14 @@ pub struct SelectorParams {
4242
/// To derive change output.
4343
///
4444
/// Will error if this is unsatisfiable descriptor.
45-
pub change_descriptor: DefiniteDescriptor,
45+
pub change_script: ScriptSource,
4646

4747
/// The policy to determine whether we create a change output.
4848
pub change_policy: ChangePolicyType,
4949

50+
/// Weight of the change output plus the future weight to spend the change
51+
pub change_weight: DrainWeights,
52+
5053
/// Params for replacing tx(s).
5154
pub replace: Option<RbfParams>,
5255
}
@@ -140,14 +143,16 @@ impl SelectorParams {
140143
pub fn new(
141144
target_feerate: bitcoin::FeeRate,
142145
target_outputs: Vec<Output>,
143-
change_descriptor: DefiniteDescriptor,
146+
change_script: ScriptSource,
144147
change_policy: ChangePolicyType,
148+
change_weight: DrainWeights,
145149
) -> Self {
146150
Self {
147-
change_descriptor,
148-
change_policy,
149151
target_feerate,
150152
target_outputs,
153+
change_script,
154+
change_policy,
155+
change_weight,
151156
replace: None,
152157
}
153158
}
@@ -171,36 +176,14 @@ impl SelectorParams {
171176
}
172177
}
173178

174-
/// To change output weights.
175-
///
176-
/// # Error
177-
///
178-
/// Fails if `change_descriptor` cannot be satisfied.
179-
pub fn to_cs_change_weights(&self) -> Result<bdk_coin_select::DrainWeights, miniscript::Error> {
180-
Ok(DrainWeights {
181-
output_weight: (TxOut {
182-
script_pubkey: self.change_descriptor.script_pubkey(),
183-
value: Amount::ZERO,
184-
})
185-
.weight()
186-
.to_wu(),
187-
spend_weight: self.change_descriptor.max_weight_to_satisfy()?.to_wu(),
188-
n_outputs: 1,
189-
})
190-
}
191-
192179
/// To change policy.
193180
///
194181
/// # Error
195182
///
196183
/// Fails if `change_descriptor` cannot be satisfied.
197184
pub fn to_cs_change_policy(&self) -> Result<bdk_coin_select::ChangePolicy, miniscript::Error> {
198-
let change_weights = self.to_cs_change_weights()?;
199-
let dust_value = self
200-
.change_descriptor
201-
.script_pubkey()
202-
.minimal_non_dust()
203-
.to_sat();
185+
let change_weights = self.change_weight;
186+
let dust_value = self.change_script.script().minimal_non_dust().to_sat();
204187
Ok(match self.change_policy {
205188
ChangePolicyType::NoDust => ChangePolicy::min_value(change_weights, dust_value),
206189
ChangePolicyType::NoDustAndLeastWaste { longterm_feerate } => {
@@ -268,7 +251,7 @@ impl<'c> Selector<'c> {
268251
.to_cs_change_policy()
269252
.map_err(SelectorError::Miniscript)?;
270253
let target_outputs = params.target_outputs;
271-
let change_descriptor = params.change_descriptor;
254+
let change_script = params.change_script;
272255
if target.value() > candidates.groups().map(|grp| grp.value().to_sat()).sum() {
273256
return Err(SelectorError::CannotMeetTarget(CannotMeetTarget));
274257
}
@@ -281,7 +264,7 @@ impl<'c> Selector<'c> {
281264
target,
282265
target_outputs,
283266
change_policy,
284-
change_descriptor,
267+
change_script,
285268
inner,
286269
})
287270
}
@@ -358,10 +341,10 @@ impl<'c> Selector<'c> {
358341
outputs: {
359342
let mut outputs = self.target_outputs.clone();
360343
if maybe_change.is_some() {
361-
outputs.push(Output::with_descriptor(
362-
self.change_descriptor.clone(),
344+
outputs.push(Output::from((
345+
self.change_script.clone(),
363346
Amount::from_sat(maybe_change.value),
364-
));
347+
)));
365348
}
366349
outputs
367350
},

src/signer.rs

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use alloc::string::ToString;
2+
use alloc::vec::Vec;
3+
use std::collections::BTreeMap;
24

35
use bitcoin::{
46
psbt::{GetKey, GetKeyError, KeyRequest},
@@ -24,9 +26,10 @@ impl GetKey for Signer {
2426
for entry in &self.0 {
2527
match entry {
2628
(_, DescriptorSecretKey::Single(prv)) => {
27-
let pk = prv.key.public_key(secp);
28-
if key_request == KeyRequest::Pubkey(pk) {
29-
return Ok(Some(prv.key));
29+
let map: BTreeMap<_, _> =
30+
core::iter::once((prv.key.public_key(secp), prv.key)).collect();
31+
if let Ok(Some(prv)) = GetKey::get_key(&map, key_request.clone(), secp) {
32+
return Ok(Some(prv));
3033
}
3134
}
3235
(_, desc_sk) => {
@@ -44,7 +47,11 @@ impl GetKey for Signer {
4447
if fingerprint == fp
4548
&& derivation.to_string().starts_with(&path.to_string())
4649
{
47-
let to_derive = &derivation[k.xkey.depth as usize..];
50+
let to_derive = derivation
51+
.into_iter()
52+
.skip(path.len())
53+
.cloned()
54+
.collect::<Vec<_>>();
4855
let derived = k.xkey.derive_priv(secp, &to_derive)?;
4956
return Ok(Some(derived.to_priv()));
5057
}
@@ -61,6 +68,8 @@ impl GetKey for Signer {
6168

6269
#[cfg(test)]
6370
mod test {
71+
use crate::bitcoin::bip32::ChildNumber;
72+
use core::str::FromStr;
6473
use std::string::String;
6574

6675
use bitcoin::bip32::{DerivationPath, Xpriv};
@@ -89,6 +98,26 @@ mod test {
8998
Ok(())
9099
}
91100

101+
#[test]
102+
fn get_key_x_only_pubkey() -> anyhow::Result<()> {
103+
let secp = Secp256k1::new();
104+
let wif = "cU6BxEezV8FnkEPBCaFtc4WNuUKmgFaAu6sJErB154GXgMUjhgWe";
105+
let prv = bitcoin::PrivateKey::from_wif(wif)?;
106+
let (x_only_pk, _parity) = prv.inner.x_only_public_key(&secp);
107+
108+
let s = format!("wpkh({wif})");
109+
let (_, keymap) = Descriptor::parse_descriptor(&secp, &s).unwrap();
110+
111+
let signer = Signer(keymap);
112+
let req = KeyRequest::XOnlyPubkey(x_only_pk);
113+
let res = signer.get_key(req, &secp);
114+
assert!(matches!(
115+
res,
116+
Ok(Some(k)) if k.inner.x_only_public_key(&secp).0 == x_only_pk
117+
));
118+
Ok(())
119+
}
120+
92121
// Test `Signer` can fulfill a bip32 KeyRequest if we know the key origin
93122
#[test]
94123
fn get_key_bip32() -> anyhow::Result<()> {
@@ -141,4 +170,35 @@ mod test {
141170

142171
Ok(())
143172
}
173+
174+
#[test]
175+
fn get_key_xpriv_with_key_origin() -> anyhow::Result<()> {
176+
let secp = Secp256k1::new();
177+
let s = "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)";
178+
let (_, keymap) = Descriptor::parse_descriptor(&secp, s)?;
179+
180+
let desc_sk = DescriptorSecretKey::from_str("[d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*")?;
181+
let desc_xkey = match desc_sk {
182+
DescriptorSecretKey::XPrv(k) => k,
183+
_ => panic!(),
184+
};
185+
186+
let (fp, _) = desc_xkey.origin.clone().unwrap();
187+
let path = DerivationPath::from_str("84h/1h/0h/7")?;
188+
let req = KeyRequest::Bip32((fp, path));
189+
190+
let exp_prv = desc_xkey
191+
.xkey
192+
.derive_priv(&secp, &[ChildNumber::from(7)])?
193+
.to_priv();
194+
195+
let res = Signer(keymap).get_key(req, &secp);
196+
197+
assert!(matches!(
198+
res,
199+
Ok(Some(k)) if k == exp_prv,
200+
));
201+
202+
Ok(())
203+
}
144204
}

0 commit comments

Comments
 (0)