Skip to content

Commit cf3f2de

Browse files
Add plan capabilities to miniscript
Add a `plan` module that contains utilities to calculate the cheapest spending path given an AssetProvider (that could keys, preimages, or timelocks). Adds a `get_plan` method on the various descriptor types. Co-authored-by: Daniela Brozzoni <[email protected]>
1 parent 1dad95a commit cf3f2de

File tree

12 files changed

+1365
-183
lines changed

12 files changed

+1365
-183
lines changed

src/descriptor/bare.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ use bitcoin::blockdata::script;
2424
use bitcoin::{Address, Network, Script};
2525

2626
use super::checksum::{self, verify_checksum};
27+
use crate::descriptor::{DefiniteDescriptorKey, DescriptorType};
2728
use crate::expression::{self, FromTree};
2829
use crate::miniscript::context::ScriptContext;
30+
use crate::miniscript::satisfy::{Placeholder, WitnessTemplate};
31+
use crate::plan::{AssetProvider, Plan};
2932
use crate::policy::{semantic, Liftable};
3033
use crate::prelude::*;
3134
use crate::util::{varint_len, witness_to_scriptsig};
@@ -124,6 +127,28 @@ impl<Pk: MiniscriptKey + ToPublicKey> Bare<Pk> {
124127
}
125128
}
126129

130+
impl Bare<DefiniteDescriptorKey> {
131+
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
132+
pub fn get_plan<P>(&self, provider: &P) -> Option<Plan>
133+
where
134+
P: AssetProvider<DefiniteDescriptorKey>,
135+
{
136+
self.ms
137+
.build_template(provider)
138+
.into_plan(DescriptorType::Bare)
139+
}
140+
141+
/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
142+
pub fn get_plan_mall<P>(&self, provider: &P) -> Option<Plan>
143+
where
144+
P: AssetProvider<DefiniteDescriptorKey>,
145+
{
146+
self.ms
147+
.build_template_mall(provider)
148+
.into_plan(DescriptorType::Bare)
149+
}
150+
}
151+
127152
impl<Pk: MiniscriptKey> fmt::Debug for Bare<Pk> {
128153
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129154
write!(f, "{:?}", self.ms)
@@ -278,6 +303,37 @@ impl<Pk: MiniscriptKey + ToPublicKey> Pkh<Pk> {
278303
}
279304
}
280305

306+
impl Pkh<DefiniteDescriptorKey> {
307+
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
308+
pub fn get_plan<P>(&self, provider: &P) -> Option<Plan>
309+
where
310+
P: AssetProvider<DefiniteDescriptorKey>,
311+
{
312+
if provider.lookup_ecdsa_sig(&self.pk) {
313+
let stack = vec![
314+
Placeholder::EcdsaSigPk(self.pk.clone()),
315+
Placeholder::Pubkey(self.pk.clone(), BareCtx::pk_len(&self.pk)),
316+
];
317+
Some(Plan {
318+
relative_timelock: None,
319+
absolute_timelock: None,
320+
desc_type: DescriptorType::Pkh,
321+
template: WitnessTemplate::from_placeholder_stack(stack),
322+
})
323+
} else {
324+
None
325+
}
326+
}
327+
328+
/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
329+
pub fn get_plan_mall<P>(&self, provider: &P) -> Option<Plan>
330+
where
331+
P: AssetProvider<DefiniteDescriptorKey>,
332+
{
333+
self.get_plan(provider)
334+
}
335+
}
336+
281337
impl<Pk: MiniscriptKey> fmt::Debug for Pkh<Pk> {
282338
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
283339
write!(f, "pkh({:?})", self.pk)

src/descriptor/key.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ pub enum SinglePubKey {
7272

7373
/// A [`DescriptorPublicKey`] without any wildcards.
7474
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)]
75-
pub struct DefiniteDescriptorKey(DescriptorPublicKey);
75+
pub struct DefiniteDescriptorKey(pub(super) DescriptorPublicKey);
7676

7777
impl fmt::Display for DescriptorSecretKey {
7878
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -485,6 +485,27 @@ impl DescriptorPublicKey {
485485
DefiniteDescriptorKey::new(definite)
486486
.expect("The key should not contain any wildcards at this point")
487487
}
488+
489+
/// Whether this key matches a [`DefiniteDescriptorKey`]
490+
///
491+
/// Returns `true` if this key is the un-derived version of the definite key passed in.
492+
pub fn matches(&self, definite_key: &DefiniteDescriptorKey) -> bool {
493+
// If the key is `Single` or it's an `XPub` with no wildcard it will match the definite key
494+
// exactly, so we try this check first
495+
if self == &definite_key.0 {
496+
return true;
497+
}
498+
499+
match (self, &definite_key.0) {
500+
(DescriptorPublicKey::XPub(self_xkey), DescriptorPublicKey::XPub(definite_xkey)) => {
501+
self_xkey.origin == definite_xkey.origin
502+
&& self_xkey.xkey == definite_xkey.xkey
503+
&& self_xkey.derivation_path.as_ref()
504+
== &definite_xkey.derivation_path[..(self_xkey.derivation_path.len() - 1)]
505+
}
506+
_ => false,
507+
}
508+
}
488509
}
489510

490511
impl FromStr for DescriptorSecretKey {

src/descriptor/mod.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use sync::Arc;
3535

3636
use self::checksum::verify_checksum;
3737
use crate::miniscript::{Legacy, Miniscript, Segwitv0};
38+
use crate::plan::{AssetProvider, Plan};
3839
use crate::prelude::*;
3940
use crate::{
4041
expression, hash256, miniscript, BareCtx, Error, ForEachKey, MiniscriptKey, Satisfier,
@@ -435,7 +436,7 @@ impl<Pk: MiniscriptKey + ToPublicKey> Descriptor<Pk> {
435436
Descriptor::Wpkh(ref wpkh) => wpkh.get_satisfaction(satisfier),
436437
Descriptor::Wsh(ref wsh) => wsh.get_satisfaction(satisfier),
437438
Descriptor::Sh(ref sh) => sh.get_satisfaction(satisfier),
438-
Descriptor::Tr(ref tr) => tr.get_satisfaction(satisfier),
439+
Descriptor::Tr(ref tr) => tr.get_satisfaction(&satisfier),
439440
}
440441
}
441442

@@ -452,7 +453,7 @@ impl<Pk: MiniscriptKey + ToPublicKey> Descriptor<Pk> {
452453
Descriptor::Wpkh(ref wpkh) => wpkh.get_satisfaction_mall(satisfier),
453454
Descriptor::Wsh(ref wsh) => wsh.get_satisfaction_mall(satisfier),
454455
Descriptor::Sh(ref sh) => sh.get_satisfaction_mall(satisfier),
455-
Descriptor::Tr(ref tr) => tr.get_satisfaction_mall(satisfier),
456+
Descriptor::Tr(ref tr) => tr.get_satisfaction_mall(&satisfier),
456457
}
457458
}
458459

@@ -470,6 +471,38 @@ impl<Pk: MiniscriptKey + ToPublicKey> Descriptor<Pk> {
470471
}
471472
}
472473

474+
impl Descriptor<DefiniteDescriptorKey> {
475+
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
476+
pub fn get_plan<P>(&self, provider: &P) -> Option<Plan>
477+
where
478+
P: AssetProvider<DefiniteDescriptorKey>,
479+
{
480+
match *self {
481+
Descriptor::Bare(ref bare) => bare.get_plan(provider),
482+
Descriptor::Pkh(ref pkh) => pkh.get_plan(provider),
483+
Descriptor::Wpkh(ref wpkh) => wpkh.get_plan(provider),
484+
Descriptor::Wsh(ref wsh) => wsh.get_plan(provider),
485+
Descriptor::Sh(ref sh) => sh.get_plan(provider),
486+
Descriptor::Tr(ref tr) => tr.get_plan(provider),
487+
}
488+
}
489+
490+
/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
491+
pub fn get_plan_mall<P>(&self, provider: &P) -> Option<Plan>
492+
where
493+
P: AssetProvider<DefiniteDescriptorKey>,
494+
{
495+
match *self {
496+
Descriptor::Bare(ref bare) => bare.get_plan_mall(provider),
497+
Descriptor::Pkh(ref pkh) => pkh.get_plan_mall(provider),
498+
Descriptor::Wpkh(ref wpkh) => wpkh.get_plan_mall(provider),
499+
Descriptor::Wsh(ref wsh) => wsh.get_plan_mall(provider),
500+
Descriptor::Sh(ref sh) => sh.get_plan_mall(provider),
501+
Descriptor::Tr(ref tr) => tr.get_plan_mall(provider),
502+
}
503+
}
504+
}
505+
473506
impl<P, Q> TranslatePk<P, Q> for Descriptor<P>
474507
where
475508
P: MiniscriptKey,

src/descriptor/segwitv0.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ use bitcoin::{self, Address, Network, Script};
2222

2323
use super::checksum::{self, verify_checksum};
2424
use super::SortedMultiVec;
25+
use crate::descriptor::{DefiniteDescriptorKey, DescriptorType};
2526
use crate::expression::{self, FromTree};
2627
use crate::miniscript::context::{ScriptContext, ScriptContextError};
28+
use crate::miniscript::satisfy::{Placeholder, WitnessTemplate};
29+
use crate::plan::{AssetProvider, Plan};
2730
use crate::policy::{semantic, Liftable};
2831
use crate::prelude::*;
2932
use crate::util::varint_len;
@@ -173,6 +176,32 @@ impl<Pk: MiniscriptKey + ToPublicKey> Wsh<Pk> {
173176
}
174177
}
175178

179+
impl Wsh<DefiniteDescriptorKey> {
180+
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
181+
pub fn get_plan<P>(&self, provider: &P) -> Option<Plan>
182+
where
183+
P: AssetProvider<DefiniteDescriptorKey>,
184+
{
185+
match &self.inner {
186+
WshInner::SortedMulti(sm) => sm.build_template(provider).into_plan(DescriptorType::Wsh),
187+
WshInner::Ms(ms) => ms.build_template(provider).into_plan(DescriptorType::Wsh),
188+
}
189+
}
190+
191+
/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
192+
pub fn get_plan_mall<P>(&self, provider: &P) -> Option<Plan>
193+
where
194+
P: AssetProvider<DefiniteDescriptorKey>,
195+
{
196+
match &self.inner {
197+
WshInner::SortedMulti(sm) => sm.build_template(provider).into_plan(DescriptorType::Wsh),
198+
WshInner::Ms(ms) => ms
199+
.build_template_mall(provider)
200+
.into_plan(DescriptorType::Wsh),
201+
}
202+
}
203+
}
204+
176205
/// Wsh Inner
177206
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
178207
pub enum WshInner<Pk: MiniscriptKey> {
@@ -393,6 +422,37 @@ impl<Pk: MiniscriptKey + ToPublicKey> Wpkh<Pk> {
393422
}
394423
}
395424

425+
impl Wpkh<DefiniteDescriptorKey> {
426+
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
427+
pub fn get_plan<P>(&self, provider: &P) -> Option<Plan>
428+
where
429+
P: AssetProvider<DefiniteDescriptorKey>,
430+
{
431+
if provider.lookup_ecdsa_sig(&self.pk) {
432+
let stack = vec![
433+
Placeholder::EcdsaSigPk(self.pk.clone()),
434+
Placeholder::Pubkey(self.pk.clone(), Segwitv0::pk_len(&self.pk)),
435+
];
436+
Some(Plan {
437+
relative_timelock: None,
438+
absolute_timelock: None,
439+
desc_type: DescriptorType::Wpkh,
440+
template: WitnessTemplate::from_placeholder_stack(stack),
441+
})
442+
} else {
443+
None
444+
}
445+
}
446+
447+
/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
448+
pub fn get_plan_mall<P>(&self, provider: &P) -> Option<Plan>
449+
where
450+
P: AssetProvider<DefiniteDescriptorKey>,
451+
{
452+
self.get_plan(provider)
453+
}
454+
}
455+
396456
impl<Pk: MiniscriptKey> fmt::Debug for Wpkh<Pk> {
397457
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
398458
write!(f, "wpkh({:?})", self.pk)

src/descriptor/sh.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ use bitcoin::{Address, Network, Script};
2525

2626
use super::checksum::{self, verify_checksum};
2727
use super::{SortedMultiVec, Wpkh, Wsh};
28+
use crate::descriptor::{DefiniteDescriptorKey, DescriptorType};
2829
use crate::expression::{self, FromTree};
2930
use crate::miniscript::context::ScriptContext;
31+
use crate::plan::{AssetProvider, Plan};
3032
use crate::policy::{semantic, Liftable};
3133
use crate::prelude::*;
3234
use crate::util::{varint_len, witness_to_scriptsig};
@@ -377,6 +379,44 @@ impl<Pk: MiniscriptKey + ToPublicKey> Sh<Pk> {
377379
}
378380
}
379381

382+
impl Sh<DefiniteDescriptorKey> {
383+
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
384+
pub fn get_plan<P>(&self, provider: &P) -> Option<Plan>
385+
where
386+
P: AssetProvider<DefiniteDescriptorKey>,
387+
{
388+
match &self.inner {
389+
ShInner::Wsh(ref wsh) => wsh.get_plan(provider).map(|mut plan| {
390+
plan.desc_type = DescriptorType::ShWsh;
391+
plan
392+
}),
393+
ShInner::Wpkh(ref wpkh) => wpkh.get_plan(provider).map(|mut plan| {
394+
plan.desc_type = DescriptorType::ShWpkh;
395+
plan
396+
}),
397+
ShInner::SortedMulti(ref _smv) => todo!(),
398+
ShInner::Ms(ref ms) => ms.build_template(provider).into_plan(DescriptorType::Sh),
399+
}
400+
}
401+
402+
/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
403+
pub fn get_plan_mall<P>(&self, provider: &P) -> Option<Plan>
404+
where
405+
P: AssetProvider<DefiniteDescriptorKey>,
406+
{
407+
match &self.inner {
408+
ShInner::Wsh(ref wsh) => wsh.get_plan_mall(provider).map(|mut plan| {
409+
plan.desc_type = DescriptorType::ShWsh;
410+
plan
411+
}),
412+
ShInner::Ms(ref ms) => ms
413+
.build_template_mall(provider)
414+
.into_plan(DescriptorType::Sh),
415+
_ => self.get_plan(provider),
416+
}
417+
}
418+
}
419+
380420
impl<Pk: MiniscriptKey> ForEachKey<Pk> for Sh<Pk> {
381421
fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, pred: F) -> bool
382422
where

src/descriptor/sortedmulti.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ use bitcoin::blockdata::script;
2525
use crate::miniscript::context::ScriptContext;
2626
use crate::miniscript::decode::Terminal;
2727
use crate::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG;
28+
use crate::miniscript::satisfy::{Placeholder, Satisfaction};
29+
use crate::plan::AssetProvider;
2830
use crate::prelude::*;
2931
use crate::{
3032
errstr, expression, miniscript, policy, script_num_size, Error, ForEachKey, Miniscript,
@@ -171,6 +173,16 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
171173
ms.satisfy(satisfier)
172174
}
173175

176+
/// Attempt to produce a witness template given the assets available
177+
pub fn build_template<P>(&self, provider: &P) -> Satisfaction<Placeholder<Pk>>
178+
where
179+
Pk: ToPublicKey,
180+
P: AssetProvider<Pk>,
181+
{
182+
let ms = Miniscript::from_ast(self.sorted_node()).expect("Multi node typecheck");
183+
ms.build_template(provider)
184+
}
185+
174186
/// Size, in bytes of the script-pubkey. If this Miniscript is used outside
175187
/// of segwit (e.g. in a bare or P2SH descriptor), this quantity should be
176188
/// multiplied by 4 to compute the weight.

0 commit comments

Comments
 (0)