Move everything else over 🎉

This completes the move of things from https://github.com/LLFourn/bdk_core_staging
This commit is contained in:
LLFourn
2023-03-02 16:23:06 +11:00
committed by Daniela Brozzoni
parent 949608ab1f
commit c069b0fb41
37 changed files with 5023 additions and 15 deletions

5
nursery/README.md Normal file
View File

@@ -0,0 +1,5 @@
# Bitcoin Dev Kit Nursery
This is a directory for crates that are experimental and have not been released yet.
Keep in mind that they may never be released.
Things in `/example-crates` may use them to demonsrate how things might look in the future.

View File

@@ -0,0 +1,11 @@
[package]
name = "bdk_coin_select"
version = "0.0.1"
authors = [ "LLFourn <lloyd.fourn@gmail.com>" ]
[dependencies]
bdk_chain = { version = "0.3", path = "../../crates/chain" }
[features]
default = ["std"]
std = []

View File

@@ -0,0 +1,651 @@
use super::*;
/// Strategy in which we should branch.
pub enum BranchStrategy {
/// We continue exploring subtrees of this node, starting with the inclusion branch.
Continue,
/// We continue exploring ONY the omission branch of this node, skipping the inclusion branch.
SkipInclusion,
/// We skip both the inclusion and omission branches of this node.
SkipBoth,
}
impl BranchStrategy {
pub fn will_continue(&self) -> bool {
match self {
Self::Continue | Self::SkipInclusion => true,
_ => false,
}
}
}
/// Closure to decide the branching strategy, alongside a score (if the current selection is a
/// candidate solution).
pub type DecideStrategy<'c, S> = dyn Fn(&Bnb<'c, S>) -> (BranchStrategy, Option<S>);
/// [`Bnb`] represents the current state of the BnB algorithm.
pub struct Bnb<'c, S> {
pub pool: Vec<(usize, &'c WeightedValue)>,
pub pool_pos: usize,
pub best_score: S,
pub selection: CoinSelector<'c>,
pub rem_abs: u64,
pub rem_eff: i64,
}
impl<'c, S: Ord> Bnb<'c, S> {
/// Creates a new [`Bnb`].
pub fn new(selector: CoinSelector<'c>, pool: Vec<(usize, &'c WeightedValue)>, max: S) -> Self {
let (rem_abs, rem_eff) = pool.iter().fold((0, 0), |(abs, eff), (_, c)| {
(
abs + c.value,
eff + c.effective_value(selector.opts.target_feerate),
)
});
Self {
pool,
pool_pos: 0,
best_score: max,
selection: selector,
rem_abs,
rem_eff,
}
}
/// Turns our [`Bnb`] state into an iterator.
///
/// `strategy` should assess our current selection/node and determine the branching strategy and
/// whether this selection is a candidate solution (if so, return the score of the selection).
pub fn into_iter<'f>(self, strategy: &'f DecideStrategy<'c, S>) -> BnbIter<'c, 'f, S> {
BnbIter {
state: self,
done: false,
strategy,
}
}
/// Attempt to backtrack to the previously selected node's omission branch, return false
/// otherwise (no more solutions).
pub fn backtrack(&mut self) -> bool {
(0..self.pool_pos)
.rev()
.find(|&pos| {
let (index, candidate) = self.pool[pos];
if self.selection.is_selected(index) {
// deselect last `pos`, so next round will check omission branch
self.pool_pos = pos;
self.selection.deselect(index);
return true;
} else {
self.rem_abs += candidate.value;
self.rem_eff += candidate.effective_value(self.selection.opts.target_feerate);
return false;
}
})
.is_some()
}
/// Continue down this branch, skip inclusion branch if specified.
pub fn forward(&mut self, skip: bool) {
let (index, candidate) = self.pool[self.pool_pos];
self.rem_abs -= candidate.value;
self.rem_eff -= candidate.effective_value(self.selection.opts.target_feerate);
if !skip {
self.selection.select(index);
}
}
/// Compare advertised score with current best. New best will be the smaller value. Return true
/// if best is replaced.
pub fn advertise_new_score(&mut self, score: S) -> bool {
if score <= self.best_score {
self.best_score = score;
return true;
}
return false;
}
}
pub struct BnbIter<'c, 'f, S> {
state: Bnb<'c, S>,
done: bool,
/// Check our current selection (node), and returns the branching strategy, alongside a score
/// (if the current selection is a candidate solution).
strategy: &'f DecideStrategy<'c, S>,
}
impl<'c, 'f, S: Ord + Copy + Display> Iterator for BnbIter<'c, 'f, S> {
type Item = Option<CoinSelector<'c>>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
let (strategy, score) = (self.strategy)(&self.state);
let mut found_best = Option::<CoinSelector>::None;
if let Some(score) = score {
if self.state.advertise_new_score(score) {
found_best = Some(self.state.selection.clone());
}
}
debug_assert!(
!strategy.will_continue() || self.state.pool_pos < self.state.pool.len(),
"Faulty strategy implementation! Strategy suggested that we continue traversing, however we have already reached the end of the candidates pool! pool_len={}, pool_pos={}",
self.state.pool.len(), self.state.pool_pos,
);
match strategy {
BranchStrategy::Continue => {
self.state.forward(false);
}
BranchStrategy::SkipInclusion => {
self.state.forward(true);
}
BranchStrategy::SkipBoth => {
if !self.state.backtrack() {
self.done = true;
}
}
};
// increment selection pool position for next round
self.state.pool_pos += 1;
if found_best.is_some() || !self.done {
Some(found_best)
} else {
// we have traversed all branches
None
}
}
}
/// Determines how we should limit rounds of branch and bound.
pub enum BnbLimit {
Rounds(usize),
#[cfg(feature = "std")]
Duration(core::time::Duration),
}
impl From<usize> for BnbLimit {
fn from(v: usize) -> Self {
Self::Rounds(v)
}
}
#[cfg(feature = "std")]
impl From<core::time::Duration> for BnbLimit {
fn from(v: core::time::Duration) -> Self {
Self::Duration(v)
}
}
/// This is a variation of the Branch and Bound Coin Selection algorithm designed by Murch (as seen
/// in Bitcoin Core).
///
/// The differences are as follows:
/// * In additional to working with effective values, we also work with absolute values.
/// This way, we can use bounds of absolute values to enforce `min_absolute_fee` (which is used by
/// RBF), and `max_extra_target` (which can be used to increase the possible solution set, given
/// that the sender is okay with sending extra to the receiver).
///
/// Murch's Master Thesis: <https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf>
/// Bitcoin Core Implementation: <https://github.com/bitcoin/bitcoin/blob/23.x/src/wallet/coinselection.cpp#L65>
///
/// TODO: Another optimization we could do is figure out candidate with smallest waste, and
/// if we find a result with waste equal to this, we can just break.
pub fn coin_select_bnb<L>(limit: L, selector: CoinSelector) -> Option<CoinSelector>
where
L: Into<BnbLimit>,
{
let opts = selector.opts;
// prepare pool of candidates to select from:
// * filter out candidates with negative/zero effective values
// * sort candidates by descending effective value
let pool = {
let mut pool = selector
.unselected()
.filter(|(_, c)| c.effective_value(opts.target_feerate) > 0)
.collect::<Vec<_>>();
pool.sort_unstable_by(|(_, a), (_, b)| {
let a = a.effective_value(opts.target_feerate);
let b = b.effective_value(opts.target_feerate);
b.cmp(&a)
});
pool
};
let feerate_decreases = opts.target_feerate > opts.long_term_feerate();
let target_abs = opts.target_value.unwrap_or(0) + opts.min_absolute_fee;
let target_eff = selector.effective_target();
let upper_bound_abs = target_abs + (opts.drain_weight as f32 * opts.target_feerate) as u64;
let upper_bound_eff = target_eff + opts.drain_waste();
let strategy = move |bnb: &Bnb<i64>| -> (BranchStrategy, Option<i64>) {
let selected_abs = bnb.selection.selected_absolute_value();
let selected_eff = bnb.selection.selected_effective_value();
// backtrack if remaining value is not enough to reach target
if selected_abs + bnb.rem_abs < target_abs || selected_eff + bnb.rem_eff < target_eff {
return (BranchStrategy::SkipBoth, None);
}
// backtrack if selected value already surpassed upper bounds
if selected_abs > upper_bound_abs && selected_eff > upper_bound_eff {
return (BranchStrategy::SkipBoth, None);
}
let selected_waste = bnb.selection.selected_waste();
// when feerate decreases, waste without excess is guaranteed to increase with each
// selection. So if we have already surpassed best score, we can backtrack.
if feerate_decreases && selected_waste > bnb.best_score {
return (BranchStrategy::SkipBoth, None);
}
// solution?
if selected_abs >= target_abs && selected_eff >= target_eff {
let waste = selected_waste + bnb.selection.current_excess();
return (BranchStrategy::SkipBoth, Some(waste));
}
// early bailout optimization:
// If the candidate at the previous position is NOT selected and has the same weight and
// value as the current candidate, we can skip selecting the current candidate.
if bnb.pool_pos > 0 && !bnb.selection.is_empty() {
let (_, candidate) = bnb.pool[bnb.pool_pos];
let (prev_index, prev_candidate) = bnb.pool[bnb.pool_pos - 1];
if !bnb.selection.is_selected(prev_index)
&& candidate.value == prev_candidate.value
&& candidate.weight == prev_candidate.weight
{
return (BranchStrategy::SkipInclusion, None);
}
}
// check out inclusion branch first
return (BranchStrategy::Continue, None);
};
// determine sum of absolute and effective values for current selection
let (selected_abs, selected_eff) = selector.selected().fold((0, 0), |(abs, eff), (_, c)| {
(
abs + c.value,
eff + c.effective_value(selector.opts.target_feerate),
)
});
let bnb = Bnb::new(selector, pool, i64::MAX);
// not enough to select anyway
if selected_abs + bnb.rem_abs < target_abs || selected_eff + bnb.rem_eff < target_eff {
return None;
}
match limit.into() {
BnbLimit::Rounds(rounds) => {
bnb.into_iter(&strategy)
.take(rounds)
.reduce(|b, c| if c.is_some() { c } else { b })
}
#[cfg(feature = "std")]
BnbLimit::Duration(duration) => {
let start = std::time::SystemTime::now();
bnb.into_iter(&strategy)
.take_while(|_| start.elapsed().expect("failed to get system time") <= duration)
.reduce(|b, c| if c.is_some() { c } else { b })
}
}?
}
#[cfg(all(test, feature = "miniscript"))]
mod test {
use bitcoin::secp256k1::Secp256k1;
use crate::coin_select::{evaluate_cs::evaluate, ExcessStrategyKind};
use super::{
coin_select_bnb,
evaluate_cs::{Evaluation, EvaluationError},
tester::Tester,
CoinSelector, CoinSelectorOpt, Vec, WeightedValue,
};
fn tester() -> Tester {
const DESC_STR: &str = "tr(xprv9uBuvtdjghkz8D1qzsSXS9Vs64mqrUnXqzNccj2xcvnCHPpXKYE1U2Gbh9CDHk8UPyF2VuXpVkDA7fk5ZP4Hd9KnhUmTscKmhee9Dp5sBMK)";
Tester::new(&Secp256k1::default(), DESC_STR)
}
fn evaluate_bnb(
initial_selector: CoinSelector,
max_tries: usize,
) -> Result<Evaluation, EvaluationError> {
evaluate(initial_selector, |cs| {
coin_select_bnb(max_tries, cs.clone()).map_or(false, |new_cs| {
*cs = new_cs;
true
})
})
}
#[test]
fn not_enough_coins() {
let t = tester();
let candidates: Vec<WeightedValue> = vec![
t.gen_candidate(0, 100_000).into(),
t.gen_candidate(1, 100_000).into(),
];
let opts = t.gen_opts(200_000);
let selector = CoinSelector::new(&candidates, &opts);
assert!(!coin_select_bnb(10_000, selector).is_some());
}
#[test]
fn exactly_enough_coins_preselected() {
let t = tester();
let candidates: Vec<WeightedValue> = vec![
t.gen_candidate(0, 100_000).into(), // to preselect
t.gen_candidate(1, 100_000).into(), // to preselect
t.gen_candidate(2, 100_000).into(),
];
let opts = CoinSelectorOpt {
target_feerate: 0.0,
..t.gen_opts(200_000)
};
let selector = {
let mut selector = CoinSelector::new(&candidates, &opts);
selector.select(0); // preselect
selector.select(1); // preselect
selector
};
let evaluation = evaluate_bnb(selector, 10_000).expect("eval failed");
println!("{}", evaluation);
assert_eq!(evaluation.solution.selected, (0..=1).collect());
assert_eq!(evaluation.solution.excess_strategies.len(), 1);
assert_eq!(
evaluation.feerate_offset(ExcessStrategyKind::ToFee).floor(),
0.0
);
}
/// `cost_of_change` acts as the upper-bound in Bnb, we check whether these boundaries are
/// enforced in code
#[test]
fn cost_of_change() {
let t = tester();
let candidates: Vec<WeightedValue> = vec![
t.gen_candidate(0, 200_000).into(),
t.gen_candidate(1, 200_000).into(),
t.gen_candidate(2, 200_000).into(),
];
// lowest and highest possible `recipient_value` opts for derived `drain_waste`, assuming
// that we want 2 candidates selected
let (lowest_opts, highest_opts) = {
let opts = t.gen_opts(0);
let fee_from_inputs =
(candidates[0].weight as f32 * opts.target_feerate).ceil() as u64 * 2;
let fee_from_template =
((opts.base_weight + 2) as f32 * opts.target_feerate).ceil() as u64;
let lowest_opts = CoinSelectorOpt {
target_value: Some(
400_000 - fee_from_inputs - fee_from_template - opts.drain_waste() as u64,
),
..opts
};
let highest_opts = CoinSelectorOpt {
target_value: Some(400_000 - fee_from_inputs - fee_from_template),
..opts
};
(lowest_opts, highest_opts)
};
// test lowest possible target we are able to select
let lowest_eval = evaluate_bnb(CoinSelector::new(&candidates, &lowest_opts), 10_000);
assert!(lowest_eval.is_ok());
let lowest_eval = lowest_eval.unwrap();
println!("LB {}", lowest_eval);
assert_eq!(lowest_eval.solution.selected.len(), 2);
assert_eq!(lowest_eval.solution.excess_strategies.len(), 1);
assert_eq!(
lowest_eval
.feerate_offset(ExcessStrategyKind::ToFee)
.floor(),
0.0
);
// test highest possible target we are able to select
let highest_eval = evaluate_bnb(CoinSelector::new(&candidates, &highest_opts), 10_000);
assert!(highest_eval.is_ok());
let highest_eval = highest_eval.unwrap();
println!("UB {}", highest_eval);
assert_eq!(highest_eval.solution.selected.len(), 2);
assert_eq!(highest_eval.solution.excess_strategies.len(), 1);
assert_eq!(
highest_eval
.feerate_offset(ExcessStrategyKind::ToFee)
.floor(),
0.0
);
// test lower out of bounds
let loob_opts = CoinSelectorOpt {
target_value: lowest_opts.target_value.map(|v| v - 1),
..lowest_opts
};
let loob_eval = evaluate_bnb(CoinSelector::new(&candidates, &loob_opts), 10_000);
assert!(loob_eval.is_err());
println!("Lower OOB: {}", loob_eval.unwrap_err());
// test upper out of bounds
let uoob_opts = CoinSelectorOpt {
target_value: highest_opts.target_value.map(|v| v + 1),
..highest_opts
};
let uoob_eval = evaluate_bnb(CoinSelector::new(&candidates, &uoob_opts), 10_000);
assert!(uoob_eval.is_err());
println!("Upper OOB: {}", uoob_eval.unwrap_err());
}
#[test]
fn try_select() {
let t = tester();
let candidates: Vec<WeightedValue> = vec![
t.gen_candidate(0, 300_000).into(),
t.gen_candidate(1, 300_000).into(),
t.gen_candidate(2, 300_000).into(),
t.gen_candidate(3, 200_000).into(),
t.gen_candidate(4, 200_000).into(),
];
let make_opts = |v: u64| -> CoinSelectorOpt {
CoinSelectorOpt {
target_feerate: 0.0,
..t.gen_opts(v)
}
};
let test_cases = vec![
(make_opts(100_000), false, 0),
(make_opts(200_000), true, 1),
(make_opts(300_000), true, 1),
(make_opts(500_000), true, 2),
(make_opts(1_000_000), true, 4),
(make_opts(1_200_000), false, 0),
(make_opts(1_300_000), true, 5),
(make_opts(1_400_000), false, 0),
];
for (opts, expect_solution, expect_selected) in test_cases {
let res = evaluate_bnb(CoinSelector::new(&candidates, &opts), 10_000);
assert_eq!(res.is_ok(), expect_solution);
match res {
Ok(eval) => {
println!("{}", eval);
assert_eq!(eval.feerate_offset(ExcessStrategyKind::ToFee), 0.0);
assert_eq!(eval.solution.selected.len(), expect_selected as _);
}
Err(err) => println!("expected failure: {}", err),
}
}
}
#[test]
fn early_bailout_optimization() {
let t = tester();
// target: 300_000
// candidates: 2x of 125_000, 1000x of 100_000, 1x of 50_000
// expected solution: 2x 125_000, 1x 50_000
// set bnb max tries: 1100, should succeed
let candidates = {
let mut candidates: Vec<WeightedValue> = vec![
t.gen_candidate(0, 125_000).into(),
t.gen_candidate(1, 125_000).into(),
t.gen_candidate(2, 50_000).into(),
];
(3..3 + 1000_u32)
.for_each(|index| candidates.push(t.gen_candidate(index, 100_000).into()));
candidates
};
let opts = CoinSelectorOpt {
target_feerate: 0.0,
..t.gen_opts(300_000)
};
let result = evaluate_bnb(CoinSelector::new(&candidates, &opts), 1100);
assert!(result.is_ok());
let eval = result.unwrap();
println!("{}", eval);
assert_eq!(eval.solution.selected, (0..=2).collect());
}
#[test]
fn should_exhaust_iteration() {
static MAX_TRIES: usize = 1000;
let t = tester();
let candidates = (0..MAX_TRIES + 1)
.map(|index| t.gen_candidate(index as _, 10_000).into())
.collect::<Vec<WeightedValue>>();
let opts = t.gen_opts(10_001 * MAX_TRIES as u64);
let result = evaluate_bnb(CoinSelector::new(&candidates, &opts), MAX_TRIES);
assert!(result.is_err());
println!("error as expected: {}", result.unwrap_err());
}
/// Solution should have fee >= min_absolute_fee (or no solution at all)
#[test]
fn min_absolute_fee() {
let t = tester();
let candidates = {
let mut candidates = Vec::new();
t.gen_weighted_values(&mut candidates, 5, 10_000);
t.gen_weighted_values(&mut candidates, 5, 20_000);
t.gen_weighted_values(&mut candidates, 5, 30_000);
t.gen_weighted_values(&mut candidates, 10, 10_300);
t.gen_weighted_values(&mut candidates, 10, 10_500);
t.gen_weighted_values(&mut candidates, 10, 10_700);
t.gen_weighted_values(&mut candidates, 10, 10_900);
t.gen_weighted_values(&mut candidates, 10, 11_000);
t.gen_weighted_values(&mut candidates, 10, 12_000);
t.gen_weighted_values(&mut candidates, 10, 13_000);
candidates
};
let mut opts = CoinSelectorOpt {
min_absolute_fee: 1,
..t.gen_opts(100_000)
};
(1..=120_u64).for_each(|fee_factor| {
opts.min_absolute_fee = fee_factor * 31;
let result = evaluate_bnb(CoinSelector::new(&candidates, &opts), 21_000);
match result {
Ok(result) => {
println!("Solution {}", result);
let fee = result.solution.excess_strategies[&ExcessStrategyKind::ToFee].fee;
assert!(fee >= opts.min_absolute_fee);
assert_eq!(result.solution.excess_strategies.len(), 1);
}
Err(err) => {
println!("No Solution: {}", err);
}
}
});
}
/// For a decreasing feerate (longterm feerate is lower than effective feerate), we should
/// select less. For increasing feerate (longterm feerate is higher than effective feerate), we
/// should select more.
#[test]
fn feerate_difference() {
let t = tester();
let candidates = {
let mut candidates = Vec::new();
t.gen_weighted_values(&mut candidates, 10, 2_000);
t.gen_weighted_values(&mut candidates, 10, 5_000);
t.gen_weighted_values(&mut candidates, 10, 20_000);
candidates
};
let decreasing_feerate_opts = CoinSelectorOpt {
target_feerate: 1.25,
long_term_feerate: Some(0.25),
..t.gen_opts(100_000)
};
let increasing_feerate_opts = CoinSelectorOpt {
target_feerate: 0.25,
long_term_feerate: Some(1.25),
..t.gen_opts(100_000)
};
let decreasing_res = evaluate_bnb(
CoinSelector::new(&candidates, &decreasing_feerate_opts),
21_000,
)
.expect("no result");
let decreasing_len = decreasing_res.solution.selected.len();
let increasing_res = evaluate_bnb(
CoinSelector::new(&candidates, &increasing_feerate_opts),
21_000,
)
.expect("no result");
let increasing_len = increasing_res.solution.selected.len();
println!("decreasing_len: {}", decreasing_len);
println!("increasing_len: {}", increasing_len);
assert!(decreasing_len < increasing_len);
}
/// TODO: UNIMPLEMENTED TESTS:
/// * Excess strategies:
/// * We should always have `ExcessStrategy::ToFee`.
/// * We should only have `ExcessStrategy::ToRecipient` when `max_extra_target > 0`.
/// * We should only have `ExcessStrategy::ToDrain` when `drain_value >= min_drain_value`.
/// * Fuzz
/// * Solution feerate should never be lower than target feerate
/// * Solution fee should never be lower than `min_absolute_fee`
/// * Preselected should always remain selected
fn _todo() {}
}

View File

@@ -0,0 +1,617 @@
use super::*;
/// A [`WeightedValue`] represents an input candidate for [`CoinSelector`]. This can either be a
/// single UTXO, or a group of UTXOs that should be spent together.
#[derive(Debug, Clone, Copy)]
pub struct WeightedValue {
/// Total value of the UTXO(s) that this [`WeightedValue`] represents.
pub value: u64,
/// Total weight of including this/these UTXO(s).
/// `txin` fields: `prevout`, `nSequence`, `scriptSigLen`, `scriptSig`, `scriptWitnessLen`,
/// `scriptWitness` should all be included.
pub weight: u32,
/// Total number of inputs; so we can calculate extra `varint` weight due to `vin` len changes.
pub input_count: usize,
/// Whether this [`WeightedValue`] contains at least one segwit spend.
pub is_segwit: bool,
}
impl WeightedValue {
/// Create a new [`WeightedValue`] that represents a single input.
///
/// `satisfaction_weight` is the weight of `scriptSigLen + scriptSig + scriptWitnessLen +
/// scriptWitness`.
pub fn new(value: u64, satisfaction_weight: u32, is_segwit: bool) -> WeightedValue {
let weight = TXIN_BASE_WEIGHT + satisfaction_weight;
WeightedValue {
value,
weight,
input_count: 1,
is_segwit,
}
}
/// Effective value of this input candidate: `actual_value - input_weight * feerate (sats/wu)`.
pub fn effective_value(&self, effective_feerate: f32) -> i64 {
// We prefer undershooting the candidate's effective value (so we over estimate the fee of a
// candidate). If we overshoot the candidate's effective value, it may be possible to find a
// solution which does not meet the target feerate.
self.value as i64 - (self.weight as f32 * effective_feerate).ceil() as i64
}
}
#[derive(Debug, Clone, Copy)]
pub struct CoinSelectorOpt {
/// The value we need to select.
/// If the value is `None` then the selection will be complete if it can pay for the drain
/// output and satisfy the other constraints (e.g. minimum fees).
pub target_value: Option<u64>,
/// Additional leeway for the target value.
pub max_extra_target: u64, // TODO: Maybe out of scope here?
/// The feerate we should try and achieve in sats per weight unit.
pub target_feerate: f32,
/// The feerate
pub long_term_feerate: Option<f32>, // TODO: Maybe out of scope? (waste)
/// The minimum absolute fee. I.e. needed for RBF.
pub min_absolute_fee: u64,
/// The weight of the template transaction including fixed fields and outputs.
pub base_weight: u32,
/// Additional weight if we include the drain (change) output.
pub drain_weight: u32,
/// Weight of spending the drain (change) output in the future.
pub spend_drain_weight: u32, // TODO: Maybe out of scope? (waste)
/// Minimum value allowed for a drain (change) output.
pub min_drain_value: u64,
}
impl CoinSelectorOpt {
fn from_weights(base_weight: u32, drain_weight: u32, spend_drain_weight: u32) -> Self {
// 0.25 sats/wu == 1 sat/vb
let target_feerate = 0.25_f32;
// set `min_drain_value` to dust limit
let min_drain_value =
3 * ((drain_weight + spend_drain_weight) as f32 * target_feerate) as u64;
Self {
target_value: None,
max_extra_target: 0,
target_feerate,
long_term_feerate: None,
min_absolute_fee: 0,
base_weight,
drain_weight,
spend_drain_weight,
min_drain_value,
}
}
pub fn fund_outputs(
txouts: &[TxOut],
drain_output: &TxOut,
drain_satisfaction_weight: u32,
) -> Self {
let mut tx = Transaction {
input: vec![],
version: 1,
lock_time: LockTime::ZERO.into(),
output: txouts.to_vec(),
};
let base_weight = tx.weight();
// this awkward calculation is necessary since TxOut doesn't have \.weight()
let drain_weight = {
tx.output.push(drain_output.clone());
tx.weight() - base_weight
};
Self {
target_value: if txouts.is_empty() {
None
} else {
Some(txouts.iter().map(|txout| txout.value).sum())
},
..Self::from_weights(
base_weight as u32,
drain_weight as u32,
TXIN_BASE_WEIGHT + drain_satisfaction_weight,
)
}
}
pub fn long_term_feerate(&self) -> f32 {
self.long_term_feerate.unwrap_or(self.target_feerate)
}
pub fn drain_waste(&self) -> i64 {
(self.drain_weight as f32 * self.target_feerate
+ self.spend_drain_weight as f32 * self.long_term_feerate()) as i64
}
}
/// [`CoinSelector`] is responsible for selecting and deselecting from a set of canididates.
#[derive(Debug, Clone)]
pub struct CoinSelector<'a> {
pub opts: &'a CoinSelectorOpt,
pub candidates: &'a Vec<WeightedValue>,
selected: BTreeSet<usize>,
}
impl<'a> CoinSelector<'a> {
pub fn candidate(&self, index: usize) -> &WeightedValue {
&self.candidates[index]
}
pub fn new(candidates: &'a Vec<WeightedValue>, opts: &'a CoinSelectorOpt) -> Self {
Self {
candidates,
selected: Default::default(),
opts,
}
}
pub fn select(&mut self, index: usize) -> bool {
assert!(index < self.candidates.len());
self.selected.insert(index)
}
pub fn deselect(&mut self, index: usize) -> bool {
self.selected.remove(&index)
}
pub fn is_selected(&self, index: usize) -> bool {
self.selected.contains(&index)
}
pub fn is_empty(&self) -> bool {
self.selected.is_empty()
}
/// Weight sum of all selected inputs.
pub fn selected_weight(&self) -> u32 {
self.selected
.iter()
.map(|&index| self.candidates[index].weight)
.sum()
}
/// Effective value sum of all selected inputs.
pub fn selected_effective_value(&self) -> i64 {
self.selected
.iter()
.map(|&index| self.candidates[index].effective_value(self.opts.target_feerate))
.sum()
}
/// Absolute value sum of all selected inputs.
pub fn selected_absolute_value(&self) -> u64 {
self.selected
.iter()
.map(|&index| self.candidates[index].value)
.sum()
}
/// Waste sum of all selected inputs.
pub fn selected_waste(&self) -> i64 {
(self.selected_weight() as f32 * (self.opts.target_feerate - self.opts.long_term_feerate()))
as i64
}
/// Current weight of template tx + selected inputs.
pub fn current_weight(&self) -> u32 {
let witness_header_extra_weight = self
.selected()
.find(|(_, wv)| wv.is_segwit)
.map(|_| 2)
.unwrap_or(0);
let vin_count_varint_extra_weight = {
let input_count = self.selected().map(|(_, wv)| wv.input_count).sum::<usize>();
(varint_size(input_count) - 1) * 4
};
self.opts.base_weight
+ self.selected_weight()
+ witness_header_extra_weight
+ vin_count_varint_extra_weight
}
/// Current excess.
pub fn current_excess(&self) -> i64 {
self.selected_effective_value() - self.effective_target()
}
/// This is the effective target value.
pub fn effective_target(&self) -> i64 {
let (has_segwit, max_input_count) = self
.candidates
.iter()
.fold((false, 0_usize), |(is_segwit, input_count), c| {
(is_segwit || c.is_segwit, input_count + c.input_count)
});
let effective_base_weight = self.opts.base_weight
+ if has_segwit { 2_u32 } else { 0_u32 }
+ (varint_size(max_input_count) - 1) * 4;
self.opts.target_value.unwrap_or(0) as i64
+ (effective_base_weight as f32 * self.opts.target_feerate).ceil() as i64
}
pub fn selected_count(&self) -> usize {
self.selected.len()
}
pub fn selected(&self) -> impl Iterator<Item = (usize, &'a WeightedValue)> + '_ {
self.selected
.iter()
.map(move |&index| (index, &self.candidates[index]))
}
pub fn unselected(&self) -> impl Iterator<Item = (usize, &'a WeightedValue)> + '_ {
self.candidates
.iter()
.enumerate()
.filter(move |(index, _)| !self.selected.contains(index))
}
pub fn selected_indexes(&self) -> impl Iterator<Item = usize> + '_ {
self.selected.iter().cloned()
}
pub fn unselected_indexes(&self) -> impl Iterator<Item = usize> + '_ {
(0..self.candidates.len()).filter(move |index| !self.selected.contains(index))
}
pub fn all_selected(&self) -> bool {
self.selected.len() == self.candidates.len()
}
pub fn select_all(&mut self) {
self.selected = (0..self.candidates.len()).collect();
}
pub fn select_until_finished(&mut self) -> Result<Selection, SelectionError> {
let mut selection = self.finish();
if selection.is_ok() {
return selection;
}
let unselected = self.unselected_indexes().collect::<Vec<_>>();
for index in unselected {
self.select(index);
selection = self.finish();
if selection.is_ok() {
break;
}
}
selection
}
pub fn finish(&self) -> Result<Selection, SelectionError> {
let weight_without_drain = self.current_weight();
let weight_with_drain = weight_without_drain + self.opts.drain_weight;
let fee_without_drain =
(weight_without_drain as f32 * self.opts.target_feerate).ceil() as u64;
let fee_with_drain = (weight_with_drain as f32 * self.opts.target_feerate).ceil() as u64;
let inputs_minus_outputs = {
let target_value = self.opts.target_value.unwrap_or(0);
let selected = self.selected_absolute_value();
// find the largest unsatisfied constraint (if any), and return error of that constraint
// "selected" should always be greater than or equal to these selected values
[
(
SelectionConstraint::TargetValue,
target_value.saturating_sub(selected),
),
(
SelectionConstraint::TargetFee,
(target_value + fee_without_drain).saturating_sub(selected),
),
(
SelectionConstraint::MinAbsoluteFee,
(target_value + self.opts.min_absolute_fee).saturating_sub(selected),
),
(
SelectionConstraint::MinDrainValue,
// when we have no target value (hence no recipient txouts), we need to ensure
// the selected amount can satisfy requirements for a drain output (so we at
// least have one txout)
if self.opts.target_value.is_none() {
(fee_with_drain + self.opts.min_drain_value).saturating_sub(selected)
} else {
0
},
),
]
.iter()
.filter(|&(_, v)| v > &0)
.max_by_key(|&(_, v)| v)
.map_or(Ok(()), |(constraint, missing)| {
Err(SelectionError {
selected,
missing: *missing,
constraint: *constraint,
})
})?;
(selected - target_value) as u64
};
let fee_without_drain = fee_without_drain.max(self.opts.min_absolute_fee);
let fee_with_drain = fee_with_drain.max(self.opts.min_absolute_fee);
let excess_without_drain = inputs_minus_outputs - fee_without_drain;
let input_waste = self.selected_waste();
// begin preparing excess strategies for final selection
let mut excess_strategies = HashMap::new();
// only allow `ToFee` and `ToRecipient` excess strategies when we have a `target_value`,
// otherwise we will result in a result with no txouts, or attempt to add value to an output
// that does not exist
if self.opts.target_value.is_some() {
// no drain, excess to fee
excess_strategies.insert(
ExcessStrategyKind::ToFee,
ExcessStrategy {
recipient_value: self.opts.target_value,
drain_value: None,
fee: fee_without_drain + excess_without_drain,
weight: weight_without_drain,
waste: input_waste + excess_without_drain as i64,
},
);
// no drain, excess to recipient
// if `excess == 0`, this result will be the same as the previous, so don't consider it
// if `max_extra_target == 0`, there is no leeway for this strategy
if excess_without_drain > 0 && self.opts.max_extra_target > 0 {
let extra_recipient_value =
core::cmp::min(self.opts.max_extra_target, excess_without_drain);
let extra_fee = excess_without_drain - extra_recipient_value;
excess_strategies.insert(
ExcessStrategyKind::ToRecipient,
ExcessStrategy {
recipient_value: self.opts.target_value.map(|v| v + extra_recipient_value),
drain_value: None,
fee: fee_without_drain + extra_fee,
weight: weight_without_drain,
waste: input_waste + extra_fee as i64,
},
);
}
}
// with drain
if fee_with_drain >= self.opts.min_absolute_fee
&& inputs_minus_outputs >= fee_with_drain + self.opts.min_drain_value
{
excess_strategies.insert(
ExcessStrategyKind::ToDrain,
ExcessStrategy {
recipient_value: self.opts.target_value,
drain_value: Some(inputs_minus_outputs.saturating_sub(fee_with_drain)),
fee: fee_with_drain,
weight: weight_with_drain,
waste: input_waste + self.opts.drain_waste(),
},
);
}
debug_assert!(
!excess_strategies.is_empty(),
"should have at least one excess strategy"
);
Ok(Selection {
selected: self.selected.clone(),
excess: excess_without_drain,
excess_strategies,
})
}
}
#[derive(Clone, Debug)]
pub struct SelectionError {
selected: u64,
missing: u64,
constraint: SelectionConstraint,
}
impl core::fmt::Display for SelectionError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SelectionError {
selected,
missing,
constraint,
} => write!(
f,
"insufficient coins selected; selected={}, missing={}, unsatisfied_constraint={:?}",
selected, missing, constraint
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SelectionError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SelectionConstraint {
/// The target is not met
TargetValue,
/// The target fee (given the feerate) is not met
TargetFee,
/// Min absolute fee is not met
MinAbsoluteFee,
/// Min drain value is not met
MinDrainValue,
}
impl core::fmt::Display for SelectionConstraint {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SelectionConstraint::TargetValue => core::write!(f, "target_value"),
SelectionConstraint::TargetFee => core::write!(f, "target_fee"),
SelectionConstraint::MinAbsoluteFee => core::write!(f, "min_absolute_fee"),
SelectionConstraint::MinDrainValue => core::write!(f, "min_drain_value"),
}
}
}
#[derive(Clone, Debug)]
pub struct Selection {
pub selected: BTreeSet<usize>,
pub excess: u64,
pub excess_strategies: HashMap<ExcessStrategyKind, ExcessStrategy>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
pub enum ExcessStrategyKind {
ToFee,
ToRecipient,
ToDrain,
}
#[derive(Clone, Copy, Debug)]
pub struct ExcessStrategy {
pub recipient_value: Option<u64>,
pub drain_value: Option<u64>,
pub fee: u64,
pub weight: u32,
pub waste: i64,
}
impl Selection {
pub fn apply_selection<'a, T>(
&'a self,
candidates: &'a [T],
) -> impl Iterator<Item = &'a T> + 'a {
self.selected.iter().map(move |i| &candidates[*i])
}
/// Returns the [`ExcessStrategy`] that results in the least waste.
pub fn best_strategy(&self) -> (&ExcessStrategyKind, &ExcessStrategy) {
self.excess_strategies
.iter()
.min_by_key(|&(_, a)| a.waste)
.expect("selection has no excess strategy")
}
}
impl core::fmt::Display for ExcessStrategyKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ExcessStrategyKind::ToFee => core::write!(f, "to_fee"),
ExcessStrategyKind::ToRecipient => core::write!(f, "to_recipient"),
ExcessStrategyKind::ToDrain => core::write!(f, "to_drain"),
}
}
}
impl ExcessStrategy {
/// Returns feerate in sats/wu.
pub fn feerate(&self) -> f32 {
self.fee as f32 / self.weight as f32
}
}
#[cfg(test)]
mod test {
use crate::{ExcessStrategyKind, SelectionConstraint};
use super::{CoinSelector, CoinSelectorOpt, WeightedValue};
/// Ensure `target_value` is respected. Can't have no disrespect.
#[test]
fn target_value_respected() {
let target_value = 1000_u64;
let candidates = (500..1500_u64)
.map(|value| WeightedValue {
value,
weight: 100,
input_count: 1,
is_segwit: false,
})
.collect::<super::Vec<_>>();
let opts = CoinSelectorOpt {
target_value: Some(target_value),
max_extra_target: 0,
target_feerate: 0.00,
long_term_feerate: None,
min_absolute_fee: 0,
base_weight: 10,
drain_weight: 10,
spend_drain_weight: 10,
min_drain_value: 10,
};
for (index, v) in candidates.iter().enumerate() {
let mut selector = CoinSelector::new(&candidates, &opts);
assert!(selector.select(index));
let res = selector.finish();
if v.value < opts.target_value.unwrap_or(0) {
let err = res.expect_err("should have failed");
assert_eq!(err.selected, v.value);
assert_eq!(err.missing, target_value - v.value);
assert_eq!(err.constraint, SelectionConstraint::MinAbsoluteFee);
} else {
let sel = res.expect("should have succeeded");
assert_eq!(sel.excess, v.value - opts.target_value.unwrap_or(0));
}
}
}
#[test]
fn drain_all() {
let candidates = (0..100)
.map(|_| WeightedValue {
value: 666,
weight: 166,
input_count: 1,
is_segwit: false,
})
.collect::<super::Vec<_>>();
let opts = CoinSelectorOpt {
target_value: None,
max_extra_target: 0,
target_feerate: 0.25,
long_term_feerate: None,
min_absolute_fee: 0,
base_weight: 10,
drain_weight: 100,
spend_drain_weight: 66,
min_drain_value: 1000,
};
let selection = CoinSelector::new(&candidates, &opts)
.select_until_finished()
.expect("should succeed");
assert!(selection.selected.len() > 1);
assert_eq!(selection.excess_strategies.len(), 1);
let (kind, strategy) = selection.best_strategy();
assert_eq!(*kind, ExcessStrategyKind::ToDrain);
assert!(strategy.recipient_value.is_none());
assert!(strategy.drain_value.is_some());
}
/// TODO: Tests to add:
/// * `finish` should ensure at least `target_value` is selected.
/// * actual feerate should be equal or higher than `target_feerate`.
/// * actual drain value should be equal or higher than `min_drain_value` (or else no drain).
fn _todo() {}
}

View File

@@ -0,0 +1,33 @@
#![no_std]
#[cfg(feature = "std")]
extern crate std;
#[macro_use]
extern crate alloc;
extern crate bdk_chain;
use alloc::vec::Vec;
use bdk_chain::{
bitcoin,
collections::{BTreeSet, HashMap},
};
use bitcoin::{LockTime, Transaction, TxOut};
use core::fmt::{Debug, Display};
mod coin_selector;
pub use coin_selector::*;
mod bnb;
pub use bnb::*;
/// Txin "base" fields include `outpoint` (32+4) and `nSequence` (4). This does not include
/// `scriptSigLen` or `scriptSig`.
pub const TXIN_BASE_WEIGHT: u32 = (32 + 4 + 4) * 4;
/// Helper to calculate varint size. `v` is the value the varint represents.
// Shamelessly copied from
// https://github.com/rust-bitcoin/rust-miniscript/blob/d5615acda1a7fdc4041a11c1736af139b8c7ebe8/src/util.rs#L8
pub(crate) fn varint_size(v: usize) -> u32 {
bitcoin::VarInt(v as u64).len() as u32
}

View File

@@ -0,0 +1,13 @@
[package]
name = "bdk_tmp_plan"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bdk_chain = { version = "0.3", features = ["miniscript"], path = "../../crates/chain" }
[features]
default = ["std"]
std = []

View File

@@ -0,0 +1,3 @@
# Temporary planning module
A temporary place to hold the planning module until https://github.com/rust-bitcoin/rust-miniscript/pull/481 is merged and released

View File

@@ -0,0 +1,13 @@
[package]
name = "bdk_tmp_plan"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bdk_chain = { version = "0.3", features = ["miniscript"] }
[features]
default = ["std"]
std = []

View File

@@ -0,0 +1,3 @@
# Temporary planning module
A temporary place to hold the planning module until https://github.com/rust-bitcoin/rust-miniscript/pull/481 is merged and released

View File

@@ -0,0 +1,436 @@
#![allow(unused)]
#![allow(missing_docs)]
//! A spending plan or *plan* for short is a representation of a particular spending path on a
//! descriptor. This allows us to analayze a choice of spending path without producing any
//! signatures or other witness data for it.
//!
//! To make a plan you provide the descriptor with "assets" like which keys you are able to use, hash
//! pre-images you have access to, the current block height etc.
//!
//! Once you've got a plan it can tell you its expected satisfaction weight which can be useful for
//! doing coin selection. Furthermore it provides which subset of those keys and hash pre-images you
//! will actually need as well as what locktime or sequence number you need to set.
//!
//! Once you've obstained signatures, hash pre-images etc required by the plan, it can create a
//! witness/script_sig for the input.
use bdk_chain::{bitcoin, collections::*, miniscript};
use bitcoin::{
blockdata::{locktime::LockTime, transaction::Sequence},
hashes::{hash160, ripemd160, sha256},
secp256k1::Secp256k1,
util::{
address::WitnessVersion,
bip32::{DerivationPath, Fingerprint, KeySource},
taproot::{LeafVersion, TapBranchHash, TapLeafHash},
},
EcdsaSig, SchnorrSig, Script, TxIn, Witness,
};
use miniscript::{
descriptor::{InnerXKey, Tr},
hash256, DefiniteDescriptorKey, Descriptor, DescriptorPublicKey, ScriptContext, ToPublicKey,
};
pub(crate) fn varint_len(v: usize) -> usize {
bitcoin::VarInt(v as u64).len() as usize
}
mod plan_impls;
mod requirements;
mod template;
pub use requirements::*;
pub use template::PlanKey;
use template::TemplateItem;
#[derive(Clone, Debug)]
enum TrSpend {
KeySpend,
LeafSpend {
script: Script,
leaf_version: LeafVersion,
},
}
#[derive(Clone, Debug)]
enum Target {
Legacy,
Segwitv0 {
script_code: Script,
},
Segwitv1 {
tr: Tr<DefiniteDescriptorKey>,
tr_plan: TrSpend,
},
}
impl Target {}
#[derive(Clone, Debug)]
/// A plan represents a particular spending path for a descriptor.
///
/// See the module level documentation for more info.
pub struct Plan<AK> {
template: Vec<TemplateItem<AK>>,
target: Target,
set_locktime: Option<LockTime>,
set_sequence: Option<Sequence>,
}
impl Default for Target {
fn default() -> Self {
Target::Legacy
}
}
#[derive(Clone, Debug, Default)]
/// Signatures and hash pre-images that can be used to complete a plan.
pub struct SatisfactionMaterial {
/// Schnorr signautres under their keys
pub schnorr_sigs: BTreeMap<DefiniteDescriptorKey, SchnorrSig>,
/// ECDSA signatures under their keys
pub ecdsa_sigs: BTreeMap<DefiniteDescriptorKey, EcdsaSig>,
/// SHA256 pre-images under their images
pub sha256_preimages: BTreeMap<sha256::Hash, Vec<u8>>,
/// hash160 pre-images under their images
pub hash160_preimages: BTreeMap<hash160::Hash, Vec<u8>>,
/// hash256 pre-images under their images
pub hash256_preimages: BTreeMap<hash256::Hash, Vec<u8>>,
/// ripemd160 pre-images under their images
pub ripemd160_preimages: BTreeMap<ripemd160::Hash, Vec<u8>>,
}
impl<Ak> Plan<Ak>
where
Ak: Clone,
{
/// The expected satisfaction weight for the plan if it is completed.
pub fn expected_weight(&self) -> usize {
let script_sig_size = match self.target {
Target::Legacy => unimplemented!(), // self
// .template
// .iter()
// .map(|step| {
// let size = step.expected_size();
// size + push_opcode_size(size)
// })
// .sum()
Target::Segwitv0 { .. } | Target::Segwitv1 { .. } => 1,
};
let witness_elem_sizes: Option<Vec<usize>> = match &self.target {
Target::Legacy => None,
Target::Segwitv0 { .. } => Some(
self.template
.iter()
.map(|step| step.expected_size())
.collect(),
),
Target::Segwitv1 { tr, tr_plan } => {
let mut witness_elems = self
.template
.iter()
.map(|step| step.expected_size())
.collect::<Vec<_>>();
if let TrSpend::LeafSpend {
script,
leaf_version,
} = tr_plan
{
let control_block = tr
.spend_info()
.control_block(&(script.clone(), *leaf_version))
.expect("must exist");
witness_elems.push(script.len());
witness_elems.push(control_block.size());
}
Some(witness_elems)
}
};
let witness_size: usize = match witness_elem_sizes {
Some(elems) => {
varint_len(elems.len())
+ elems
.into_iter()
.map(|elem| varint_len(elem) + elem)
.sum::<usize>()
}
None => 0,
};
script_sig_size * 4 + witness_size
}
pub fn requirements(&self) -> Requirements<Ak> {
match self.try_complete(&SatisfactionMaterial::default()) {
PlanState::Complete { .. } => Requirements::default(),
PlanState::Incomplete(requirements) => requirements,
}
}
pub fn try_complete(&self, auth_data: &SatisfactionMaterial) -> PlanState<Ak> {
let unsatisfied_items = self
.template
.iter()
.filter(|step| match step {
TemplateItem::Sign(key) => {
!auth_data.schnorr_sigs.contains_key(&key.descriptor_key)
}
TemplateItem::Hash160(image) => !auth_data.hash160_preimages.contains_key(image),
TemplateItem::Hash256(image) => !auth_data.hash256_preimages.contains_key(image),
TemplateItem::Sha256(image) => !auth_data.sha256_preimages.contains_key(image),
TemplateItem::Ripemd160(image) => {
!auth_data.ripemd160_preimages.contains_key(image)
}
TemplateItem::Pk { .. } | TemplateItem::One | TemplateItem::Zero => false,
})
.collect::<Vec<_>>();
if unsatisfied_items.is_empty() {
let mut witness = self
.template
.iter()
.flat_map(|step| step.to_witness_stack(&auth_data))
.collect::<Vec<_>>();
match &self.target {
Target::Segwitv0 { .. } => todo!(),
Target::Legacy => todo!(),
Target::Segwitv1 {
tr_plan: TrSpend::KeySpend,
..
} => PlanState::Complete {
final_script_sig: None,
final_script_witness: Some(Witness::from_vec(witness)),
},
Target::Segwitv1 {
tr,
tr_plan:
TrSpend::LeafSpend {
script,
leaf_version,
},
} => {
let spend_info = tr.spend_info();
let control_block = spend_info
.control_block(&(script.clone(), *leaf_version))
.expect("must exist");
witness.push(script.clone().into_bytes());
witness.push(control_block.serialize());
PlanState::Complete {
final_script_sig: None,
final_script_witness: Some(Witness::from_vec(witness)),
}
}
}
} else {
let mut requirements = Requirements::default();
match &self.target {
Target::Legacy => {
todo!()
}
Target::Segwitv0 { .. } => {
todo!()
}
Target::Segwitv1 { tr, tr_plan } => {
let spend_info = tr.spend_info();
match tr_plan {
TrSpend::KeySpend => match &self.template[..] {
[TemplateItem::Sign(ref plan_key)] => {
requirements.signatures = RequiredSignatures::TapKey {
merkle_root: spend_info.merkle_root(),
plan_key: plan_key.clone(),
};
}
_ => unreachable!("tapkey spend will always have only one sign step"),
},
TrSpend::LeafSpend {
script,
leaf_version,
} => {
let leaf_hash = TapLeafHash::from_script(&script, *leaf_version);
requirements.signatures = RequiredSignatures::TapScript {
leaf_hash,
plan_keys: vec![],
}
}
}
}
}
let required_signatures = match requirements.signatures {
RequiredSignatures::Legacy { .. } => todo!(),
RequiredSignatures::Segwitv0 { .. } => todo!(),
RequiredSignatures::TapKey { .. } => return PlanState::Incomplete(requirements),
RequiredSignatures::TapScript {
plan_keys: ref mut keys,
..
} => keys,
};
for step in unsatisfied_items {
match step {
TemplateItem::Sign(plan_key) => {
required_signatures.push(plan_key.clone());
}
TemplateItem::Hash160(image) => {
requirements.hash160_images.insert(image.clone());
}
TemplateItem::Hash256(image) => {
requirements.hash256_images.insert(image.clone());
}
TemplateItem::Sha256(image) => {
requirements.sha256_images.insert(image.clone());
}
TemplateItem::Ripemd160(image) => {
requirements.ripemd160_images.insert(image.clone());
}
TemplateItem::Pk { .. } | TemplateItem::One | TemplateItem::Zero => { /* no requirements */
}
}
}
PlanState::Incomplete(requirements)
}
}
/// Witness version for the plan
pub fn witness_version(&self) -> Option<WitnessVersion> {
match self.target {
Target::Legacy => None,
Target::Segwitv0 { .. } => Some(WitnessVersion::V0),
Target::Segwitv1 { .. } => Some(WitnessVersion::V1),
}
}
/// The minimum required locktime height or time on the transaction using the plan.
pub fn required_locktime(&self) -> Option<LockTime> {
self.set_locktime.clone()
}
/// The minimum required sequence (height or time) on the input to satisfy the plan
pub fn required_sequence(&self) -> Option<Sequence> {
self.set_sequence.clone()
}
/// The minmum required transaction version required on the transaction using the plan.
pub fn min_version(&self) -> Option<u32> {
if let Some(_) = self.set_sequence {
Some(2)
} else {
Some(1)
}
}
}
/// The returned value from [`Plan::try_complete`].
pub enum PlanState<Ak> {
/// The plan is complete
Complete {
/// The script sig that should be set on the input
final_script_sig: Option<Script>,
/// The witness that should be set on the input
final_script_witness: Option<Witness>,
},
Incomplete(Requirements<Ak>),
}
#[derive(Clone, Debug)]
pub struct Assets<K> {
pub keys: Vec<K>,
pub txo_age: Option<Sequence>,
pub max_locktime: Option<LockTime>,
pub sha256: Vec<sha256::Hash>,
pub hash256: Vec<hash256::Hash>,
pub ripemd160: Vec<ripemd160::Hash>,
pub hash160: Vec<hash160::Hash>,
}
impl<K> Default for Assets<K> {
fn default() -> Self {
Self {
keys: Default::default(),
txo_age: Default::default(),
max_locktime: Default::default(),
sha256: Default::default(),
hash256: Default::default(),
ripemd160: Default::default(),
hash160: Default::default(),
}
}
}
pub trait CanDerive {
fn can_derive(&self, key: &DefiniteDescriptorKey) -> Option<DerivationPath>;
}
impl CanDerive for KeySource {
fn can_derive(&self, key: &DefiniteDescriptorKey) -> Option<DerivationPath> {
match DescriptorPublicKey::from(key.clone()) {
DescriptorPublicKey::Single(single_pub) => {
path_to_child(self, single_pub.origin.as_ref()?, None)
}
DescriptorPublicKey::XPub(dxk) => {
let origin = dxk.origin.clone().unwrap_or_else(|| {
let secp = Secp256k1::signing_only();
(dxk.xkey.xkey_fingerprint(&secp), DerivationPath::master())
});
path_to_child(self, &origin, Some(&dxk.derivation_path))
}
}
}
}
impl CanDerive for DescriptorPublicKey {
fn can_derive(&self, key: &DefiniteDescriptorKey) -> Option<DerivationPath> {
match (self, DescriptorPublicKey::from(key.clone())) {
(parent, child) if parent == &child => Some(DerivationPath::master()),
(DescriptorPublicKey::XPub(parent), _) => {
let origin = parent.origin.clone().unwrap_or_else(|| {
let secp = Secp256k1::signing_only();
(
parent.xkey.xkey_fingerprint(&secp),
DerivationPath::master(),
)
});
KeySource::from(origin).can_derive(key)
}
_ => None,
}
}
}
fn path_to_child(
parent: &KeySource,
child_origin: &(Fingerprint, DerivationPath),
child_derivation: Option<&DerivationPath>,
) -> Option<DerivationPath> {
if parent.0 == child_origin.0 {
let mut remaining_derivation =
DerivationPath::from(child_origin.1[..].strip_prefix(&parent.1[..])?);
remaining_derivation =
remaining_derivation.extend(child_derivation.unwrap_or(&DerivationPath::master()));
Some(remaining_derivation)
} else {
None
}
}
pub fn plan_satisfaction<Ak>(
desc: &Descriptor<DefiniteDescriptorKey>,
assets: &Assets<Ak>,
) -> Option<Plan<Ak>>
where
Ak: CanDerive + Clone,
{
match desc {
Descriptor::Bare(_) => todo!(),
Descriptor::Pkh(_) => todo!(),
Descriptor::Wpkh(_) => todo!(),
Descriptor::Sh(_) => todo!(),
Descriptor::Wsh(_) => todo!(),
Descriptor::Tr(tr) => crate::plan_impls::plan_satisfaction_tr(tr, assets),
}
}

View File

@@ -0,0 +1,323 @@
use bdk_chain::{bitcoin, miniscript};
use bitcoin::locktime::{Height, Time};
use miniscript::Terminal;
use super::*;
impl<Ak> TermPlan<Ak> {
fn combine(self, other: Self) -> Option<Self> {
let min_locktime = {
match (self.min_locktime, other.min_locktime) {
(Some(lhs), Some(rhs)) => {
if lhs.is_same_unit(rhs) {
Some(if lhs.to_consensus_u32() > rhs.to_consensus_u32() {
lhs
} else {
rhs
})
} else {
return None;
}
}
_ => self.min_locktime.or(other.min_locktime),
}
};
let min_sequence = {
match (self.min_sequence, other.min_sequence) {
(Some(lhs), Some(rhs)) => {
if lhs.is_height_locked() == rhs.is_height_locked() {
Some(if lhs.to_consensus_u32() > rhs.to_consensus_u32() {
lhs
} else {
rhs
})
} else {
return None;
}
}
_ => self.min_sequence.or(other.min_sequence),
}
};
let mut template = self.template;
template.extend(other.template);
Some(Self {
min_locktime,
min_sequence,
template,
})
}
pub(crate) fn expected_size(&self) -> usize {
self.template.iter().map(|step| step.expected_size()).sum()
}
}
// impl crate::descriptor::Pkh<DefiniteDescriptorKey> {
// pub(crate) fn plan_satisfaction<Ak>(&self, assets: &Assets<Ak>) -> Option<Plan<Ak>>
// where
// Ak: CanDerive + Clone,
// {
// let (asset_key, derivation_hint) = assets.keys.iter().find_map(|asset_key| {
// let derivation_hint = asset_key.can_derive(self.as_inner())?;
// Some((asset_key, derivation_hint))
// })?;
// Some(Plan {
// template: vec![TemplateItem::Sign(PlanKey {
// asset_key: asset_key.clone(),
// descriptor_key: self.as_inner().clone(),
// derivation_hint,
// })],
// target: Target::Legacy,
// set_locktime: None,
// set_sequence: None,
// })
// }
// }
// impl crate::descriptor::Wpkh<DefiniteDescriptorKey> {
// pub(crate) fn plan_satisfaction<Ak>(&self, assets: &Assets<Ak>) -> Option<Plan<Ak>>
// where
// Ak: CanDerive + Clone,
// {
// let (asset_key, derivation_hint) = assets.keys.iter().find_map(|asset_key| {
// let derivation_hint = asset_key.can_derive(self.as_inner())?;
// Some((asset_key, derivation_hint))
// })?;
// Some(Plan {
// template: vec![TemplateItem::Sign(PlanKey {
// asset_key: asset_key.clone(),
// descriptor_key: self.as_inner().clone(),
// derivation_hint,
// })],
// target: Target::Segwitv0,
// set_locktime: None,
// set_sequence: None,
// })
// }
// }
pub(crate) fn plan_satisfaction_tr<Ak>(
tr: &miniscript::descriptor::Tr<DefiniteDescriptorKey>,
assets: &Assets<Ak>,
) -> Option<Plan<Ak>>
where
Ak: CanDerive + Clone,
{
let key_path_spend = assets.keys.iter().find_map(|asset_key| {
let derivation_hint = asset_key.can_derive(tr.internal_key())?;
Some((asset_key, derivation_hint))
});
if let Some((asset_key, derivation_hint)) = key_path_spend {
return Some(Plan {
template: vec![TemplateItem::Sign(PlanKey {
asset_key: asset_key.clone(),
descriptor_key: tr.internal_key().clone(),
derivation_hint,
})],
target: Target::Segwitv1 {
tr: tr.clone(),
tr_plan: TrSpend::KeySpend,
},
set_locktime: None,
set_sequence: None,
});
}
let mut plans = tr
.iter_scripts()
.filter_map(|(_, ms)| Some((ms, (plan_steps(&ms.node, assets)?))))
.collect::<Vec<_>>();
plans.sort_by_cached_key(|(_, plan)| plan.expected_size());
let (script, best_plan) = plans.into_iter().next()?;
Some(Plan {
target: Target::Segwitv1 {
tr: tr.clone(),
tr_plan: TrSpend::LeafSpend {
script: script.encode(),
leaf_version: LeafVersion::TapScript,
},
},
set_locktime: best_plan.min_locktime.clone(),
set_sequence: best_plan.min_sequence.clone(),
template: best_plan.template,
})
}
#[derive(Debug)]
struct TermPlan<Ak> {
pub min_locktime: Option<LockTime>,
pub min_sequence: Option<Sequence>,
pub template: Vec<TemplateItem<Ak>>,
}
impl<Ak> TermPlan<Ak> {
fn new(template: Vec<TemplateItem<Ak>>) -> Self {
TermPlan {
template,
..Default::default()
}
}
}
impl<Ak> Default for TermPlan<Ak> {
fn default() -> Self {
Self {
min_locktime: Default::default(),
min_sequence: Default::default(),
template: Default::default(),
}
}
}
fn plan_steps<Ak: Clone + CanDerive, Ctx: ScriptContext>(
term: &Terminal<DefiniteDescriptorKey, Ctx>,
assets: &Assets<Ak>,
) -> Option<TermPlan<Ak>> {
match term {
Terminal::True => Some(TermPlan::new(vec![])),
Terminal::False => return None,
Terminal::PkH(key) => {
let (asset_key, derivation_hint) = assets
.keys
.iter()
.find_map(|asset_key| Some((asset_key, asset_key.can_derive(key)?)))?;
Some(TermPlan::new(vec![
TemplateItem::Sign(PlanKey {
asset_key: asset_key.clone(),
derivation_hint,
descriptor_key: key.clone(),
}),
TemplateItem::Pk { key: key.clone() },
]))
}
Terminal::PkK(key) => {
let (asset_key, derivation_hint) = assets
.keys
.iter()
.find_map(|asset_key| Some((asset_key, asset_key.can_derive(key)?)))?;
Some(TermPlan::new(vec![TemplateItem::Sign(PlanKey {
asset_key: asset_key.clone(),
derivation_hint,
descriptor_key: key.clone(),
})]))
}
Terminal::RawPkH(_pk_hash) => {
/* TODO */
None
}
Terminal::After(locktime) => {
let max_locktime = assets.max_locktime?;
let locktime = LockTime::from(locktime);
let (height, time) = match max_locktime {
LockTime::Blocks(height) => (height, Time::from_consensus(0).unwrap()),
LockTime::Seconds(seconds) => (Height::from_consensus(0).unwrap(), seconds),
};
if max_locktime.is_satisfied_by(height, time) {
Some(TermPlan {
min_locktime: Some(locktime),
..Default::default()
})
} else {
None
}
}
Terminal::Older(older) => {
// FIXME: older should be a height or time not a sequence.
let max_sequence = assets.txo_age?;
//TODO: this whole thing is probably wrong but upstream should provide a way of
// doing it properly.
if max_sequence.is_height_locked() == older.is_height_locked() {
if max_sequence.to_consensus_u32() >= older.to_consensus_u32() {
Some(TermPlan {
min_sequence: Some(*older),
..Default::default()
})
} else {
None
}
} else {
None
}
}
Terminal::Sha256(image) => {
if assets.sha256.contains(&image) {
Some(TermPlan::new(vec![TemplateItem::Sha256(image.clone())]))
} else {
None
}
}
Terminal::Hash256(image) => {
if assets.hash256.contains(image) {
Some(TermPlan::new(vec![TemplateItem::Hash256(image.clone())]))
} else {
None
}
}
Terminal::Ripemd160(image) => {
if assets.ripemd160.contains(&image) {
Some(TermPlan::new(vec![TemplateItem::Ripemd160(image.clone())]))
} else {
None
}
}
Terminal::Hash160(image) => {
if assets.hash160.contains(&image) {
Some(TermPlan::new(vec![TemplateItem::Hash160(image.clone())]))
} else {
None
}
}
Terminal::Alt(ms)
| Terminal::Swap(ms)
| Terminal::Check(ms)
| Terminal::Verify(ms)
| Terminal::NonZero(ms)
| Terminal::ZeroNotEqual(ms) => plan_steps(&ms.node, assets),
Terminal::DupIf(ms) => {
let mut plan = plan_steps(&ms.node, assets)?;
plan.template.push(TemplateItem::One);
Some(plan)
}
Terminal::AndV(l, r) | Terminal::AndB(l, r) => {
let lhs = plan_steps(&l.node, assets)?;
let rhs = plan_steps(&r.node, assets)?;
lhs.combine(rhs)
}
Terminal::AndOr(_, _, _) => todo!(),
Terminal::OrB(_, _) => todo!(),
Terminal::OrD(_, _) => todo!(),
Terminal::OrC(_, _) => todo!(),
Terminal::OrI(lhs, rhs) => {
let lplan = plan_steps(&lhs.node, assets).map(|mut plan| {
plan.template.push(TemplateItem::One);
plan
});
let rplan = plan_steps(&rhs.node, assets).map(|mut plan| {
plan.template.push(TemplateItem::Zero);
plan
});
match (lplan, rplan) {
(Some(lplan), Some(rplan)) => {
if lplan.expected_size() <= rplan.expected_size() {
Some(lplan)
} else {
Some(rplan)
}
}
(lplan, rplan) => lplan.or(rplan),
}
}
Terminal::Thresh(_, _) => todo!(),
Terminal::Multi(_, _) => todo!(),
Terminal::MultiA(_, _) => todo!(),
}
}

View File

@@ -0,0 +1,218 @@
use bdk_chain::{bitcoin, collections::*, miniscript};
use core::ops::Deref;
use bitcoin::{
hashes::{hash160, ripemd160, sha256},
psbt::Prevouts,
secp256k1::{KeyPair, Message, PublicKey, Signing, Verification},
util::{bip32, sighash, sighash::SighashCache, taproot},
EcdsaSighashType, SchnorrSighashType, Transaction, TxOut, XOnlyPublicKey,
};
use super::*;
use miniscript::{
descriptor::{DescriptorSecretKey, KeyMap},
hash256,
};
#[derive(Clone, Debug)]
/// Signatures and hash pre-images that must be provided to complete the plan.
pub struct Requirements<Ak> {
/// required signatures
pub signatures: RequiredSignatures<Ak>,
/// required sha256 pre-images
pub sha256_images: HashSet<sha256::Hash>,
/// required hash160 pre-images
pub hash160_images: HashSet<hash160::Hash>,
/// required hash256 pre-images
pub hash256_images: HashSet<hash256::Hash>,
/// required ripemd160 pre-images
pub ripemd160_images: HashSet<ripemd160::Hash>,
}
impl<Ak> Default for RequiredSignatures<Ak> {
fn default() -> Self {
RequiredSignatures::Legacy {
keys: Default::default(),
}
}
}
impl<Ak> Default for Requirements<Ak> {
fn default() -> Self {
Self {
signatures: Default::default(),
sha256_images: Default::default(),
hash160_images: Default::default(),
hash256_images: Default::default(),
ripemd160_images: Default::default(),
}
}
}
impl<Ak> Requirements<Ak> {
/// Whether any hash pre-images are required in the plan
pub fn requires_hash_preimages(&self) -> bool {
!(self.sha256_images.is_empty()
&& self.hash160_images.is_empty()
&& self.hash256_images.is_empty()
&& self.ripemd160_images.is_empty())
}
}
/// The signatures required to complete the plan
#[derive(Clone, Debug)]
pub enum RequiredSignatures<Ak> {
/// Legacy ECDSA signatures are required
Legacy { keys: Vec<PlanKey<Ak>> },
/// Segwitv0 ECDSA signatures are required
Segwitv0 { keys: Vec<PlanKey<Ak>> },
/// A Taproot key spend signature is required
TapKey {
/// the internal key
plan_key: PlanKey<Ak>,
/// The merkle root of the taproot output
merkle_root: Option<TapBranchHash>,
},
/// Taproot script path signatures are required
TapScript {
/// The leaf hash of the script being used
leaf_hash: TapLeafHash,
/// The keys in the script that require signatures
plan_keys: Vec<PlanKey<Ak>>,
},
}
#[derive(Clone, Debug)]
pub enum SigningError {
SigHashError(sighash::Error),
DerivationError(bip32::Error),
}
impl From<sighash::Error> for SigningError {
fn from(e: sighash::Error) -> Self {
Self::SigHashError(e)
}
}
impl core::fmt::Display for SigningError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SigningError::SigHashError(e) => e.fmt(f),
SigningError::DerivationError(e) => e.fmt(f),
}
}
}
impl From<bip32::Error> for SigningError {
fn from(e: bip32::Error) -> Self {
Self::DerivationError(e)
}
}
#[cfg(feature = "std")]
impl std::error::Error for SigningError {}
impl RequiredSignatures<DescriptorPublicKey> {
pub fn sign_with_keymap<T: Deref<Target = Transaction>>(
&self,
input_index: usize,
keymap: &KeyMap,
prevouts: &Prevouts<'_, impl core::borrow::Borrow<TxOut>>,
schnorr_sighashty: Option<SchnorrSighashType>,
_ecdsa_sighashty: Option<EcdsaSighashType>,
sighash_cache: &mut SighashCache<T>,
auth_data: &mut SatisfactionMaterial,
secp: &Secp256k1<impl Signing + Verification>,
) -> Result<bool, SigningError> {
match self {
RequiredSignatures::Legacy { .. } | RequiredSignatures::Segwitv0 { .. } => todo!(),
RequiredSignatures::TapKey {
plan_key,
merkle_root,
} => {
let schnorr_sighashty = schnorr_sighashty.unwrap_or(SchnorrSighashType::Default);
let sighash = sighash_cache.taproot_key_spend_signature_hash(
input_index,
prevouts,
schnorr_sighashty,
)?;
let secret_key = match keymap.get(&plan_key.asset_key) {
Some(secret_key) => secret_key,
None => return Ok(false),
};
let secret_key = match secret_key {
DescriptorSecretKey::Single(single) => single.key.inner,
DescriptorSecretKey::XPrv(xprv) => {
xprv.xkey
.derive_priv(&secp, &plan_key.derivation_hint)?
.private_key
}
};
let pubkey = PublicKey::from_secret_key(&secp, &secret_key);
let x_only_pubkey = XOnlyPublicKey::from(pubkey);
let tweak =
taproot::TapTweakHash::from_key_and_tweak(x_only_pubkey, merkle_root.clone());
let keypair = KeyPair::from_secret_key(&secp, &secret_key.clone())
.add_xonly_tweak(&secp, &tweak.to_scalar())
.unwrap();
let msg = Message::from_slice(sighash.as_ref()).expect("Sighashes are 32 bytes");
let sig = secp.sign_schnorr_no_aux_rand(&msg, &keypair);
let bitcoin_sig = SchnorrSig {
sig,
hash_ty: schnorr_sighashty,
};
auth_data
.schnorr_sigs
.insert(plan_key.descriptor_key.clone(), bitcoin_sig);
Ok(true)
}
RequiredSignatures::TapScript {
leaf_hash,
plan_keys,
} => {
let sighash_type = schnorr_sighashty.unwrap_or(SchnorrSighashType::Default);
let sighash = sighash_cache.taproot_script_spend_signature_hash(
input_index,
prevouts,
*leaf_hash,
sighash_type,
)?;
let mut modified = false;
for plan_key in plan_keys {
if let Some(secret_key) = keymap.get(&plan_key.asset_key) {
let secret_key = match secret_key {
DescriptorSecretKey::Single(single) => single.key.inner,
DescriptorSecretKey::XPrv(xprv) => {
xprv.xkey
.derive_priv(&secp, &plan_key.derivation_hint)?
.private_key
}
};
let keypair = KeyPair::from_secret_key(&secp, &secret_key.clone());
let msg =
Message::from_slice(sighash.as_ref()).expect("Sighashes are 32 bytes");
let sig = secp.sign_schnorr_no_aux_rand(&msg, &keypair);
let bitcoin_sig = SchnorrSig {
sig,
hash_ty: sighash_type,
};
auth_data
.schnorr_sigs
.insert(plan_key.descriptor_key.clone(), bitcoin_sig);
modified = true;
}
}
Ok(modified)
}
}
}
}

View File

@@ -0,0 +1,76 @@
use bdk_chain::{bitcoin, miniscript};
use bitcoin::{
hashes::{hash160, ripemd160, sha256},
util::bip32::DerivationPath,
};
use super::*;
use crate::{hash256, varint_len, DefiniteDescriptorKey};
#[derive(Clone, Debug)]
pub(crate) enum TemplateItem<Ak> {
Sign(PlanKey<Ak>),
Pk { key: DefiniteDescriptorKey },
One,
Zero,
Sha256(sha256::Hash),
Hash256(hash256::Hash),
Ripemd160(ripemd160::Hash),
Hash160(hash160::Hash),
}
/// A plan key contains the asset key originally provided along with key in the descriptor it
/// purports to be able to derive for along with a "hint" on how to derive it.
#[derive(Clone, Debug)]
pub struct PlanKey<Ak> {
/// The key the planner will sign with
pub asset_key: Ak,
/// A hint from how to get from the asset key to the concrete key we need to sign with.
pub derivation_hint: DerivationPath,
/// The key that was in the descriptor that we are satisfying with the signature from the asset
/// key.
pub descriptor_key: DefiniteDescriptorKey,
}
impl<Ak> TemplateItem<Ak> {
pub fn expected_size(&self) -> usize {
match self {
TemplateItem::Sign { .. } => 64, /*size of sig TODO: take into consideration sighash falg*/
TemplateItem::Pk { .. } => 32,
TemplateItem::One => varint_len(1),
TemplateItem::Zero => 0, /* zero means an empty witness element */
// I'm not sure if it should be 32 here (it's a 20 byte hash) but that's what other
// parts of the code were doing.
TemplateItem::Hash160(_) | TemplateItem::Ripemd160(_) => 32,
TemplateItem::Sha256(_) | TemplateItem::Hash256(_) => 32,
}
}
// this can only be called if we are sure that auth_data has what we need
pub(super) fn to_witness_stack(&self, auth_data: &SatisfactionMaterial) -> Vec<Vec<u8>> {
match self {
TemplateItem::Sign(plan_key) => {
vec![auth_data
.schnorr_sigs
.get(&plan_key.descriptor_key)
.unwrap()
.to_vec()]
}
TemplateItem::One => vec![vec![1]],
TemplateItem::Zero => vec![vec![]],
TemplateItem::Sha256(image) => {
vec![auth_data.sha256_preimages.get(image).unwrap().to_vec()]
}
TemplateItem::Hash160(image) => {
vec![auth_data.hash160_preimages.get(image).unwrap().to_vec()]
}
TemplateItem::Ripemd160(image) => {
vec![auth_data.ripemd160_preimages.get(image).unwrap().to_vec()]
}
TemplateItem::Hash256(image) => {
vec![auth_data.hash256_preimages.get(image).unwrap().to_vec()]
}
TemplateItem::Pk { key } => vec![key.to_public_key().to_bytes()],
}
}
}

436
nursery/tmp_plan/src/lib.rs Normal file
View File

@@ -0,0 +1,436 @@
#![allow(unused)]
#![allow(missing_docs)]
//! A spending plan or *plan* for short is a representation of a particular spending path on a
//! descriptor. This allows us to analayze a choice of spending path without producing any
//! signatures or other witness data for it.
//!
//! To make a plan you provide the descriptor with "assets" like which keys you are able to use, hash
//! pre-images you have access to, the current block height etc.
//!
//! Once you've got a plan it can tell you its expected satisfaction weight which can be useful for
//! doing coin selection. Furthermore it provides which subset of those keys and hash pre-images you
//! will actually need as well as what locktime or sequence number you need to set.
//!
//! Once you've obstained signatures, hash pre-images etc required by the plan, it can create a
//! witness/script_sig for the input.
use bdk_chain::{bitcoin, collections::*, miniscript};
use bitcoin::{
blockdata::{locktime::LockTime, transaction::Sequence},
hashes::{hash160, ripemd160, sha256},
secp256k1::Secp256k1,
util::{
address::WitnessVersion,
bip32::{DerivationPath, Fingerprint, KeySource},
taproot::{LeafVersion, TapBranchHash, TapLeafHash},
},
EcdsaSig, SchnorrSig, Script, TxIn, Witness,
};
use miniscript::{
descriptor::{InnerXKey, Tr},
hash256, DefiniteDescriptorKey, Descriptor, DescriptorPublicKey, ScriptContext, ToPublicKey,
};
pub(crate) fn varint_len(v: usize) -> usize {
bitcoin::VarInt(v as u64).len() as usize
}
mod plan_impls;
mod requirements;
mod template;
pub use requirements::*;
pub use template::PlanKey;
use template::TemplateItem;
#[derive(Clone, Debug)]
enum TrSpend {
KeySpend,
LeafSpend {
script: Script,
leaf_version: LeafVersion,
},
}
#[derive(Clone, Debug)]
enum Target {
Legacy,
Segwitv0 {
script_code: Script,
},
Segwitv1 {
tr: Tr<DefiniteDescriptorKey>,
tr_plan: TrSpend,
},
}
impl Target {}
#[derive(Clone, Debug)]
/// A plan represents a particular spending path for a descriptor.
///
/// See the module level documentation for more info.
pub struct Plan<AK> {
template: Vec<TemplateItem<AK>>,
target: Target,
set_locktime: Option<LockTime>,
set_sequence: Option<Sequence>,
}
impl Default for Target {
fn default() -> Self {
Target::Legacy
}
}
#[derive(Clone, Debug, Default)]
/// Signatures and hash pre-images that can be used to complete a plan.
pub struct SatisfactionMaterial {
/// Schnorr signautres under their keys
pub schnorr_sigs: BTreeMap<DefiniteDescriptorKey, SchnorrSig>,
/// ECDSA signatures under their keys
pub ecdsa_sigs: BTreeMap<DefiniteDescriptorKey, EcdsaSig>,
/// SHA256 pre-images under their images
pub sha256_preimages: BTreeMap<sha256::Hash, Vec<u8>>,
/// hash160 pre-images under their images
pub hash160_preimages: BTreeMap<hash160::Hash, Vec<u8>>,
/// hash256 pre-images under their images
pub hash256_preimages: BTreeMap<hash256::Hash, Vec<u8>>,
/// ripemd160 pre-images under their images
pub ripemd160_preimages: BTreeMap<ripemd160::Hash, Vec<u8>>,
}
impl<Ak> Plan<Ak>
where
Ak: Clone,
{
/// The expected satisfaction weight for the plan if it is completed.
pub fn expected_weight(&self) -> usize {
let script_sig_size = match self.target {
Target::Legacy => unimplemented!(), // self
// .template
// .iter()
// .map(|step| {
// let size = step.expected_size();
// size + push_opcode_size(size)
// })
// .sum()
Target::Segwitv0 { .. } | Target::Segwitv1 { .. } => 1,
};
let witness_elem_sizes: Option<Vec<usize>> = match &self.target {
Target::Legacy => None,
Target::Segwitv0 { .. } => Some(
self.template
.iter()
.map(|step| step.expected_size())
.collect(),
),
Target::Segwitv1 { tr, tr_plan } => {
let mut witness_elems = self
.template
.iter()
.map(|step| step.expected_size())
.collect::<Vec<_>>();
if let TrSpend::LeafSpend {
script,
leaf_version,
} = tr_plan
{
let control_block = tr
.spend_info()
.control_block(&(script.clone(), *leaf_version))
.expect("must exist");
witness_elems.push(script.len());
witness_elems.push(control_block.size());
}
Some(witness_elems)
}
};
let witness_size: usize = match witness_elem_sizes {
Some(elems) => {
varint_len(elems.len())
+ elems
.into_iter()
.map(|elem| varint_len(elem) + elem)
.sum::<usize>()
}
None => 0,
};
script_sig_size * 4 + witness_size
}
pub fn requirements(&self) -> Requirements<Ak> {
match self.try_complete(&SatisfactionMaterial::default()) {
PlanState::Complete { .. } => Requirements::default(),
PlanState::Incomplete(requirements) => requirements,
}
}
pub fn try_complete(&self, auth_data: &SatisfactionMaterial) -> PlanState<Ak> {
let unsatisfied_items = self
.template
.iter()
.filter(|step| match step {
TemplateItem::Sign(key) => {
!auth_data.schnorr_sigs.contains_key(&key.descriptor_key)
}
TemplateItem::Hash160(image) => !auth_data.hash160_preimages.contains_key(image),
TemplateItem::Hash256(image) => !auth_data.hash256_preimages.contains_key(image),
TemplateItem::Sha256(image) => !auth_data.sha256_preimages.contains_key(image),
TemplateItem::Ripemd160(image) => {
!auth_data.ripemd160_preimages.contains_key(image)
}
TemplateItem::Pk { .. } | TemplateItem::One | TemplateItem::Zero => false,
})
.collect::<Vec<_>>();
if unsatisfied_items.is_empty() {
let mut witness = self
.template
.iter()
.flat_map(|step| step.to_witness_stack(&auth_data))
.collect::<Vec<_>>();
match &self.target {
Target::Segwitv0 { .. } => todo!(),
Target::Legacy => todo!(),
Target::Segwitv1 {
tr_plan: TrSpend::KeySpend,
..
} => PlanState::Complete {
final_script_sig: None,
final_script_witness: Some(Witness::from_vec(witness)),
},
Target::Segwitv1 {
tr,
tr_plan:
TrSpend::LeafSpend {
script,
leaf_version,
},
} => {
let spend_info = tr.spend_info();
let control_block = spend_info
.control_block(&(script.clone(), *leaf_version))
.expect("must exist");
witness.push(script.clone().into_bytes());
witness.push(control_block.serialize());
PlanState::Complete {
final_script_sig: None,
final_script_witness: Some(Witness::from_vec(witness)),
}
}
}
} else {
let mut requirements = Requirements::default();
match &self.target {
Target::Legacy => {
todo!()
}
Target::Segwitv0 { .. } => {
todo!()
}
Target::Segwitv1 { tr, tr_plan } => {
let spend_info = tr.spend_info();
match tr_plan {
TrSpend::KeySpend => match &self.template[..] {
[TemplateItem::Sign(ref plan_key)] => {
requirements.signatures = RequiredSignatures::TapKey {
merkle_root: spend_info.merkle_root(),
plan_key: plan_key.clone(),
};
}
_ => unreachable!("tapkey spend will always have only one sign step"),
},
TrSpend::LeafSpend {
script,
leaf_version,
} => {
let leaf_hash = TapLeafHash::from_script(&script, *leaf_version);
requirements.signatures = RequiredSignatures::TapScript {
leaf_hash,
plan_keys: vec![],
}
}
}
}
}
let required_signatures = match requirements.signatures {
RequiredSignatures::Legacy { .. } => todo!(),
RequiredSignatures::Segwitv0 { .. } => todo!(),
RequiredSignatures::TapKey { .. } => return PlanState::Incomplete(requirements),
RequiredSignatures::TapScript {
plan_keys: ref mut keys,
..
} => keys,
};
for step in unsatisfied_items {
match step {
TemplateItem::Sign(plan_key) => {
required_signatures.push(plan_key.clone());
}
TemplateItem::Hash160(image) => {
requirements.hash160_images.insert(image.clone());
}
TemplateItem::Hash256(image) => {
requirements.hash256_images.insert(image.clone());
}
TemplateItem::Sha256(image) => {
requirements.sha256_images.insert(image.clone());
}
TemplateItem::Ripemd160(image) => {
requirements.ripemd160_images.insert(image.clone());
}
TemplateItem::Pk { .. } | TemplateItem::One | TemplateItem::Zero => { /* no requirements */
}
}
}
PlanState::Incomplete(requirements)
}
}
/// Witness version for the plan
pub fn witness_version(&self) -> Option<WitnessVersion> {
match self.target {
Target::Legacy => None,
Target::Segwitv0 { .. } => Some(WitnessVersion::V0),
Target::Segwitv1 { .. } => Some(WitnessVersion::V1),
}
}
/// The minimum required locktime height or time on the transaction using the plan.
pub fn required_locktime(&self) -> Option<LockTime> {
self.set_locktime.clone()
}
/// The minimum required sequence (height or time) on the input to satisfy the plan
pub fn required_sequence(&self) -> Option<Sequence> {
self.set_sequence.clone()
}
/// The minmum required transaction version required on the transaction using the plan.
pub fn min_version(&self) -> Option<u32> {
if let Some(_) = self.set_sequence {
Some(2)
} else {
Some(1)
}
}
}
/// The returned value from [`Plan::try_complete`].
pub enum PlanState<Ak> {
/// The plan is complete
Complete {
/// The script sig that should be set on the input
final_script_sig: Option<Script>,
/// The witness that should be set on the input
final_script_witness: Option<Witness>,
},
Incomplete(Requirements<Ak>),
}
#[derive(Clone, Debug)]
pub struct Assets<K> {
pub keys: Vec<K>,
pub txo_age: Option<Sequence>,
pub max_locktime: Option<LockTime>,
pub sha256: Vec<sha256::Hash>,
pub hash256: Vec<hash256::Hash>,
pub ripemd160: Vec<ripemd160::Hash>,
pub hash160: Vec<hash160::Hash>,
}
impl<K> Default for Assets<K> {
fn default() -> Self {
Self {
keys: Default::default(),
txo_age: Default::default(),
max_locktime: Default::default(),
sha256: Default::default(),
hash256: Default::default(),
ripemd160: Default::default(),
hash160: Default::default(),
}
}
}
pub trait CanDerive {
fn can_derive(&self, key: &DefiniteDescriptorKey) -> Option<DerivationPath>;
}
impl CanDerive for KeySource {
fn can_derive(&self, key: &DefiniteDescriptorKey) -> Option<DerivationPath> {
match DescriptorPublicKey::from(key.clone()) {
DescriptorPublicKey::Single(single_pub) => {
path_to_child(self, single_pub.origin.as_ref()?, None)
}
DescriptorPublicKey::XPub(dxk) => {
let origin = dxk.origin.clone().unwrap_or_else(|| {
let secp = Secp256k1::signing_only();
(dxk.xkey.xkey_fingerprint(&secp), DerivationPath::master())
});
path_to_child(self, &origin, Some(&dxk.derivation_path))
}
}
}
}
impl CanDerive for DescriptorPublicKey {
fn can_derive(&self, key: &DefiniteDescriptorKey) -> Option<DerivationPath> {
match (self, DescriptorPublicKey::from(key.clone())) {
(parent, child) if parent == &child => Some(DerivationPath::master()),
(DescriptorPublicKey::XPub(parent), _) => {
let origin = parent.origin.clone().unwrap_or_else(|| {
let secp = Secp256k1::signing_only();
(
parent.xkey.xkey_fingerprint(&secp),
DerivationPath::master(),
)
});
KeySource::from(origin).can_derive(key)
}
_ => None,
}
}
}
fn path_to_child(
parent: &KeySource,
child_origin: &(Fingerprint, DerivationPath),
child_derivation: Option<&DerivationPath>,
) -> Option<DerivationPath> {
if parent.0 == child_origin.0 {
let mut remaining_derivation =
DerivationPath::from(child_origin.1[..].strip_prefix(&parent.1[..])?);
remaining_derivation =
remaining_derivation.extend(child_derivation.unwrap_or(&DerivationPath::master()));
Some(remaining_derivation)
} else {
None
}
}
pub fn plan_satisfaction<Ak>(
desc: &Descriptor<DefiniteDescriptorKey>,
assets: &Assets<Ak>,
) -> Option<Plan<Ak>>
where
Ak: CanDerive + Clone,
{
match desc {
Descriptor::Bare(_) => todo!(),
Descriptor::Pkh(_) => todo!(),
Descriptor::Wpkh(_) => todo!(),
Descriptor::Sh(_) => todo!(),
Descriptor::Wsh(_) => todo!(),
Descriptor::Tr(tr) => crate::plan_impls::plan_satisfaction_tr(tr, assets),
}
}

View File

@@ -0,0 +1,323 @@
use bdk_chain::{bitcoin, miniscript};
use bitcoin::locktime::{Height, Time};
use miniscript::Terminal;
use super::*;
impl<Ak> TermPlan<Ak> {
fn combine(self, other: Self) -> Option<Self> {
let min_locktime = {
match (self.min_locktime, other.min_locktime) {
(Some(lhs), Some(rhs)) => {
if lhs.is_same_unit(rhs) {
Some(if lhs.to_consensus_u32() > rhs.to_consensus_u32() {
lhs
} else {
rhs
})
} else {
return None;
}
}
_ => self.min_locktime.or(other.min_locktime),
}
};
let min_sequence = {
match (self.min_sequence, other.min_sequence) {
(Some(lhs), Some(rhs)) => {
if lhs.is_height_locked() == rhs.is_height_locked() {
Some(if lhs.to_consensus_u32() > rhs.to_consensus_u32() {
lhs
} else {
rhs
})
} else {
return None;
}
}
_ => self.min_sequence.or(other.min_sequence),
}
};
let mut template = self.template;
template.extend(other.template);
Some(Self {
min_locktime,
min_sequence,
template,
})
}
pub(crate) fn expected_size(&self) -> usize {
self.template.iter().map(|step| step.expected_size()).sum()
}
}
// impl crate::descriptor::Pkh<DefiniteDescriptorKey> {
// pub(crate) fn plan_satisfaction<Ak>(&self, assets: &Assets<Ak>) -> Option<Plan<Ak>>
// where
// Ak: CanDerive + Clone,
// {
// let (asset_key, derivation_hint) = assets.keys.iter().find_map(|asset_key| {
// let derivation_hint = asset_key.can_derive(self.as_inner())?;
// Some((asset_key, derivation_hint))
// })?;
// Some(Plan {
// template: vec![TemplateItem::Sign(PlanKey {
// asset_key: asset_key.clone(),
// descriptor_key: self.as_inner().clone(),
// derivation_hint,
// })],
// target: Target::Legacy,
// set_locktime: None,
// set_sequence: None,
// })
// }
// }
// impl crate::descriptor::Wpkh<DefiniteDescriptorKey> {
// pub(crate) fn plan_satisfaction<Ak>(&self, assets: &Assets<Ak>) -> Option<Plan<Ak>>
// where
// Ak: CanDerive + Clone,
// {
// let (asset_key, derivation_hint) = assets.keys.iter().find_map(|asset_key| {
// let derivation_hint = asset_key.can_derive(self.as_inner())?;
// Some((asset_key, derivation_hint))
// })?;
// Some(Plan {
// template: vec![TemplateItem::Sign(PlanKey {
// asset_key: asset_key.clone(),
// descriptor_key: self.as_inner().clone(),
// derivation_hint,
// })],
// target: Target::Segwitv0,
// set_locktime: None,
// set_sequence: None,
// })
// }
// }
pub(crate) fn plan_satisfaction_tr<Ak>(
tr: &miniscript::descriptor::Tr<DefiniteDescriptorKey>,
assets: &Assets<Ak>,
) -> Option<Plan<Ak>>
where
Ak: CanDerive + Clone,
{
let key_path_spend = assets.keys.iter().find_map(|asset_key| {
let derivation_hint = asset_key.can_derive(tr.internal_key())?;
Some((asset_key, derivation_hint))
});
if let Some((asset_key, derivation_hint)) = key_path_spend {
return Some(Plan {
template: vec![TemplateItem::Sign(PlanKey {
asset_key: asset_key.clone(),
descriptor_key: tr.internal_key().clone(),
derivation_hint,
})],
target: Target::Segwitv1 {
tr: tr.clone(),
tr_plan: TrSpend::KeySpend,
},
set_locktime: None,
set_sequence: None,
});
}
let mut plans = tr
.iter_scripts()
.filter_map(|(_, ms)| Some((ms, (plan_steps(&ms.node, assets)?))))
.collect::<Vec<_>>();
plans.sort_by_cached_key(|(_, plan)| plan.expected_size());
let (script, best_plan) = plans.into_iter().next()?;
Some(Plan {
target: Target::Segwitv1 {
tr: tr.clone(),
tr_plan: TrSpend::LeafSpend {
script: script.encode(),
leaf_version: LeafVersion::TapScript,
},
},
set_locktime: best_plan.min_locktime.clone(),
set_sequence: best_plan.min_sequence.clone(),
template: best_plan.template,
})
}
#[derive(Debug)]
struct TermPlan<Ak> {
pub min_locktime: Option<LockTime>,
pub min_sequence: Option<Sequence>,
pub template: Vec<TemplateItem<Ak>>,
}
impl<Ak> TermPlan<Ak> {
fn new(template: Vec<TemplateItem<Ak>>) -> Self {
TermPlan {
template,
..Default::default()
}
}
}
impl<Ak> Default for TermPlan<Ak> {
fn default() -> Self {
Self {
min_locktime: Default::default(),
min_sequence: Default::default(),
template: Default::default(),
}
}
}
fn plan_steps<Ak: Clone + CanDerive, Ctx: ScriptContext>(
term: &Terminal<DefiniteDescriptorKey, Ctx>,
assets: &Assets<Ak>,
) -> Option<TermPlan<Ak>> {
match term {
Terminal::True => Some(TermPlan::new(vec![])),
Terminal::False => return None,
Terminal::PkH(key) => {
let (asset_key, derivation_hint) = assets
.keys
.iter()
.find_map(|asset_key| Some((asset_key, asset_key.can_derive(key)?)))?;
Some(TermPlan::new(vec![
TemplateItem::Sign(PlanKey {
asset_key: asset_key.clone(),
derivation_hint,
descriptor_key: key.clone(),
}),
TemplateItem::Pk { key: key.clone() },
]))
}
Terminal::PkK(key) => {
let (asset_key, derivation_hint) = assets
.keys
.iter()
.find_map(|asset_key| Some((asset_key, asset_key.can_derive(key)?)))?;
Some(TermPlan::new(vec![TemplateItem::Sign(PlanKey {
asset_key: asset_key.clone(),
derivation_hint,
descriptor_key: key.clone(),
})]))
}
Terminal::RawPkH(_pk_hash) => {
/* TODO */
None
}
Terminal::After(locktime) => {
let max_locktime = assets.max_locktime?;
let locktime = LockTime::from(locktime);
let (height, time) = match max_locktime {
LockTime::Blocks(height) => (height, Time::from_consensus(0).unwrap()),
LockTime::Seconds(seconds) => (Height::from_consensus(0).unwrap(), seconds),
};
if max_locktime.is_satisfied_by(height, time) {
Some(TermPlan {
min_locktime: Some(locktime),
..Default::default()
})
} else {
None
}
}
Terminal::Older(older) => {
// FIXME: older should be a height or time not a sequence.
let max_sequence = assets.txo_age?;
//TODO: this whole thing is probably wrong but upstream should provide a way of
// doing it properly.
if max_sequence.is_height_locked() == older.is_height_locked() {
if max_sequence.to_consensus_u32() >= older.to_consensus_u32() {
Some(TermPlan {
min_sequence: Some(*older),
..Default::default()
})
} else {
None
}
} else {
None
}
}
Terminal::Sha256(image) => {
if assets.sha256.contains(&image) {
Some(TermPlan::new(vec![TemplateItem::Sha256(image.clone())]))
} else {
None
}
}
Terminal::Hash256(image) => {
if assets.hash256.contains(image) {
Some(TermPlan::new(vec![TemplateItem::Hash256(image.clone())]))
} else {
None
}
}
Terminal::Ripemd160(image) => {
if assets.ripemd160.contains(&image) {
Some(TermPlan::new(vec![TemplateItem::Ripemd160(image.clone())]))
} else {
None
}
}
Terminal::Hash160(image) => {
if assets.hash160.contains(&image) {
Some(TermPlan::new(vec![TemplateItem::Hash160(image.clone())]))
} else {
None
}
}
Terminal::Alt(ms)
| Terminal::Swap(ms)
| Terminal::Check(ms)
| Terminal::Verify(ms)
| Terminal::NonZero(ms)
| Terminal::ZeroNotEqual(ms) => plan_steps(&ms.node, assets),
Terminal::DupIf(ms) => {
let mut plan = plan_steps(&ms.node, assets)?;
plan.template.push(TemplateItem::One);
Some(plan)
}
Terminal::AndV(l, r) | Terminal::AndB(l, r) => {
let lhs = plan_steps(&l.node, assets)?;
let rhs = plan_steps(&r.node, assets)?;
lhs.combine(rhs)
}
Terminal::AndOr(_, _, _) => todo!(),
Terminal::OrB(_, _) => todo!(),
Terminal::OrD(_, _) => todo!(),
Terminal::OrC(_, _) => todo!(),
Terminal::OrI(lhs, rhs) => {
let lplan = plan_steps(&lhs.node, assets).map(|mut plan| {
plan.template.push(TemplateItem::One);
plan
});
let rplan = plan_steps(&rhs.node, assets).map(|mut plan| {
plan.template.push(TemplateItem::Zero);
plan
});
match (lplan, rplan) {
(Some(lplan), Some(rplan)) => {
if lplan.expected_size() <= rplan.expected_size() {
Some(lplan)
} else {
Some(rplan)
}
}
(lplan, rplan) => lplan.or(rplan),
}
}
Terminal::Thresh(_, _) => todo!(),
Terminal::Multi(_, _) => todo!(),
Terminal::MultiA(_, _) => todo!(),
}
}

View File

@@ -0,0 +1,218 @@
use bdk_chain::{bitcoin, collections::*, miniscript};
use core::ops::Deref;
use bitcoin::{
hashes::{hash160, ripemd160, sha256},
psbt::Prevouts,
secp256k1::{KeyPair, Message, PublicKey, Signing, Verification},
util::{bip32, sighash, sighash::SighashCache, taproot},
EcdsaSighashType, SchnorrSighashType, Transaction, TxOut, XOnlyPublicKey,
};
use super::*;
use miniscript::{
descriptor::{DescriptorSecretKey, KeyMap},
hash256,
};
#[derive(Clone, Debug)]
/// Signatures and hash pre-images that must be provided to complete the plan.
pub struct Requirements<Ak> {
/// required signatures
pub signatures: RequiredSignatures<Ak>,
/// required sha256 pre-images
pub sha256_images: HashSet<sha256::Hash>,
/// required hash160 pre-images
pub hash160_images: HashSet<hash160::Hash>,
/// required hash256 pre-images
pub hash256_images: HashSet<hash256::Hash>,
/// required ripemd160 pre-images
pub ripemd160_images: HashSet<ripemd160::Hash>,
}
impl<Ak> Default for RequiredSignatures<Ak> {
fn default() -> Self {
RequiredSignatures::Legacy {
keys: Default::default(),
}
}
}
impl<Ak> Default for Requirements<Ak> {
fn default() -> Self {
Self {
signatures: Default::default(),
sha256_images: Default::default(),
hash160_images: Default::default(),
hash256_images: Default::default(),
ripemd160_images: Default::default(),
}
}
}
impl<Ak> Requirements<Ak> {
/// Whether any hash pre-images are required in the plan
pub fn requires_hash_preimages(&self) -> bool {
!(self.sha256_images.is_empty()
&& self.hash160_images.is_empty()
&& self.hash256_images.is_empty()
&& self.ripemd160_images.is_empty())
}
}
/// The signatures required to complete the plan
#[derive(Clone, Debug)]
pub enum RequiredSignatures<Ak> {
/// Legacy ECDSA signatures are required
Legacy { keys: Vec<PlanKey<Ak>> },
/// Segwitv0 ECDSA signatures are required
Segwitv0 { keys: Vec<PlanKey<Ak>> },
/// A Taproot key spend signature is required
TapKey {
/// the internal key
plan_key: PlanKey<Ak>,
/// The merkle root of the taproot output
merkle_root: Option<TapBranchHash>,
},
/// Taproot script path signatures are required
TapScript {
/// The leaf hash of the script being used
leaf_hash: TapLeafHash,
/// The keys in the script that require signatures
plan_keys: Vec<PlanKey<Ak>>,
},
}
#[derive(Clone, Debug)]
pub enum SigningError {
SigHashError(sighash::Error),
DerivationError(bip32::Error),
}
impl From<sighash::Error> for SigningError {
fn from(e: sighash::Error) -> Self {
Self::SigHashError(e)
}
}
impl core::fmt::Display for SigningError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SigningError::SigHashError(e) => e.fmt(f),
SigningError::DerivationError(e) => e.fmt(f),
}
}
}
impl From<bip32::Error> for SigningError {
fn from(e: bip32::Error) -> Self {
Self::DerivationError(e)
}
}
#[cfg(feature = "std")]
impl std::error::Error for SigningError {}
impl RequiredSignatures<DescriptorPublicKey> {
pub fn sign_with_keymap<T: Deref<Target = Transaction>>(
&self,
input_index: usize,
keymap: &KeyMap,
prevouts: &Prevouts<'_, impl core::borrow::Borrow<TxOut>>,
schnorr_sighashty: Option<SchnorrSighashType>,
_ecdsa_sighashty: Option<EcdsaSighashType>,
sighash_cache: &mut SighashCache<T>,
auth_data: &mut SatisfactionMaterial,
secp: &Secp256k1<impl Signing + Verification>,
) -> Result<bool, SigningError> {
match self {
RequiredSignatures::Legacy { .. } | RequiredSignatures::Segwitv0 { .. } => todo!(),
RequiredSignatures::TapKey {
plan_key,
merkle_root,
} => {
let schnorr_sighashty = schnorr_sighashty.unwrap_or(SchnorrSighashType::Default);
let sighash = sighash_cache.taproot_key_spend_signature_hash(
input_index,
prevouts,
schnorr_sighashty,
)?;
let secret_key = match keymap.get(&plan_key.asset_key) {
Some(secret_key) => secret_key,
None => return Ok(false),
};
let secret_key = match secret_key {
DescriptorSecretKey::Single(single) => single.key.inner,
DescriptorSecretKey::XPrv(xprv) => {
xprv.xkey
.derive_priv(&secp, &plan_key.derivation_hint)?
.private_key
}
};
let pubkey = PublicKey::from_secret_key(&secp, &secret_key);
let x_only_pubkey = XOnlyPublicKey::from(pubkey);
let tweak =
taproot::TapTweakHash::from_key_and_tweak(x_only_pubkey, merkle_root.clone());
let keypair = KeyPair::from_secret_key(&secp, &secret_key.clone())
.add_xonly_tweak(&secp, &tweak.to_scalar())
.unwrap();
let msg = Message::from_slice(sighash.as_ref()).expect("Sighashes are 32 bytes");
let sig = secp.sign_schnorr_no_aux_rand(&msg, &keypair);
let bitcoin_sig = SchnorrSig {
sig,
hash_ty: schnorr_sighashty,
};
auth_data
.schnorr_sigs
.insert(plan_key.descriptor_key.clone(), bitcoin_sig);
Ok(true)
}
RequiredSignatures::TapScript {
leaf_hash,
plan_keys,
} => {
let sighash_type = schnorr_sighashty.unwrap_or(SchnorrSighashType::Default);
let sighash = sighash_cache.taproot_script_spend_signature_hash(
input_index,
prevouts,
*leaf_hash,
sighash_type,
)?;
let mut modified = false;
for plan_key in plan_keys {
if let Some(secret_key) = keymap.get(&plan_key.asset_key) {
let secret_key = match secret_key {
DescriptorSecretKey::Single(single) => single.key.inner,
DescriptorSecretKey::XPrv(xprv) => {
xprv.xkey
.derive_priv(&secp, &plan_key.derivation_hint)?
.private_key
}
};
let keypair = KeyPair::from_secret_key(&secp, &secret_key.clone());
let msg =
Message::from_slice(sighash.as_ref()).expect("Sighashes are 32 bytes");
let sig = secp.sign_schnorr_no_aux_rand(&msg, &keypair);
let bitcoin_sig = SchnorrSig {
sig,
hash_ty: sighash_type,
};
auth_data
.schnorr_sigs
.insert(plan_key.descriptor_key.clone(), bitcoin_sig);
modified = true;
}
}
Ok(modified)
}
}
}
}

View File

@@ -0,0 +1,76 @@
use bdk_chain::{bitcoin, miniscript};
use bitcoin::{
hashes::{hash160, ripemd160, sha256},
util::bip32::DerivationPath,
};
use super::*;
use crate::{hash256, varint_len, DefiniteDescriptorKey};
#[derive(Clone, Debug)]
pub(crate) enum TemplateItem<Ak> {
Sign(PlanKey<Ak>),
Pk { key: DefiniteDescriptorKey },
One,
Zero,
Sha256(sha256::Hash),
Hash256(hash256::Hash),
Ripemd160(ripemd160::Hash),
Hash160(hash160::Hash),
}
/// A plan key contains the asset key originally provided along with key in the descriptor it
/// purports to be able to derive for along with a "hint" on how to derive it.
#[derive(Clone, Debug)]
pub struct PlanKey<Ak> {
/// The key the planner will sign with
pub asset_key: Ak,
/// A hint from how to get from the asset key to the concrete key we need to sign with.
pub derivation_hint: DerivationPath,
/// The key that was in the descriptor that we are satisfying with the signature from the asset
/// key.
pub descriptor_key: DefiniteDescriptorKey,
}
impl<Ak> TemplateItem<Ak> {
pub fn expected_size(&self) -> usize {
match self {
TemplateItem::Sign { .. } => 64, /*size of sig TODO: take into consideration sighash falg*/
TemplateItem::Pk { .. } => 32,
TemplateItem::One => varint_len(1),
TemplateItem::Zero => 0, /* zero means an empty witness element */
// I'm not sure if it should be 32 here (it's a 20 byte hash) but that's what other
// parts of the code were doing.
TemplateItem::Hash160(_) | TemplateItem::Ripemd160(_) => 32,
TemplateItem::Sha256(_) | TemplateItem::Hash256(_) => 32,
}
}
// this can only be called if we are sure that auth_data has what we need
pub(super) fn to_witness_stack(&self, auth_data: &SatisfactionMaterial) -> Vec<Vec<u8>> {
match self {
TemplateItem::Sign(plan_key) => {
vec![auth_data
.schnorr_sigs
.get(&plan_key.descriptor_key)
.unwrap()
.to_vec()]
}
TemplateItem::One => vec![vec![1]],
TemplateItem::Zero => vec![vec![]],
TemplateItem::Sha256(image) => {
vec![auth_data.sha256_preimages.get(image).unwrap().to_vec()]
}
TemplateItem::Hash160(image) => {
vec![auth_data.hash160_preimages.get(image).unwrap().to_vec()]
}
TemplateItem::Ripemd160(image) => {
vec![auth_data.ripemd160_preimages.get(image).unwrap().to_vec()]
}
TemplateItem::Hash256(image) => {
vec![auth_data.hash256_preimages.get(image).unwrap().to_vec()]
}
TemplateItem::Pk { key } => vec![key.to_public_key().to_bytes()],
}
}
}