Skip to content

Commit

Permalink
Add plan capabilities to miniscript
Browse files Browse the repository at this point in the history
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 <danielabrozzoni@protonmail.com>
  • Loading branch information
afilini and danielabrozzoni committed Oct 25, 2022
1 parent 85cf850 commit dc0b280
Show file tree
Hide file tree
Showing 12 changed files with 1,336 additions and 159 deletions.
56 changes: 56 additions & 0 deletions src/descriptor/bare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ use bitcoin::blockdata::script;
use bitcoin::{Address, Network, Script};

use super::checksum::{self, verify_checksum};
use crate::descriptor::{DefiniteDescriptorKey, DescriptorType};
use crate::expression::{self, FromTree};
use crate::miniscript::context::ScriptContext;
use crate::miniscript::satisfy::{Placeholder, WitnessTemplate};
use crate::plan::{AssetProvider, Plan};
use crate::policy::{semantic, Liftable};
use crate::prelude::*;
use crate::util::{varint_len, witness_to_scriptsig};
Expand Down Expand Up @@ -124,6 +127,28 @@ impl<Pk: MiniscriptKey + ToPublicKey> Bare<Pk> {
}
}

impl Bare<DefiniteDescriptorKey> {
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
pub fn get_plan<P>(&self, provider: &P) -> Option<Plan>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
self.ms
.build_template(provider)
.into_plan(DescriptorType::Bare)
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
pub fn get_plan_mall<P>(&self, provider: &P) -> Option<Plan>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
self.ms
.build_template_mall(provider)
.into_plan(DescriptorType::Bare)
}
}

impl<Pk: MiniscriptKey> fmt::Debug for Bare<Pk> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.ms)
Expand Down Expand Up @@ -278,6 +303,37 @@ impl<Pk: MiniscriptKey + ToPublicKey> Pkh<Pk> {
}
}

impl Pkh<DefiniteDescriptorKey> {
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
pub fn get_plan<P>(&self, provider: &P) -> Option<Plan>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
if provider.lookup_ecdsa_sig(&self.pk) {
let stack = vec![
Placeholder::EcdsaSigPk(self.pk.clone()),
Placeholder::Pubkey(self.pk.clone(), BareCtx::pk_len(&self.pk)),
];
Some(Plan {
relative_timelock: None,
absolute_timelock: None,
desc_type: DescriptorType::Pkh,
template: WitnessTemplate::from_placeholder_stack(stack),
})
} else {
None
}
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
pub fn get_plan_mall<P>(&self, provider: &P) -> Option<Plan>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
self.get_plan(provider)
}
}

impl<Pk: MiniscriptKey> fmt::Debug for Pkh<Pk> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "pkh({:?})", self.pk)
Expand Down
2 changes: 1 addition & 1 deletion src/descriptor/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub enum SinglePubKey {

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

impl fmt::Display for DescriptorSecretKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Expand Down
77 changes: 75 additions & 2 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use sync::Arc;

use self::checksum::verify_checksum;
use crate::miniscript::{Legacy, Miniscript, Segwitv0};
use crate::plan::{AssetProvider, Assets, IntoAssets, Plan};
use crate::prelude::*;
use crate::{
expression, hash256, miniscript, BareCtx, Error, ForEachKey, MiniscriptKey, Satisfier,
Expand Down Expand Up @@ -435,7 +436,7 @@ impl<Pk: MiniscriptKey + ToPublicKey> Descriptor<Pk> {
Descriptor::Wpkh(ref wpkh) => wpkh.get_satisfaction(satisfier),
Descriptor::Wsh(ref wsh) => wsh.get_satisfaction(satisfier),
Descriptor::Sh(ref sh) => sh.get_satisfaction(satisfier),
Descriptor::Tr(ref tr) => tr.get_satisfaction(satisfier),
Descriptor::Tr(ref tr) => tr.get_satisfaction(&satisfier),
}
}

Expand All @@ -452,7 +453,7 @@ impl<Pk: MiniscriptKey + ToPublicKey> Descriptor<Pk> {
Descriptor::Wpkh(ref wpkh) => wpkh.get_satisfaction_mall(satisfier),
Descriptor::Wsh(ref wsh) => wsh.get_satisfaction_mall(satisfier),
Descriptor::Sh(ref sh) => sh.get_satisfaction_mall(satisfier),
Descriptor::Tr(ref tr) => tr.get_satisfaction_mall(satisfier),
Descriptor::Tr(ref tr) => tr.get_satisfaction_mall(&satisfier),
}
}

Expand All @@ -470,6 +471,38 @@ impl<Pk: MiniscriptKey + ToPublicKey> Descriptor<Pk> {
}
}

impl Descriptor<DefiniteDescriptorKey> {
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
pub fn get_plan<P>(&self, provider: &P) -> Option<Plan>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
match *self {
Descriptor::Bare(ref bare) => bare.get_plan(provider),
Descriptor::Pkh(ref pkh) => pkh.get_plan(provider),
Descriptor::Wpkh(ref wpkh) => wpkh.get_plan(provider),
Descriptor::Wsh(ref wsh) => wsh.get_plan(provider),
Descriptor::Sh(ref sh) => sh.get_plan(provider),
Descriptor::Tr(ref tr) => tr.get_plan(provider),
}
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
pub fn get_plan_mall<P>(&self, provider: &P) -> Option<Plan>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
match *self {
Descriptor::Bare(ref bare) => bare.get_plan_mall(provider),
Descriptor::Pkh(ref pkh) => pkh.get_plan_mall(provider),
Descriptor::Wpkh(ref wpkh) => wpkh.get_plan_mall(provider),
Descriptor::Wsh(ref wsh) => wsh.get_plan_mall(provider),
Descriptor::Sh(ref sh) => sh.get_plan_mall(provider),
Descriptor::Tr(ref tr) => tr.get_plan_mall(provider),
}
}
}

impl<P, Q> TranslatePk<P, Q> for Descriptor<P>
where
P: MiniscriptKey,
Expand Down Expand Up @@ -771,6 +804,46 @@ impl Descriptor<DefiniteDescriptorKey> {
let derived = self.translate_pk(&mut Derivator(secp))?;
Ok(derived)
}

/// Returns the set of keys which are available in the [`KeyMap`] in form of [`Assets`]
pub fn available_keys(&self, key_map: &KeyMap) -> Assets {
let mut available_keys = vec![];

self.for_each_key(|pk| {
let found = match &pk.0 {
s @ DescriptorPublicKey::Single(_) => key_map.contains_key(&s),
DescriptorPublicKey::XPub(xkey) => {
if key_map.contains_key(&DescriptorPublicKey::XPub(xkey.clone())) {
true
} else if xkey.derivation_path.len() > 0 {
let unwind_wildcard = DescriptorXKey {
origin: xkey.origin.clone(),
xkey: xkey.xkey,
wildcard: Wildcard::Unhardened,
derivation_path: xkey
.derivation_path
.into_iter()
.take(xkey.derivation_path.len() - 1)
.cloned()
.collect::<Vec<_>>()
.into(),
};
key_map.contains_key(&DescriptorPublicKey::XPub(unwind_wildcard))
} else {
false
}
}
};

if found {
available_keys.push(pk.clone());
}

true
});

available_keys.into_assets()
}
}

impl_from_tree!(
Expand Down
60 changes: 60 additions & 0 deletions src/descriptor/segwitv0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ use bitcoin::{self, Address, Network, Script};

use super::checksum::{self, verify_checksum};
use super::SortedMultiVec;
use crate::descriptor::{DefiniteDescriptorKey, DescriptorType};
use crate::expression::{self, FromTree};
use crate::miniscript::context::{ScriptContext, ScriptContextError};
use crate::miniscript::satisfy::{Placeholder, WitnessTemplate};
use crate::plan::{AssetProvider, Plan};
use crate::policy::{semantic, Liftable};
use crate::prelude::*;
use crate::util::varint_len;
Expand Down Expand Up @@ -173,6 +176,32 @@ impl<Pk: MiniscriptKey + ToPublicKey> Wsh<Pk> {
}
}

impl Wsh<DefiniteDescriptorKey> {
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
pub fn get_plan<P>(&self, provider: &P) -> Option<Plan>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
match &self.inner {
WshInner::SortedMulti(sm) => sm.build_template(provider).into_plan(DescriptorType::Wsh),
WshInner::Ms(ms) => ms.build_template(provider).into_plan(DescriptorType::Wsh),
}
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
pub fn get_plan_mall<P>(&self, provider: &P) -> Option<Plan>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
match &self.inner {
WshInner::SortedMulti(sm) => sm.build_template(provider).into_plan(DescriptorType::Wsh),
WshInner::Ms(ms) => ms
.build_template_mall(provider)
.into_plan(DescriptorType::Wsh),
}
}
}

/// Wsh Inner
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum WshInner<Pk: MiniscriptKey> {
Expand Down Expand Up @@ -393,6 +422,37 @@ impl<Pk: MiniscriptKey + ToPublicKey> Wpkh<Pk> {
}
}

impl Wpkh<DefiniteDescriptorKey> {
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
pub fn get_plan<P>(&self, provider: &P) -> Option<Plan>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
if provider.lookup_ecdsa_sig(&self.pk) {
let stack = vec![
Placeholder::EcdsaSigPk(self.pk.clone()),
Placeholder::Pubkey(self.pk.clone(), Segwitv0::pk_len(&self.pk)),
];
Some(Plan {
relative_timelock: None,
absolute_timelock: None,
desc_type: DescriptorType::Wpkh,
template: WitnessTemplate::from_placeholder_stack(stack),
})
} else {
None
}
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
pub fn get_plan_mall<P>(&self, provider: &P) -> Option<Plan>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
self.get_plan(provider)
}
}

impl<Pk: MiniscriptKey> fmt::Debug for Wpkh<Pk> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "wpkh({:?})", self.pk)
Expand Down
40 changes: 40 additions & 0 deletions src/descriptor/sh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ use bitcoin::{Address, Network, Script};

use super::checksum::{self, verify_checksum};
use super::{SortedMultiVec, Wpkh, Wsh};
use crate::descriptor::{DefiniteDescriptorKey, DescriptorType};
use crate::expression::{self, FromTree};
use crate::miniscript::context::ScriptContext;
use crate::plan::{AssetProvider, Plan};
use crate::policy::{semantic, Liftable};
use crate::prelude::*;
use crate::util::{varint_len, witness_to_scriptsig};
Expand Down Expand Up @@ -377,6 +379,44 @@ impl<Pk: MiniscriptKey + ToPublicKey> Sh<Pk> {
}
}

impl Sh<DefiniteDescriptorKey> {
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
pub fn get_plan<P>(&self, provider: &P) -> Option<Plan>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
match &self.inner {
ShInner::Wsh(ref wsh) => wsh.get_plan(provider).map(|mut plan| {
plan.desc_type = DescriptorType::ShWsh;
plan
}),
ShInner::Wpkh(ref wpkh) => wpkh.get_plan(provider).map(|mut plan| {
plan.desc_type = DescriptorType::ShWpkh;
plan
}),
ShInner::SortedMulti(ref _smv) => todo!(),
ShInner::Ms(ref ms) => ms.build_template(provider).into_plan(DescriptorType::Sh),
}
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
pub fn get_plan_mall<P>(&self, provider: &P) -> Option<Plan>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
match &self.inner {
ShInner::Wsh(ref wsh) => wsh.get_plan_mall(provider).map(|mut plan| {
plan.desc_type = DescriptorType::ShWsh;
plan
}),
ShInner::Ms(ref ms) => ms
.build_template_mall(provider)
.into_plan(DescriptorType::Sh),
_ => self.get_plan(provider),
}
}
}

impl<Pk: MiniscriptKey> ForEachKey<Pk> for Sh<Pk> {
fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, pred: F) -> bool
where
Expand Down
12 changes: 12 additions & 0 deletions src/descriptor/sortedmulti.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use bitcoin::blockdata::script;
use crate::miniscript::context::ScriptContext;
use crate::miniscript::decode::Terminal;
use crate::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG;
use crate::miniscript::satisfy::{Placeholder, Satisfaction};
use crate::plan::AssetProvider;
use crate::prelude::*;
use crate::{
errstr, expression, miniscript, policy, script_num_size, Error, ForEachKey, Miniscript,
Expand Down Expand Up @@ -171,6 +173,16 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
ms.satisfy(satisfier)
}

/// Attempt to produce a witness template given the assets available
pub fn build_template<P>(&self, provider: &P) -> Satisfaction<Placeholder<Pk>>
where
Pk: ToPublicKey,
P: AssetProvider<Pk>,
{
let ms = Miniscript::from_ast(self.sorted_node()).expect("Multi node typecheck");
ms.build_template(provider)
}

/// Size, in bytes of the script-pubkey. If this Miniscript is used outside
/// of segwit (e.g. in a bare or P2SH descriptor), this quantity should be
/// multiplied by 4 to compute the weight.
Expand Down
Loading

0 comments on commit dc0b280

Please sign in to comment.