diff --git a/examples/htlc.rs b/examples/htlc.rs index 607378161..b9661f441 100644 --- a/examples/htlc.rs +++ b/examples/htlc.rs @@ -7,7 +7,8 @@ use std::str::FromStr; use miniscript::bitcoin::Network; use miniscript::descriptor::Wsh; -use miniscript::policy::{Concrete, Lift}; +use miniscript::lift::Lift; +use miniscript::policy::Concrete; fn main() { // HTLC policy with 10:1 odds for happy (co-operative) case compared to uncooperative case. diff --git a/fuzz/fuzz_targets/compile_descriptor.rs b/fuzz/fuzz_targets/compile_descriptor.rs index fc2a48b8e..9a3020be4 100644 --- a/fuzz/fuzz_targets/compile_descriptor.rs +++ b/fuzz/fuzz_targets/compile_descriptor.rs @@ -1,8 +1,8 @@ use std::str::FromStr; use honggfuzz::fuzz; +use miniscript::lift::Lift; use miniscript::{policy, Miniscript, Segwitv0}; -use policy::Lift; type Script = Miniscript; type Policy = policy::Concrete; diff --git a/fuzz/fuzz_targets/roundtrip_semantic.rs b/fuzz/fuzz_targets/roundtrip_semantic.rs index cebc223b2..d824c300a 100644 --- a/fuzz/fuzz_targets/roundtrip_semantic.rs +++ b/fuzz/fuzz_targets/roundtrip_semantic.rs @@ -1,9 +1,9 @@ use std::str::FromStr; use honggfuzz::fuzz; -use miniscript::policy; +use miniscript::lift::lifted; -type Policy = policy::Semantic; +type Policy = lifted::Policy; fn do_test(data: &[u8]) { let data_str = String::from_utf8_lossy(data); diff --git a/src/descriptor/bare.rs b/src/descriptor/bare.rs index 96caf049f..016b09a69 100644 --- a/src/descriptor/bare.rs +++ b/src/descriptor/bare.rs @@ -15,10 +15,10 @@ use bitcoin::{Address, Network, ScriptBuf}; use super::checksum::{self, verify_checksum}; use crate::descriptor::DefiniteDescriptorKey; use crate::expression::{self, FromTree}; +use crate::lift::{Lift, Lifted}; use crate::miniscript::context::{ScriptContext, ScriptContextError}; use crate::miniscript::satisfy::{Placeholder, Satisfaction, Witness}; use crate::plan::AssetProvider; -use crate::policy::{semantic, Lift}; use crate::prelude::*; use crate::util::{varint_len, witness_to_scriptsig}; use crate::{ @@ -165,7 +165,7 @@ impl fmt::Display for Bare { } impl Lift for Bare { - fn lift(&self) -> Result, Error> { self.ms.lift() } + fn lift(&self) -> Result, Error> { self.ms.lift() } } impl_from_tree!( @@ -362,9 +362,7 @@ impl fmt::Display for Pkh { } impl Lift for Pkh { - fn lift(&self) -> Result, Error> { - Ok(semantic::Policy::Key(self.pk.clone())) - } + fn lift(&self) -> Result, Error> { Ok(Lifted::Key(self.pk.clone())) } } impl_from_tree!( diff --git a/src/descriptor/segwitv0.rs b/src/descriptor/segwitv0.rs index 0e3466217..3b6d4fe8f 100644 --- a/src/descriptor/segwitv0.rs +++ b/src/descriptor/segwitv0.rs @@ -13,10 +13,10 @@ use super::checksum::{self, verify_checksum}; use super::SortedMultiVec; use crate::descriptor::DefiniteDescriptorKey; use crate::expression::{self, FromTree}; +use crate::lift::{Lift, Lifted}; use crate::miniscript::context::{ScriptContext, ScriptContextError}; use crate::miniscript::satisfy::{Placeholder, Satisfaction, Witness}; use crate::plan::AssetProvider; -use crate::policy::{semantic, Lift}; use crate::prelude::*; use crate::util::varint_len; use crate::{ @@ -220,7 +220,7 @@ pub enum WshInner { } impl Lift for Wsh { - fn lift(&self) -> Result, Error> { + fn lift(&self) -> Result, Error> { match self.inner { WshInner::SortedMulti(ref smv) => smv.lift(), WshInner::Ms(ref ms) => ms.lift(), @@ -469,9 +469,7 @@ impl fmt::Display for Wpkh { } impl Lift for Wpkh { - fn lift(&self) -> Result, Error> { - Ok(semantic::Policy::Key(self.pk.clone())) - } + fn lift(&self) -> Result, Error> { Ok(Lifted::Key(self.pk.clone())) } } impl_from_tree!( diff --git a/src/descriptor/sh.rs b/src/descriptor/sh.rs index 06746808f..84b274ec5 100644 --- a/src/descriptor/sh.rs +++ b/src/descriptor/sh.rs @@ -17,10 +17,10 @@ use super::checksum::{self, verify_checksum}; use super::{SortedMultiVec, Wpkh, Wsh}; use crate::descriptor::DefiniteDescriptorKey; use crate::expression::{self, FromTree}; +use crate::lift::{Lift, Lifted}; use crate::miniscript::context::ScriptContext; use crate::miniscript::satisfy::{Placeholder, Satisfaction}; use crate::plan::AssetProvider; -use crate::policy::{semantic, Lift}; use crate::prelude::*; use crate::util::{varint_len, witness_to_scriptsig}; use crate::{ @@ -49,10 +49,10 @@ pub enum ShInner { } impl Lift for Sh { - fn lift(&self) -> Result, Error> { + fn lift(&self) -> Result, Error> { match self.inner { ShInner::Wsh(ref wsh) => wsh.lift(), - ShInner::Wpkh(ref pk) => Ok(semantic::Policy::Key(pk.as_inner().clone())), + ShInner::Wpkh(ref pk) => Ok(Lifted::Key(pk.as_inner().clone())), ShInner::SortedMulti(ref smv) => smv.lift(), ShInner::Ms(ref ms) => ms.lift(), } diff --git a/src/descriptor/sortedmulti.rs b/src/descriptor/sortedmulti.rs index 3051ea250..c1c082a07 100644 --- a/src/descriptor/sortedmulti.rs +++ b/src/descriptor/sortedmulti.rs @@ -11,6 +11,7 @@ use core::str::FromStr; use bitcoin::script; +use crate::lift::{Lift, Lifted}; use crate::miniscript::context::ScriptContext; use crate::miniscript::decode::Terminal; use crate::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG; @@ -18,8 +19,8 @@ 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, - Satisfier, ToPublicKey, TranslateErr, Translator, + errstr, expression, script_num_size, Error, ForEachKey, Miniscript, MiniscriptKey, Satisfier, + ToPublicKey, TranslateErr, Translator, }; /// Contents of a "sortedmulti" descriptor @@ -195,15 +196,10 @@ impl SortedMultiVec { pub fn max_satisfaction_size(&self) -> usize { 1 + 73 * self.k } } -impl policy::Lift for SortedMultiVec { - fn lift(&self) -> Result, Error> { - let ret = policy::semantic::Policy::Threshold( - self.k, - self.pks - .iter() - .map(|k| policy::semantic::Policy::Key(k.clone())) - .collect(), - ); +impl Lift for SortedMultiVec { + fn lift(&self) -> Result, Error> { + let ret = + Lifted::Threshold(self.k, self.pks.iter().map(|k| Lifted::Key(k.clone())).collect()); Ok(ret) } } diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 0aa011a33..7e96a6b3c 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -13,11 +13,10 @@ use sync::Arc; use super::checksum::{self, verify_checksum}; use crate::descriptor::DefiniteDescriptorKey; use crate::expression::{self, FromTree}; +use crate::lift::{Lift, Lifted}; use crate::miniscript::satisfy::{Placeholder, Satisfaction, SchnorrSigType, Witness}; use crate::miniscript::Miniscript; use crate::plan::AssetProvider; -use crate::policy::semantic::Policy; -use crate::policy::Lift; use crate::prelude::*; use crate::util::{varint_len, witness_size}; use crate::{ @@ -617,11 +616,11 @@ fn split_once(inp: &str, delim: char) -> Option<(&str, &str)> { } impl Lift for TapTree { - fn lift(&self) -> Result, Error> { - fn lift_helper(s: &TapTree) -> Result, Error> { + fn lift(&self) -> Result, Error> { + fn lift_helper(s: &TapTree) -> Result, Error> { match *s { TapTree::Tree { ref left, ref right, height: _ } => { - Ok(Policy::Threshold(1, vec![lift_helper(left)?, lift_helper(right)?])) + Ok(Lifted::Threshold(1, vec![lift_helper(left)?, lift_helper(right)?])) } TapTree::Leaf(ref leaf) => leaf.lift(), } @@ -633,12 +632,12 @@ impl Lift for TapTree { } impl Lift for Tr { - fn lift(&self) -> Result, Error> { + fn lift(&self) -> Result, Error> { match &self.tree { Some(root) => { - Ok(Policy::Threshold(1, vec![Policy::Key(self.internal_key.clone()), root.lift()?])) + Ok(Lifted::Threshold(1, vec![Lifted::Key(self.internal_key.clone()), root.lift()?])) } - None => Ok(Policy::Key(self.internal_key.clone())), + None => Ok(Lifted::Key(self.internal_key.clone())), } } } diff --git a/src/lib.rs b/src/lib.rs index 7b798915e..51ccee075 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -122,6 +122,7 @@ pub mod descriptor; pub mod expression; pub mod interpreter; pub mod iter; +pub mod lift; pub mod miniscript; pub mod plan; pub mod policy; @@ -474,7 +475,7 @@ pub enum Error { /// Errors related to policy PolicyError(policy::concrete::PolicyError), /// Errors related to lifting - LiftError(policy::LiftError), + LiftError(lift::LiftError), /// Forward script context related errors ContextError(miniscript::context::ScriptContextError), /// Recursion depth exceeded when parsing policy/miniscript from string @@ -644,8 +645,8 @@ where } #[doc(hidden)] -impl From for Error { - fn from(e: policy::LiftError) -> Error { Error::LiftError(e) } +impl From for Error { + fn from(e: lift::LiftError) -> Error { Error::LiftError(e) } } #[doc(hidden)] diff --git a/src/policy/semantic.rs b/src/lift/lifted.rs similarity index 99% rename from src/policy/semantic.rs rename to src/lift/lifted.rs index 9636c0d2a..1fa854655 100644 --- a/src/policy/semantic.rs +++ b/src/lift/lifted.rs @@ -10,8 +10,8 @@ use core::{fmt, str}; use bitcoin::{absolute, Sequence}; -use super::concrete::PolicyError; -use super::ENTAILMENT_MAX_TERMINALS; +use crate::lift::ENTAILMENT_MAX_TERMINALS; +use crate::policy::concrete::PolicyError; use crate::prelude::*; use crate::{errstr, expression, AbsLockTime, Error, ForEachKey, MiniscriptKey, Translator}; diff --git a/src/lift/mod.rs b/src/lift/mod.rs new file mode 100644 index 000000000..8cccd6b4e --- /dev/null +++ b/src/lift/mod.rs @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Provides the `Lift` trait and `Liftable` type. +//! +//! **Please Note**: lifting is a `rust-miniscript` thing not a general miniscript thing. +//! +//! Script representations, like descriptors and concrete policies, can be "lifted" into an abstract +//! representation by discarding information. + +use core::fmt; +#[cfg(feature = "std")] +use std::error; + +pub mod lifted; + +/// Re-export/re-name to facilitate usage of `Lifted` instead of `lifted::Policy`. +#[rustfmt::skip] +pub use self::lifted::Policy as Lifted; + +use crate::descriptor::Descriptor; +use crate::miniscript::{Miniscript, ScriptContext}; +use crate::policy::concrete; +use crate::sync::Arc; +use crate::{Error, MiniscriptKey, Terminal}; + +/// Policy entailment algorithm maximum number of terminals allowed. +pub const ENTAILMENT_MAX_TERMINALS: usize = 20; + +/// Trait describing script representations which can be lifted into +/// an abstract policy, by discarding information. +/// +/// After Lifting all policies are converted into `KeyHash(Pk::HasH)` to +/// maintain the following invariant(modulo resource limits): +/// `Lift(Concrete) == Concrete -> Miniscript -> Script -> Miniscript -> Semantic` +/// +/// Lifting from [`Miniscript`] or [`Descriptor`] can fail if the miniscript +/// contains a timelock combination or if it contains a branch that exceeds +/// resource limits. +/// +/// Lifting from concrete policies can fail if the policy contains a timelock +/// combination. It is possible that a concrete policy has some branches that +/// exceed resource limits for any compilation but cannot detect such policies +/// while lifting. Note that our compiler would not succeed for any such +/// policies. +pub trait Lift { + /// Converts this object into an abstract policy. + fn lift(&self) -> Result, Error>; +} + +/// Error occurring during lifting. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum LiftError { + /// Cannot lift policies that have a combination of height and timelocks. + HeightTimelockCombination, + /// Duplicate public keys. + BranchExceedResourceLimits, + /// Cannot lift raw descriptors. + RawDescriptorLift, +} + +impl fmt::Display for LiftError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + LiftError::HeightTimelockCombination => { + f.write_str("Cannot lift policies that have a heightlock and timelock combination") + } + LiftError::BranchExceedResourceLimits => f.write_str( + "Cannot lift policies containing one branch that exceeds resource limits", + ), + LiftError::RawDescriptorLift => f.write_str("Cannot lift raw descriptors"), + } + } +} + +#[cfg(feature = "std")] +impl error::Error for LiftError { + fn cause(&self) -> Option<&dyn error::Error> { + use self::LiftError::*; + + match self { + HeightTimelockCombination | BranchExceedResourceLimits | RawDescriptorLift => None, + } + } +} + +impl Miniscript { + /// Lifting corresponds to conversion of a miniscript into a [`Semantic`] + /// policy for human readable or machine analysis. However, naively lifting + /// miniscripts can result in incorrect interpretations that don't + /// correspond to the underlying semantics when we try to spend them on + /// bitcoin network. This can occur if the miniscript contains: + /// 1. A combination of timelocks + /// 2. A spend that exceeds resource limits + pub fn lift_check(&self) -> Result<(), LiftError> { + if !self.within_resource_limits() { + Err(LiftError::BranchExceedResourceLimits) + } else if self.has_mixed_timelocks() { + Err(LiftError::HeightTimelockCombination) + } else { + Ok(()) + } + } +} + +impl Lift for Miniscript { + fn lift(&self) -> Result, Error> { + // check whether the root miniscript can have a spending path that is + // a combination of heightlock and timelock + self.lift_check()?; + self.as_inner().lift() + } +} + +impl Lift for Terminal { + fn lift(&self) -> Result, Error> { + let ret = match *self { + Terminal::PkK(ref pk) | Terminal::PkH(ref pk) => Lifted::Key(pk.clone()), + Terminal::RawPkH(ref _pkh) => { + return Err(Error::LiftError(LiftError::RawDescriptorLift)) + } + Terminal::After(t) => Lifted::After(t), + Terminal::Older(t) => Lifted::Older(t), + Terminal::Sha256(ref h) => Lifted::Sha256(h.clone()), + Terminal::Hash256(ref h) => Lifted::Hash256(h.clone()), + Terminal::Ripemd160(ref h) => Lifted::Ripemd160(h.clone()), + Terminal::Hash160(ref h) => Lifted::Hash160(h.clone()), + Terminal::False => Lifted::Unsatisfiable, + Terminal::True => Lifted::Trivial, + Terminal::Alt(ref sub) + | Terminal::Swap(ref sub) + | Terminal::Check(ref sub) + | Terminal::DupIf(ref sub) + | Terminal::Verify(ref sub) + | Terminal::NonZero(ref sub) + | Terminal::ZeroNotEqual(ref sub) => sub.node.lift()?, + Terminal::AndV(ref left, ref right) | Terminal::AndB(ref left, ref right) => { + Lifted::Threshold(2, vec![left.node.lift()?, right.node.lift()?]) + } + Terminal::AndOr(ref a, ref b, ref c) => Lifted::Threshold( + 1, + vec![ + Lifted::Threshold(2, vec![a.node.lift()?, b.node.lift()?]), + c.node.lift()?, + ], + ), + Terminal::OrB(ref left, ref right) + | Terminal::OrD(ref left, ref right) + | Terminal::OrC(ref left, ref right) + | Terminal::OrI(ref left, ref right) => { + Lifted::Threshold(1, vec![left.node.lift()?, right.node.lift()?]) + } + Terminal::Thresh(k, ref subs) => { + let semantic_subs: Result<_, Error> = subs.iter().map(|s| s.node.lift()).collect(); + Lifted::Threshold(k, semantic_subs?) + } + Terminal::Multi(k, ref keys) | Terminal::MultiA(k, ref keys) => { + Lifted::Threshold(k, keys.iter().map(|k| Lifted::Key(k.clone())).collect()) + } + } + .normalized(); + Ok(ret) + } +} + +impl Lift for Descriptor { + fn lift(&self) -> Result, Error> { + match *self { + Descriptor::Bare(ref bare) => bare.lift(), + Descriptor::Pkh(ref pkh) => pkh.lift(), + Descriptor::Wpkh(ref wpkh) => wpkh.lift(), + Descriptor::Wsh(ref wsh) => wsh.lift(), + Descriptor::Sh(ref sh) => sh.lift(), + Descriptor::Tr(ref tr) => tr.lift(), + } + } +} + +impl Lift for Lifted { + fn lift(&self) -> Result, Error> { Ok(self.clone()) } +} + +impl Lift for concrete::Policy { + fn lift(&self) -> Result, Error> { + use concrete::Policy as Concrete; + + // do not lift if there is a possible satisfaction + // involving combination of timelocks and heightlocks + self.check_timelocks()?; + let ret = match *self { + Concrete::Unsatisfiable => Lifted::Unsatisfiable, + Concrete::Trivial => Lifted::Trivial, + Concrete::Key(ref pk) => Lifted::Key(pk.clone()), + Concrete::After(t) => Lifted::After(t), + Concrete::Older(t) => Lifted::Older(t), + Concrete::Sha256(ref h) => Lifted::Sha256(h.clone()), + Concrete::Hash256(ref h) => Lifted::Hash256(h.clone()), + Concrete::Ripemd160(ref h) => Lifted::Ripemd160(h.clone()), + Concrete::Hash160(ref h) => Lifted::Hash160(h.clone()), + Concrete::And(ref subs) => { + let semantic_subs: Result<_, Error> = subs.iter().map(Lift::lift).collect(); + Lifted::Threshold(2, semantic_subs?) + } + Concrete::Or(ref subs) => { + let semantic_subs: Result<_, Error> = + subs.iter().map(|(_p, sub)| sub.lift()).collect(); + Lifted::Threshold(1, semantic_subs?) + } + Concrete::Threshold(k, ref subs) => { + let semantic_subs: Result<_, Error> = subs.iter().map(Lift::lift).collect(); + Lifted::Threshold(k, semantic_subs?) + } + } + .normalized(); + Ok(ret) + } +} +impl Lift for Arc> { + fn lift(&self) -> Result, Error> { self.as_ref().lift() } +} diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index 322c02e79..586aac48a 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -607,9 +607,9 @@ mod tests { use sync::Arc; use super::{Miniscript, ScriptContext, Segwitv0, Tap}; + use crate::lift::Lift; use crate::miniscript::types::{self, ExtData, Property, Type}; use crate::miniscript::Terminal; - use crate::policy::Lift; use crate::prelude::*; use crate::test_utils::{StrKeyTranslator, StrXOnlyKeyTranslator}; use crate::{hex_script, ExtParams, Satisfier, ToPublicKey, TranslatePk}; diff --git a/src/policy/compiler.rs b/src/policy/compiler.rs index e6b865cc9..eee94c1ce 100644 --- a/src/policy/compiler.rs +++ b/src/policy/compiler.rs @@ -1155,8 +1155,8 @@ mod tests { use bitcoin::{self, hashes, secp256k1, Sequence}; use super::*; + use crate::lift::Lift; use crate::miniscript::{Legacy, Segwitv0, Tap}; - use crate::policy::Lift; use crate::{script_num_size, ToPublicKey}; type SPolicy = Concrete; diff --git a/src/policy/concrete.rs b/src/policy/concrete.rs index d0b625b73..a5396d034 100644 --- a/src/policy/concrete.rs +++ b/src/policy/concrete.rs @@ -14,16 +14,16 @@ use { crate::miniscript::ScriptContext, crate::policy::compiler::CompilerError, crate::policy::compiler::OrdF64, - crate::policy::{compiler, Concrete, Lift, Semantic}, + crate::policy::{compiler, Concrete}, crate::Descriptor, crate::Miniscript, crate::Tap, core::cmp::Reverse, }; -use super::ENTAILMENT_MAX_TERMINALS; use crate::expression::{self, FromTree}; use crate::iter::TreeLike; +use crate::lift::{Lift, Lifted, ENTAILMENT_MAX_TERMINALS}; use crate::miniscript::types::extra_props::TimelockInfo; use crate::prelude::*; use crate::sync::Arc; @@ -238,8 +238,8 @@ impl Policy { for key in concrete_keys.into_iter() { if semantic_policy .clone() - .satisfy_constraint(&Semantic::Key(key.clone()), true) - == Semantic::Trivial + .satisfy_constraint(&Lifted::Key(key.clone()), true) + == Lifted::Trivial { match key_prob_map.get(&Concrete::Key(key.clone())) { Some(val) => { diff --git a/src/policy/mod.rs b/src/policy/mod.rs index f561b57d6..2da376b9e 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -8,215 +8,14 @@ //! //! The format represents EC public keys abstractly to allow wallets to replace //! these with BIP32 paths, pay-to-contract instructions, etc. -//! -use core::fmt; -#[cfg(feature = "std")] -use std::error; #[cfg(feature = "compiler")] pub mod compiler; pub mod concrete; -pub mod semantic; +/// Re-export/re-name to facilitate usage of `Concrete` instead of `concrete::Policy`. +#[rustfmt::skip] pub use self::concrete::Policy as Concrete; -pub use self::semantic::Policy as Semantic; -use crate::descriptor::Descriptor; -use crate::miniscript::{Miniscript, ScriptContext}; -use crate::sync::Arc; -use crate::{Error, MiniscriptKey, Terminal}; - -/// Policy entailment algorithm maximum number of terminals allowed. -const ENTAILMENT_MAX_TERMINALS: usize = 20; - -/// Trait describing script representations which can be lifted into -/// an abstract policy, by discarding information. -/// -/// After Lifting all policies are converted into `KeyHash(Pk::HasH)` to -/// maintain the following invariant(modulo resource limits): -/// `Lift(Concrete) == Concrete -> Miniscript -> Script -> Miniscript -> Semantic` -/// -/// Lifting from [`Miniscript`] or [`Descriptor`] can fail if the miniscript -/// contains a timelock combination or if it contains a branch that exceeds -/// resource limits. -/// -/// Lifting from concrete policies can fail if the policy contains a timelock -/// combination. It is possible that a concrete policy has some branches that -/// exceed resource limits for any compilation but cannot detect such policies -/// while lifting. Note that our compiler would not succeed for any such -/// policies. -pub trait Lift { - /// Converts this object into an abstract policy. - fn lift(&self) -> Result, Error>; -} - -/// Error occurring during lifting. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum LiftError { - /// Cannot lift policies that have a combination of height and timelocks. - HeightTimelockCombination, - /// Duplicate public keys. - BranchExceedResourceLimits, - /// Cannot lift raw descriptors. - RawDescriptorLift, -} - -impl fmt::Display for LiftError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - LiftError::HeightTimelockCombination => { - f.write_str("Cannot lift policies that have a heightlock and timelock combination") - } - LiftError::BranchExceedResourceLimits => f.write_str( - "Cannot lift policies containing one branch that exceeds resource limits", - ), - LiftError::RawDescriptorLift => f.write_str("Cannot lift raw descriptors"), - } - } -} - -#[cfg(feature = "std")] -impl error::Error for LiftError { - fn cause(&self) -> Option<&dyn error::Error> { - use self::LiftError::*; - - match self { - HeightTimelockCombination | BranchExceedResourceLimits | RawDescriptorLift => None, - } - } -} - -impl Miniscript { - /// Lifting corresponds to conversion of a miniscript into a [`Semantic`] - /// policy for human readable or machine analysis. However, naively lifting - /// miniscripts can result in incorrect interpretations that don't - /// correspond to the underlying semantics when we try to spend them on - /// bitcoin network. This can occur if the miniscript contains: - /// 1. A combination of timelocks - /// 2. A spend that exceeds resource limits - pub fn lift_check(&self) -> Result<(), LiftError> { - if !self.within_resource_limits() { - Err(LiftError::BranchExceedResourceLimits) - } else if self.has_mixed_timelocks() { - Err(LiftError::HeightTimelockCombination) - } else { - Ok(()) - } - } -} - -impl Lift for Miniscript { - fn lift(&self) -> Result, Error> { - // check whether the root miniscript can have a spending path that is - // a combination of heightlock and timelock - self.lift_check()?; - self.as_inner().lift() - } -} - -impl Lift for Terminal { - fn lift(&self) -> Result, Error> { - let ret = match *self { - Terminal::PkK(ref pk) | Terminal::PkH(ref pk) => Semantic::Key(pk.clone()), - Terminal::RawPkH(ref _pkh) => { - return Err(Error::LiftError(LiftError::RawDescriptorLift)) - } - Terminal::After(t) => Semantic::After(t), - Terminal::Older(t) => Semantic::Older(t), - Terminal::Sha256(ref h) => Semantic::Sha256(h.clone()), - Terminal::Hash256(ref h) => Semantic::Hash256(h.clone()), - Terminal::Ripemd160(ref h) => Semantic::Ripemd160(h.clone()), - Terminal::Hash160(ref h) => Semantic::Hash160(h.clone()), - Terminal::False => Semantic::Unsatisfiable, - Terminal::True => Semantic::Trivial, - Terminal::Alt(ref sub) - | Terminal::Swap(ref sub) - | Terminal::Check(ref sub) - | Terminal::DupIf(ref sub) - | Terminal::Verify(ref sub) - | Terminal::NonZero(ref sub) - | Terminal::ZeroNotEqual(ref sub) => sub.node.lift()?, - Terminal::AndV(ref left, ref right) | Terminal::AndB(ref left, ref right) => { - Semantic::Threshold(2, vec![left.node.lift()?, right.node.lift()?]) - } - Terminal::AndOr(ref a, ref b, ref c) => Semantic::Threshold( - 1, - vec![ - Semantic::Threshold(2, vec![a.node.lift()?, b.node.lift()?]), - c.node.lift()?, - ], - ), - Terminal::OrB(ref left, ref right) - | Terminal::OrD(ref left, ref right) - | Terminal::OrC(ref left, ref right) - | Terminal::OrI(ref left, ref right) => { - Semantic::Threshold(1, vec![left.node.lift()?, right.node.lift()?]) - } - Terminal::Thresh(k, ref subs) => { - let semantic_subs: Result<_, Error> = subs.iter().map(|s| s.node.lift()).collect(); - Semantic::Threshold(k, semantic_subs?) - } - Terminal::Multi(k, ref keys) | Terminal::MultiA(k, ref keys) => { - Semantic::Threshold(k, keys.iter().map(|k| Semantic::Key(k.clone())).collect()) - } - } - .normalized(); - Ok(ret) - } -} - -impl Lift for Descriptor { - fn lift(&self) -> Result, Error> { - match *self { - Descriptor::Bare(ref bare) => bare.lift(), - Descriptor::Pkh(ref pkh) => pkh.lift(), - Descriptor::Wpkh(ref wpkh) => wpkh.lift(), - Descriptor::Wsh(ref wsh) => wsh.lift(), - Descriptor::Sh(ref sh) => sh.lift(), - Descriptor::Tr(ref tr) => tr.lift(), - } - } -} - -impl Lift for Semantic { - fn lift(&self) -> Result, Error> { Ok(self.clone()) } -} - -impl Lift for Concrete { - fn lift(&self) -> Result, Error> { - // do not lift if there is a possible satisfaction - // involving combination of timelocks and heightlocks - self.check_timelocks()?; - let ret = match *self { - Concrete::Unsatisfiable => Semantic::Unsatisfiable, - Concrete::Trivial => Semantic::Trivial, - Concrete::Key(ref pk) => Semantic::Key(pk.clone()), - Concrete::After(t) => Semantic::After(t), - Concrete::Older(t) => Semantic::Older(t), - Concrete::Sha256(ref h) => Semantic::Sha256(h.clone()), - Concrete::Hash256(ref h) => Semantic::Hash256(h.clone()), - Concrete::Ripemd160(ref h) => Semantic::Ripemd160(h.clone()), - Concrete::Hash160(ref h) => Semantic::Hash160(h.clone()), - Concrete::And(ref subs) => { - let semantic_subs: Result<_, Error> = subs.iter().map(Lift::lift).collect(); - Semantic::Threshold(2, semantic_subs?) - } - Concrete::Or(ref subs) => { - let semantic_subs: Result<_, Error> = - subs.iter().map(|(_p, sub)| sub.lift()).collect(); - Semantic::Threshold(1, semantic_subs?) - } - Concrete::Threshold(k, ref subs) => { - let semantic_subs: Result<_, Error> = subs.iter().map(Lift::lift).collect(); - Semantic::Threshold(k, semantic_subs?) - } - } - .normalized(); - Ok(ret) - } -} -impl Lift for Arc> { - fn lift(&self) -> Result, Error> { self.as_ref().lift() } -} #[cfg(test)] mod tests { @@ -228,15 +27,16 @@ mod tests { use super::super::miniscript::context::Segwitv0; use super::super::miniscript::Miniscript; - use super::{Concrete, Lift, Semantic}; + use super::*; #[cfg(feature = "compiler")] use crate::descriptor::Tr; + use crate::lift::{Lift, Lifted}; use crate::prelude::*; #[cfg(feature = "compiler")] use crate::{descriptor::TapTree, Descriptor, Tap}; type ConcretePol = Concrete; - type SemanticPol = Semantic; + type SemanticPol = Lifted; fn concrete_policy_rtt(s: &str) { let conc = ConcretePol::from_str(s).unwrap(); @@ -345,17 +145,14 @@ mod tests { .parse() .unwrap(); assert_eq!( - Semantic::Threshold( + Lifted::Threshold( 1, vec![ - Semantic::Threshold( + Lifted::Threshold( 2, - vec![ - Semantic::Key(key_a), - Semantic::Older(Sequence::from_height(42)) - ] + vec![Lifted::Key(key_a), Lifted::Older(Sequence::from_height(42))] ), - Semantic::Key(key_b) + Lifted::Key(key_b) ] ), ms_str.lift().unwrap()