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 Sep 18, 2023
1 parent fc20eb0 commit d29c298
Show file tree
Hide file tree
Showing 11 changed files with 1,596 additions and 189 deletions.
66 changes: 66 additions & 0 deletions src/descriptor/bare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ use bitcoin::script::{self, PushBytes};
use bitcoin::{Address, Network, ScriptBuf};

use super::checksum::{self, verify_checksum};
use crate::descriptor::DefiniteDescriptorKey;
use crate::expression::{self, FromTree};
use crate::miniscript::context::{ScriptContext, ScriptContextError};
use crate::miniscript::satisfy::{Placeholder, Satisfaction, Witness};
use crate::plan::AssetProvider;
use crate::policy::{semantic, Liftable};
use crate::prelude::*;
use crate::util::{varint_len, witness_to_scriptsig};
Expand Down Expand Up @@ -134,6 +137,30 @@ 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 plan_satisfaction<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
self.ms.build_template(provider)
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
pub fn plan_satisfaction_mall<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
self.ms.build_template_mall(provider)
}
}

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 @@ -311,6 +338,45 @@ 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 plan_satisfaction<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
let stack = if provider.provider_lookup_ecdsa_sig(&self.pk) {
let stack = vec![
Placeholder::EcdsaSigPk(self.pk.clone()),
Placeholder::Pubkey(self.pk.clone(), BareCtx::pk_len(&self.pk)),
];
Witness::Stack(stack)
} else {
Witness::Unavailable
};

Satisfaction {
stack,
has_sig: true,
relative_timelock: None,
absolute_timelock: None,
}
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
pub fn plan_satisfaction_mall<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
self.plan_satisfaction(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
65 changes: 62 additions & 3 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ use sync::Arc;

use self::checksum::verify_checksum;
use crate::miniscript::decode::Terminal;
use crate::miniscript::{Legacy, Miniscript, Segwitv0};
use crate::miniscript::{satisfy, Legacy, Miniscript, Segwitv0};
use crate::plan::{AssetProvider, Plan};
use crate::prelude::*;
use crate::{
expression, hash256, BareCtx, Error, ForEachKey, MiniscriptKey, Satisfier, ToPublicKey,
Expand Down Expand Up @@ -474,7 +475,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 @@ -491,7 +492,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 @@ -509,6 +510,64 @@ impl<Pk: MiniscriptKey + ToPublicKey> Descriptor<Pk> {
}
}

impl Descriptor<DefiniteDescriptorKey> {
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
///
/// If the assets aren't sufficient for generating a Plan, the descriptor is returned
pub fn plan<P>(self, provider: &P) -> Result<Plan, Self>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
let satisfaction = match self {
Descriptor::Bare(ref bare) => bare.plan_satisfaction(provider),
Descriptor::Pkh(ref pkh) => pkh.plan_satisfaction(provider),
Descriptor::Wpkh(ref wpkh) => wpkh.plan_satisfaction(provider),
Descriptor::Wsh(ref wsh) => wsh.plan_satisfaction(provider),
Descriptor::Sh(ref sh) => sh.plan_satisfaction(provider),
Descriptor::Tr(ref tr) => tr.plan_satisfaction(provider),
};

if let satisfy::Witness::Stack(stack) = satisfaction.stack {
Ok(Plan {
descriptor: self,
template: stack,
absolute_timelock: satisfaction.absolute_timelock.map(Into::into),
relative_timelock: satisfaction.relative_timelock,
})
} else {
Err(self)
}
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
///
/// If the assets aren't sufficient for generating a Plan, the descriptor is returned
pub fn plan_mall<P>(self, provider: &P) -> Result<Plan, Self>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
let satisfaction = match self {
Descriptor::Bare(ref bare) => bare.plan_satisfaction_mall(provider),
Descriptor::Pkh(ref pkh) => pkh.plan_satisfaction_mall(provider),
Descriptor::Wpkh(ref wpkh) => wpkh.plan_satisfaction_mall(provider),
Descriptor::Wsh(ref wsh) => wsh.plan_satisfaction_mall(provider),
Descriptor::Sh(ref sh) => sh.plan_satisfaction_mall(provider),
Descriptor::Tr(ref tr) => tr.plan_satisfaction_mall(provider),
};

if let satisfy::Witness::Stack(stack) = satisfaction.stack {
Ok(Plan {
descriptor: self,
template: stack,
absolute_timelock: satisfaction.absolute_timelock.map(Into::into),
relative_timelock: satisfaction.relative_timelock,
})
} else {
Err(self)
}
}
}

impl<P, Q> TranslatePk<P, Q> for Descriptor<P>
where
P: MiniscriptKey,
Expand Down
72 changes: 72 additions & 0 deletions src/descriptor/segwitv0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ use bitcoin::{Address, Network, ScriptBuf};

use super::checksum::{self, verify_checksum};
use super::SortedMultiVec;
use crate::descriptor::DefiniteDescriptorKey;
use crate::expression::{self, FromTree};
use crate::miniscript::context::{ScriptContext, ScriptContextError};
use crate::miniscript::satisfy::{Placeholder, Satisfaction, Witness};
use crate::plan::AssetProvider;
use crate::policy::{semantic, Liftable};
use crate::prelude::*;
use crate::util::varint_len;
Expand Down Expand Up @@ -191,6 +194,36 @@ 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 plan_satisfaction<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
match &self.inner {
WshInner::SortedMulti(sm) => sm.build_template(provider),
WshInner::Ms(ms) => ms.build_template(provider),
}
}

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

/// Wsh Inner
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum WshInner<Pk: MiniscriptKey> {
Expand Down Expand Up @@ -418,6 +451,45 @@ 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 plan_satisfaction<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
let stack = if provider.provider_lookup_ecdsa_sig(&self.pk) {
let stack = vec![
Placeholder::EcdsaSigPk(self.pk.clone()),
Placeholder::Pubkey(self.pk.clone(), Segwitv0::pk_len(&self.pk)),
];
Witness::Stack(stack)
} else {
Witness::Unavailable
};

Satisfaction {
stack,
has_sig: true,
relative_timelock: None,
absolute_timelock: None,
}
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
pub fn plan_satisfaction_mall<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
self.plan_satisfaction(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
36 changes: 36 additions & 0 deletions src/descriptor/sh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ use bitcoin::{script, Address, Network, ScriptBuf};

use super::checksum::{self, verify_checksum};
use super::{SortedMultiVec, Wpkh, Wsh};
use crate::descriptor::DefiniteDescriptorKey;
use crate::expression::{self, FromTree};
use crate::miniscript::context::ScriptContext;
use crate::miniscript::satisfy::{Placeholder, Satisfaction};
use crate::plan::AssetProvider;
use crate::policy::{semantic, Liftable};
use crate::prelude::*;
use crate::util::{varint_len, witness_to_scriptsig};
Expand Down Expand Up @@ -418,6 +421,39 @@ 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 plan_satisfaction<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
match &self.inner {
ShInner::Wsh(ref wsh) => wsh.plan_satisfaction(provider),
ShInner::Wpkh(ref wpkh) => wpkh.plan_satisfaction(provider),
ShInner::SortedMulti(ref smv) => smv.build_template(provider),
ShInner::Ms(ref ms) => ms.build_template(provider),
}
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
pub fn plan_satisfaction_mall<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
match &self.inner {
ShInner::Wsh(ref wsh) => wsh.plan_satisfaction_mall(provider),
ShInner::Ms(ref ms) => ms.build_template_mall(provider),
_ => self.plan_satisfaction(provider),
}
}
}

impl<Pk: MiniscriptKey> ForEachKey<Pk> for Sh<Pk> {
fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, pred: F) -> bool {
match self.inner {
Expand Down
12 changes: 12 additions & 0 deletions src/descriptor/sortedmulti.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use bitcoin::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, policy, script_num_size, Error, ForEachKey, Miniscript, MiniscriptKey,
Expand Down Expand Up @@ -154,6 +156,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 d29c298

Please sign in to comment.