diff --git a/.travis.yml b/.travis.yml index 8b9d22c91..c54241964 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ matrix: env: DO_FUZZ=true DO_LINT=true - rust: beta - rust: nightly - env: DO_BENCH=true + env: DO_BENCH=true DO_MIRI=true - rust: 1.22.0 script: diff --git a/contrib/test.sh b/contrib/test.sh index 112f149c7..de8c80a57 100755 --- a/contrib/test.sh +++ b/contrib/test.sh @@ -43,6 +43,23 @@ then ) fi +# Miri Checks if told to +# Only supported in nightly +if [ "$DO_MIRI" = true ] +then + ( + MIRI_NIGHTLY=nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri) + echo "Installing latest nightly with Miri: $MIRI_NIGHTLY" + rustup set profile minimal + rustup default "$MIRI_NIGHTLY" + rustup component add miri + cargo miri test -- -- miri_ + + # Change back to latest nightly possibly without Miri + rustup default nightly + ) +fi + # Bench if told to if [ "$DO_BENCH" = true ] then diff --git a/examples/verify_tx.rs b/examples/verify_tx.rs index a68661f01..1d0f6a2b3 100644 --- a/examples/verify_tx.rs +++ b/examples/verify_tx.rs @@ -103,7 +103,6 @@ fn main() { 0, 0, ); - println!("\nExample one"); for elem in iter { match elem.expect("no evaluation error") { @@ -129,7 +128,6 @@ fn main() { 0, 0, ); - println!("\nExample two"); for elem in iter { match elem.expect("no evaluation error") { @@ -154,7 +152,6 @@ fn main() { 0, 0, ); - println!("\nExample three"); for elem in iter { let error = elem.expect_err("evaluation error"); diff --git a/fuzz/fuzz_targets/compile_descriptor.rs b/fuzz/fuzz_targets/compile_descriptor.rs index a65e47a22..8dbef0bc8 100644 --- a/fuzz/fuzz_targets/compile_descriptor.rs +++ b/fuzz/fuzz_targets/compile_descriptor.rs @@ -1,18 +1,19 @@ extern crate miniscript; +use miniscript::Segwitv0; use miniscript::{policy, DummyKey, Miniscript}; use policy::Liftable; use std::str::FromStr; -type DummyScript = Miniscript; +type DummyScript = Miniscript; type DummyPolicy = policy::Concrete; fn do_test(data: &[u8]) { let data_str = String::from_utf8_lossy(data); if let Ok(pol) = DummyPolicy::from_str(&data_str) { // Compile - if let Ok(desc) = pol.compile() { + if let Ok(desc) = pol.compile::() { // Lift assert_eq!(desc.clone().lift(), pol.clone().lift()); // Try to roundtrip the output of the compiler @@ -37,7 +38,8 @@ fn main() { } #[cfg(feature = "honggfuzz")] -#[macro_use] extern crate honggfuzz; +#[macro_use] +extern crate honggfuzz; #[cfg(feature = "honggfuzz")] fn main() { loop { diff --git a/fuzz/fuzz_targets/roundtrip_concrete.rs b/fuzz/fuzz_targets/roundtrip_concrete.rs index a966b43a0..76bcfde3c 100644 --- a/fuzz/fuzz_targets/roundtrip_concrete.rs +++ b/fuzz/fuzz_targets/roundtrip_concrete.rs @@ -1,9 +1,8 @@ - extern crate miniscript; extern crate regex; -use std::str::FromStr; use miniscript::{policy, DummyKey}; use regex::Regex; +use std::str::FromStr; type DummyPolicy = policy::Concrete; @@ -29,7 +28,8 @@ fn main() { } #[cfg(feature = "honggfuzz")] -#[macro_use] extern crate honggfuzz; +#[macro_use] +extern crate honggfuzz; #[cfg(feature = "honggfuzz")] fn main() { loop { diff --git a/fuzz/fuzz_targets/roundtrip_descriptor.rs b/fuzz/fuzz_targets/roundtrip_descriptor.rs index 6294cc3cb..2a9f85d6e 100644 --- a/fuzz/fuzz_targets/roundtrip_descriptor.rs +++ b/fuzz/fuzz_targets/roundtrip_descriptor.rs @@ -1,4 +1,3 @@ - extern crate miniscript; extern crate regex; @@ -10,13 +9,15 @@ fn do_test(data: &[u8]) { let s = String::from_utf8_lossy(data); if let Ok(desc) = Descriptor::::from_str(&s) { let output = desc.to_string(); - + let multi_wrap_pk_re = Regex::new("([a-z]+)c:pk_k\\(").unwrap(); let multi_wrap_pkh_re = Regex::new("([a-z]+)c:pk_h\\(").unwrap(); let normalize_aliases = multi_wrap_pk_re.replace_all(&s, "$1:pk("); let normalize_aliases = multi_wrap_pkh_re.replace_all(&normalize_aliases, "$1:pkh("); - let normalize_aliases = normalize_aliases.replace("c:pk_k(", "pk(").replace("c:pk_h(", "pkh("); + let normalize_aliases = normalize_aliases + .replace("c:pk_k(", "pk(") + .replace("c:pk_h(", "pkh("); assert_eq!(normalize_aliases.to_lowercase(), output.to_lowercase()); } @@ -32,7 +33,8 @@ fn main() { } #[cfg(feature = "honggfuzz")] -#[macro_use] extern crate honggfuzz; +#[macro_use] +extern crate honggfuzz; #[cfg(feature = "honggfuzz")] fn main() { loop { diff --git a/fuzz/fuzz_targets/roundtrip_miniscript_script.rs b/fuzz/fuzz_targets/roundtrip_miniscript_script.rs index 8f2be3151..44f24623f 100644 --- a/fuzz/fuzz_targets/roundtrip_miniscript_script.rs +++ b/fuzz/fuzz_targets/roundtrip_miniscript_script.rs @@ -1,14 +1,14 @@ - extern crate miniscript; -use miniscript::Miniscript; use miniscript::bitcoin::blockdata::script; +use miniscript::Miniscript; +use miniscript::Segwitv0; fn do_test(data: &[u8]) { // Try round-tripping as a script let script = script::Script::from(data.to_owned()); - if let Ok(pt) = Miniscript::parse(&script) { + if let Ok(pt) = Miniscript::<_, Segwitv0>::parse(&script) { let output = pt.encode(); assert_eq!(pt.script_size(), output.len()); assert_eq!(output, script); @@ -25,7 +25,8 @@ fn main() { } #[cfg(feature = "honggfuzz")] -#[macro_use] extern crate honggfuzz; +#[macro_use] +extern crate honggfuzz; #[cfg(feature = "honggfuzz")] fn main() { loop { diff --git a/fuzz/fuzz_targets/roundtrip_miniscript_str.rs b/fuzz/fuzz_targets/roundtrip_miniscript_str.rs index 23083d947..467cde833 100644 --- a/fuzz/fuzz_targets/roundtrip_miniscript_str.rs +++ b/fuzz/fuzz_targets/roundtrip_miniscript_str.rs @@ -1,27 +1,28 @@ - extern crate miniscript; extern crate regex; -use std::str::FromStr; use regex::Regex; +use std::str::FromStr; -use miniscript::{DummyKey}; +use miniscript::DummyKey; use miniscript::Miniscript; +use miniscript::Segwitv0; fn do_test(data: &[u8]) { let s = String::from_utf8_lossy(data); - if let Ok(desc) = Miniscript::::from_str(&s) { + if let Ok(desc) = Miniscript::::from_str(&s) { let output = desc.to_string(); - + let multi_wrap_pk_re = Regex::new("([a-z]+)c:pk_k\\(").unwrap(); let multi_wrap_pkh_re = Regex::new("([a-z]+)c:pk_h\\(").unwrap(); let normalize_aliases = multi_wrap_pk_re.replace_all(&s, "$1:pk("); let normalize_aliases = multi_wrap_pkh_re.replace_all(&normalize_aliases, "$1:pkh("); - let normalize_aliases = normalize_aliases.replace("c:pk_k(", "pk(").replace("c:pk_h(", "pkh("); + let normalize_aliases = normalize_aliases + .replace("c:pk_k(", "pk(") + .replace("c:pk_h(", "pkh("); assert_eq!(normalize_aliases.to_lowercase(), output.to_lowercase()); - } } @@ -35,7 +36,8 @@ fn main() { } #[cfg(feature = "honggfuzz")] -#[macro_use] extern crate honggfuzz; +#[macro_use] +extern crate honggfuzz; #[cfg(feature = "honggfuzz")] fn main() { loop { diff --git a/fuzz/fuzz_targets/roundtrip_semantic.rs b/fuzz/fuzz_targets/roundtrip_semantic.rs index eb97e55a7..3f75c197c 100644 --- a/fuzz/fuzz_targets/roundtrip_semantic.rs +++ b/fuzz/fuzz_targets/roundtrip_semantic.rs @@ -1,8 +1,7 @@ - extern crate miniscript; -use std::str::FromStr; use miniscript::{policy, DummyKey}; +use std::str::FromStr; type DummyPolicy = policy::Semantic; @@ -24,7 +23,8 @@ fn main() { } #[cfg(feature = "honggfuzz")] -#[macro_use] extern crate honggfuzz; +#[macro_use] +extern crate honggfuzz; #[cfg(feature = "honggfuzz")] fn main() { loop { @@ -32,4 +32,4 @@ fn main() { do_test(data); }); } -} \ No newline at end of file +} diff --git a/src/descriptor/create_descriptor.rs b/src/descriptor/create_descriptor.rs index b28572bcf..435f7f46c 100644 --- a/src/descriptor/create_descriptor.rs +++ b/src/descriptor/create_descriptor.rs @@ -9,7 +9,7 @@ use bitcoin::blockdata::script::Instruction; use descriptor::satisfied_constraints::Error as IntError; use descriptor::satisfied_constraints::{Stack, StackElement}; use descriptor::Descriptor; -use miniscript::Miniscript; +use miniscript::{Legacy, Miniscript, Segwitv0}; use Error; use ToPublicKey; @@ -112,7 +112,7 @@ fn verify_wsh<'txin>( script_pubkey: &bitcoin::Script, script_sig: &bitcoin::Script, witness: &'txin [Vec], -) -> Result<(Miniscript, Stack<'txin>), Error> { +) -> Result<(Miniscript, Stack<'txin>), Error> { if !script_sig.is_empty() { return Err(Error::NonEmptyScriptSig); } @@ -121,7 +121,7 @@ fn verify_wsh<'txin>( if witness_script.to_v0_p2wsh() != *script_pubkey { return Err(Error::IncorrectScriptHash); } - let ms = Miniscript::parse(&witness_script)?; + let ms = Miniscript::::parse(&witness_script)?; //only iter till len -1 to not include the witness script let stack: Vec = witness .iter() @@ -218,7 +218,7 @@ pub fn from_txin_with_witness_stack<'txin>( if !witness.is_empty() { return Err(Error::NonEmptyWitness); } - let ms = Miniscript::parse(&redeem_script)?; + let ms = Miniscript::::parse(&redeem_script)?; Ok((Descriptor::Sh(ms), stack)) } } else { @@ -230,7 +230,7 @@ pub fn from_txin_with_witness_stack<'txin>( if !witness.is_empty() { return Err(Error::NonEmptyWitness); } - let ms = Miniscript::parse(script_pubkey)?; + let ms = Miniscript::::parse(script_pubkey)?; Ok((Descriptor::Bare(ms), Stack(stack?))) } } diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 254132cc6..55dc0dcc4 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -32,7 +32,8 @@ use std::str::{self, FromStr}; use expression; use miniscript; -use miniscript::Miniscript; +use miniscript::context::ScriptContextError; +use miniscript::{Legacy, Miniscript, Segwitv0}; use Error; use MiniscriptKey; use Satisfier; @@ -50,8 +51,8 @@ pub use self::satisfied_constraints::Stack; /// Script descriptor #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum Descriptor { - /// A raw scriptpubkey (including pay-to-pubkey) - Bare(Miniscript), + /// A raw scriptpubkey (including pay-to-pubkey) under Legacy context + Bare(Miniscript), /// Pay-to-Pubkey Pk(Pk), /// Pay-to-PubKey-Hash @@ -60,16 +61,19 @@ pub enum Descriptor { Wpkh(Pk), /// Pay-to-Witness-PubKey-Hash inside P2SH ShWpkh(Pk), - /// Pay-to-ScriptHash - Sh(Miniscript), - /// Pay-to-Witness-ScriptHash - Wsh(Miniscript), - /// P2SH-P2WSH - ShWsh(Miniscript), + /// Pay-to-ScriptHash with Legacy context + Sh(Miniscript), + /// Pay-to-Witness-ScriptHash with Segwitv0 context + Wsh(Miniscript), + /// P2SH-P2WSH with Segwitv0 context + ShWsh(Miniscript), } impl Descriptor { /// Convert a descriptor using abstract keys to one using specific keys + /// This will panic if translatefpk returns an uncompressed key when + /// converting to a Segwit descriptor. To prevent this panic, ensure + /// translatefpk returns an error in this case instead. pub fn translate_pk( &self, mut translatefpk: Fpk, @@ -86,8 +90,18 @@ impl Descriptor { )), Descriptor::Pk(ref pk) => translatefpk(pk).map(Descriptor::Pk), Descriptor::Pkh(ref pk) => translatefpk(pk).map(Descriptor::Pkh), - Descriptor::Wpkh(ref pk) => translatefpk(pk).map(Descriptor::Wpkh), - Descriptor::ShWpkh(ref pk) => translatefpk(pk).map(Descriptor::ShWpkh), + Descriptor::Wpkh(ref pk) => { + if pk.is_uncompressed() { + panic!("Uncompressed pubkeys are not allowed in segwit v0 scripts"); + } + translatefpk(pk).map(Descriptor::Wpkh) + } + Descriptor::ShWpkh(ref pk) => { + if pk.is_uncompressed() { + panic!("Uncompressed pubkeys are not allowed in segwit v0 scripts"); + } + translatefpk(pk).map(Descriptor::ShWpkh) + } Descriptor::Sh(ref ms) => Ok(Descriptor::Sh( ms.translate_pk(&mut translatefpk, &mut translatefpkh)?, )), @@ -201,7 +215,8 @@ impl Descriptor { let addr = bitcoin::Address::p2wpkh(&pk.to_public_key(), bitcoin::Network::Bitcoin); addr.script_pubkey() } - Descriptor::Sh(ref d) | Descriptor::Wsh(ref d) | Descriptor::ShWsh(ref d) => d.encode(), + Descriptor::Sh(ref d) => d.encode(), + Descriptor::Wsh(ref d) | Descriptor::ShWsh(ref d) => d.encode(), } } @@ -396,7 +411,12 @@ where expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Descriptor::Pkh)) } ("wpkh", 1) => { - expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Descriptor::Wpkh)) + let wpkh = expression::terminal(&top.args[0], |pk| Pk::from_str(pk))?; + if wpkh.is_uncompressed() { + Err(Error::ContextError(ScriptContextError::CompressedOnly)) + } else { + Ok(Descriptor::Wpkh(wpkh)) + } } ("sh", 1) => { let newtop = &top.args[0]; @@ -409,9 +429,14 @@ where Ok(Descriptor::ShWsh(sub)) } } - ("wpkh", 1) => expression::terminal(&newtop.args[0], |pk| { - Pk::from_str(pk).map(Descriptor::ShWpkh) - }), + ("wpkh", 1) => { + let wpkh = expression::terminal(&newtop.args[0], |pk| Pk::from_str(pk))?; + if wpkh.is_uncompressed() { + Err(Error::ContextError(ScriptContextError::CompressedOnly)) + } else { + Ok(Descriptor::ShWpkh(wpkh)) + } + } _ => { let sub = Miniscript::from_tree(&top.args[0])?; if sub.ty.corr.base != miniscript::types::Base::B { @@ -586,6 +611,18 @@ mod tests { StdDescriptor::from_str("nl:0").unwrap_err(); //issue 63 StdDescriptor::from_str(TEST_PK).unwrap(); + + let uncompressed_pk = + "0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf"; + + // Context tests + StdDescriptor::from_str(&format!("pk({})", uncompressed_pk)).unwrap(); + StdDescriptor::from_str(&format!("pkh({})", uncompressed_pk)).unwrap(); + StdDescriptor::from_str(&format!("sh(pk({}))", uncompressed_pk)).unwrap(); + StdDescriptor::from_str(&format!("wpkh({})", uncompressed_pk)).unwrap_err(); + StdDescriptor::from_str(&format!("sh(wpkh({}))", uncompressed_pk)).unwrap_err(); + StdDescriptor::from_str(&format!("wsh(pk{})", uncompressed_pk)).unwrap_err(); + StdDescriptor::from_str(&format!("sh(wsh(pk{}))", uncompressed_pk)).unwrap_err(); } #[test] @@ -875,6 +912,8 @@ mod tests { ); assert_eq!(sh.unsigned_script_sig(), bitcoin::Script::new()); + let ms = ms_str!("c:pk_k({})", pk); + let wsh = Descriptor::Wsh(ms.clone()); wsh.satisfy(&mut txin, &satisfier).expect("satisfaction"); assert_eq!( diff --git a/src/descriptor/satisfied_constraints.rs b/src/descriptor/satisfied_constraints.rs index 8f6dab5a7..60446d600 100644 --- a/src/descriptor/satisfied_constraints.rs +++ b/src/descriptor/satisfied_constraints.rs @@ -15,6 +15,8 @@ use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; use bitcoin::{self, secp256k1}; use fmt; +use miniscript::context::Any; +use miniscript::ScriptContext; use Descriptor; use Terminal; use {error, Miniscript}; @@ -202,7 +204,7 @@ pub enum SatisfiedConstraint<'desc, 'stack> { ///depending on evaluation of the children. struct NodeEvaluationState<'desc> { ///The node which is being evaluated - node: &'desc Miniscript, + node: &'desc Miniscript, ///number of children evaluated n_evaluated: usize, ///number of children satisfied @@ -235,6 +237,7 @@ pub struct Stack<'stack>(pub Vec>); ///Iterator for SatisfiedConstraints impl<'desc, 'stack, F> Iterator for SatisfiedConstraints<'desc, 'stack, F> where + Any: ScriptContext, F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool, { type Item = Result, Error>; @@ -257,21 +260,7 @@ impl<'desc, 'stack, F> SatisfiedConstraints<'desc, 'stack, F> where F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool, { - /// Helper function to push a NodeEvaluationState on state stack - fn push_evaluation_state( - &mut self, - node: &'desc Miniscript, - n_evaluated: usize, - n_satisfied: usize, - ) -> () { - self.state.push(NodeEvaluationState { - node, - n_evaluated, - n_satisfied, - }) - } - - /// Creates a new iterator over all constraints satisfied for a given + // Creates a new iterator over all constraints satisfied for a given /// descriptor by a given witness stack. Because this iterator is lazy, /// it may return satisfied constraints even if these turn out to be /// irrelevant to the final (dis)satisfaction of the descriptor. @@ -283,10 +272,7 @@ where height: u32, ) -> SatisfiedConstraints<'desc, 'stack, F> { match des { - &Descriptor::Pk(ref pk) - | &Descriptor::Pkh(ref pk) - | &Descriptor::ShWpkh(ref pk) - | &Descriptor::Wpkh(ref pk) => SatisfiedConstraints { + &Descriptor::Pk(ref pk) | &Descriptor::Pkh(ref pk) => SatisfiedConstraints { verify_sig: verify_sig, public_key: Some(pk), state: vec![], @@ -295,24 +281,67 @@ where height, has_errored: false, }, - &Descriptor::Sh(ref miniscript) - | &Descriptor::Bare(ref miniscript) - | &Descriptor::ShWsh(ref miniscript) - | &Descriptor::Wsh(ref miniscript) => SatisfiedConstraints { + &Descriptor::ShWpkh(ref pk) | &Descriptor::Wpkh(ref pk) => SatisfiedConstraints { verify_sig: verify_sig, - public_key: None, - state: vec![NodeEvaluationState { - node: miniscript, - n_evaluated: 0, - n_satisfied: 0, - }], + public_key: Some(pk), + state: vec![], stack: stack, age, height, has_errored: false, }, + &Descriptor::Wsh(ref miniscript) | &Descriptor::ShWsh(ref miniscript) => { + SatisfiedConstraints { + verify_sig: verify_sig, + public_key: None, + state: vec![NodeEvaluationState { + node: Any::from_segwitv0(miniscript), + n_evaluated: 0, + n_satisfied: 0, + }], + stack: stack, + age, + height, + has_errored: false, + } + } + &Descriptor::Sh(ref miniscript) | &Descriptor::Bare(ref miniscript) => { + SatisfiedConstraints { + verify_sig: verify_sig, + public_key: None, + state: vec![NodeEvaluationState { + node: Any::from_legacy(miniscript), + n_evaluated: 0, + n_satisfied: 0, + }], + stack: stack, + age, + height, + has_errored: false, + } + } } } +} + +impl<'desc, 'stack, F> SatisfiedConstraints<'desc, 'stack, F> +where + Any: ScriptContext, + F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool, +{ + /// Helper function to push a NodeEvaluationState on state stack + fn push_evaluation_state( + &mut self, + node: &'desc Miniscript, + n_evaluated: usize, + n_satisfied: usize, + ) -> () { + self.state.push(NodeEvaluationState { + node, + n_evaluated, + n_satisfied, + }) + } /// Helper function to step the iterator fn iter_next(&mut self) -> Option, Error>> { @@ -1022,6 +1051,7 @@ mod tests { Error, HashLockType, NodeEvaluationState, SatisfiedConstraint, SatisfiedConstraints, Stack, StackElement, }; + use miniscript::context::{Any, Legacy}; use std::str::FromStr; use BitcoinSig; use Miniscript; @@ -1074,7 +1104,7 @@ mod tests { fn from_stack<'stack, 'elem, F>( verify_fn: F, stack: Stack<'stack>, - ms: &'elem Miniscript, + ms: &'elem Miniscript, ) -> SatisfiedConstraints<'elem, 'stack, F> where F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool, @@ -1084,7 +1114,7 @@ mod tests { stack: stack, public_key: None, state: vec![NodeEvaluationState { - node: ms, + node: Any::from_legacy(ms), n_evaluated: 0, n_satisfied: 0, }], diff --git a/src/lib.rs b/src/lib.rs index 26e868cc2..46c44dd02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,6 +105,7 @@ use bitcoin::blockdata::{opcodes, script}; use bitcoin::hashes::{hash160, sha256, Hash}; pub use descriptor::{Descriptor, SatisfiedConstraints}; +pub use miniscript::context::{Legacy, ScriptContext, Segwitv0}; pub use miniscript::decode::Terminal; pub use miniscript::satisfy::{BitcoinSig, Satisfier}; pub use miniscript::Miniscript; @@ -113,6 +114,12 @@ pub use miniscript::Miniscript; pub trait MiniscriptKey: Clone + Eq + Ord + str::FromStr + fmt::Debug + fmt::Display + hash::Hash { + /// Check if the publicKey is uncompressed. The default + /// implementation returns false + fn is_uncompressed(&self) -> bool { + false + } + /// The associated Hash type with the publicKey type Hash: Clone + Eq + Ord + str::FromStr + fmt::Display + fmt::Debug + hash::Hash; ///Converts an object to PublicHash @@ -120,6 +127,12 @@ pub trait MiniscriptKey: } impl MiniscriptKey for bitcoin::PublicKey { + /// `is_uncompressed` returns true only for + /// bitcoin::Publickey type if the underlying key is uncompressed. + fn is_uncompressed(&self) -> bool { + !self.compressed + } + type Hash = hash160::Hash; fn to_pubkeyhash(&self) -> Self::Hash { @@ -314,6 +327,8 @@ pub enum Error { PolicyError(policy::concrete::PolicyError), ///Interpreter related errors InterpreterError(descriptor::InterpreterError), + /// Forward script context related errors + ContextError(miniscript::context::ScriptContextError), /// Bad Script Sig. As per standardness rules, only pushes are allowed in /// scriptSig. This error is invoked when op_codes are pushed onto the stack /// As per the current implementation, pushing an integer apart from 0 or 1 @@ -338,15 +353,23 @@ pub enum Error { } #[doc(hidden)] -impl From> for Error +impl From> for Error where Pk: MiniscriptKey, + Ctx: ScriptContext, { - fn from(e: miniscript::types::Error) -> Error { + fn from(e: miniscript::types::Error) -> Error { Error::TypeCheck(e.to_string()) } } +#[doc(hidden)] +impl From for Error { + fn from(e: miniscript::context::ScriptContextError) -> Error { + Error::ContextError(e) + } +} + fn errstr(s: &str) -> Error { Error::Unexpected(s.to_owned()) } @@ -410,6 +433,7 @@ impl fmt::Display for Error { Error::BadDescriptor => f.write_str("could not create a descriptor"), Error::Secp(ref e) => fmt::Display::fmt(e, f), Error::InterpreterError(ref e) => fmt::Display::fmt(e, f), + Error::ContextError(ref e) => fmt::Display::fmt(e, f), #[cfg(feature = "compiler")] Error::CompilerError(ref e) => fmt::Display::fmt(e, f), Error::PolicyError(ref e) => fmt::Display::fmt(e, f), diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index b442a0874..857888358 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -28,6 +28,7 @@ use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; use errstr; use expression; use miniscript::types::{self, Property}; +use miniscript::ScriptContext; use script_num_size; use std::sync::Arc; use str::FromStr; @@ -37,11 +38,11 @@ use MiniscriptKey; use Terminal; use ToPublicKey; -impl Terminal { +impl Terminal { /// Internal helper function for displaying wrapper types; returns /// a character to display before the `:` as well as a reference /// to the wrapped type to allow easy recursion - fn wrap_char(&self) -> Option<(char, &Arc>)> { + fn wrap_char(&self) -> Option<(char, &Arc>)> { match *self { Terminal::Alt(ref sub) => Some(('a', sub)), Terminal::Swap(ref sub) => Some(('s', sub)), @@ -58,20 +59,21 @@ impl Terminal { } } -impl Terminal { +impl Terminal { /// Convert an AST element with one public key type to one of another - /// public key type + /// public key type .This will panic while converting to + /// Segwit Miniscript using uncompressed public keys pub fn translate_pk( &self, translatefpk: &mut FPk, translatefpkh: &mut FPkh, - ) -> Result, Error> + ) -> Result, Error> where FPk: FnMut(&Pk) -> Result, FPkh: FnMut(&Pk::Hash) -> Result, Q: MiniscriptKey, { - Ok(match *self { + let frag = match *self { Terminal::PkK(ref p) => Terminal::PkK(translatefpk(p)?), Terminal::PkH(ref p) => Terminal::PkH(translatefpkh(p)?), Terminal::After(n) => Terminal::After(n), @@ -133,7 +135,7 @@ impl Terminal { Arc::new(right.translate_pk(translatefpk, translatefpkh)?), ), Terminal::Thresh(k, ref subs) => { - let subs: Result>>, _> = subs + let subs: Result>>, _> = subs .iter() .map(|s| { s.translate_pk(&mut *translatefpk, &mut *translatefpkh) @@ -146,11 +148,16 @@ impl Terminal { let keys: Result, _> = keys.iter().map(&mut *translatefpk).collect(); Terminal::Multi(k, keys?) } - }) + }; + Ctx::check_frag_validity(&frag).expect( + "Translated fragment not valid.\n + Uncompressed Pubkeys are non-standard in Segwit Context", + ); + Ok(frag) } } -impl fmt::Debug for Terminal { +impl fmt::Debug for Terminal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("[")?; if let Ok(type_map) = types::Type::type_check(self, |_| None) { @@ -244,7 +251,7 @@ impl fmt::Debug for Terminal { } } -impl fmt::Display for Terminal { +impl fmt::Display for Terminal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Terminal::PkK(ref pk) => write!(f, "pk_k({})", pk), @@ -332,24 +339,26 @@ impl fmt::Display for Terminal { } } -impl expression::FromTree for Arc> +impl expression::FromTree for Arc> where Pk: MiniscriptKey, + Ctx: ScriptContext, ::Err: ToString, <::Hash as str::FromStr>::Err: ToString, { - fn from_tree(top: &expression::Tree) -> Result>, Error> { + fn from_tree(top: &expression::Tree) -> Result>, Error> { Ok(Arc::new(expression::FromTree::from_tree(top)?)) } } -impl expression::FromTree for Terminal +impl expression::FromTree for Terminal where Pk: MiniscriptKey, + Ctx: ScriptContext, ::Err: ToString, <::Hash as str::FromStr>::Err: ToString, { - fn from_tree(top: &expression::Tree) -> Result, Error> { + fn from_tree(top: &expression::Tree) -> Result, Error> { let mut aliased_wrap; let frag_name; let frag_wrap; @@ -471,7 +480,7 @@ where return Err(errstr("empty thresholds not allowed in descriptors")); } - let subs: Result>>, _> = top.args[1..] + let subs: Result>>, _> = top.args[1..] .iter() .map(|sub| expression::FromTree::from_tree(sub)) .collect(); @@ -500,6 +509,8 @@ where top.args.len(), ))), }?; + // Check whether the unwrapped miniscript is valid under the current context + Ctx::check_frag_validity(&unwrapped)?; for ch in frag_wrap.chars().rev() { match ch { 'a' => unwrapped = Terminal::Alt(Arc::new(Miniscript::from_ast(unwrapped)?)), @@ -534,23 +545,25 @@ where } x => return Err(Error::UnknownWrapper(x)), } + // Check whether the wrapper is valid under the current context + Ctx::check_frag_validity(&unwrapped)?; } Ok(unwrapped) } } /// Helper trait to add a `push_astelem` method to `script::Builder` -trait PushAstElem { - fn push_astelem(self, ast: &Miniscript) -> Self; +trait PushAstElem { + fn push_astelem(self, ast: &Miniscript) -> Self; } -impl PushAstElem for script::Builder { - fn push_astelem(self, ast: &Miniscript) -> Self { +impl PushAstElem for script::Builder { + fn push_astelem(self, ast: &Miniscript) -> Self { ast.node.encode(self) } } -impl Terminal { +impl Terminal { /// Encode the element as a fragment of Bitcoin Script. The inverse /// function, from Script to an AST element, is implemented in the /// `parse` module. diff --git a/src/miniscript/context.rs b/src/miniscript/context.rs new file mode 100644 index 000000000..8fe870e49 --- /dev/null +++ b/src/miniscript/context.rs @@ -0,0 +1,198 @@ +// Miniscript +// Written in 2019 by +// Sanket Kanjalkar and Andrew Poelstra +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +use std::fmt; +use {Miniscript, MiniscriptKey, Terminal}; + +/// Error for Script Context +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum ScriptContextError { + /// Script Context does not permit PkH for non-malleability + /// It is not possible to estimate the pubkey size at the creation + /// time because of uncompressed pubkeys + MalleablePkH, + /// Script Context does not permit OrI for non-malleability + /// Legacy fragments allow non-minimal IF which results in malleability + MalleableOrI, + /// Script Context does not permit DupIf for non-malleability + /// Legacy fragments allow non-minimal IF which results in malleability + MalleableDupIf, + /// Only Compressed keys allowed under current descriptor + /// Segwitv0 fragments do not allow uncompressed pubkeys + CompressedOnly, +} + +impl fmt::Display for ScriptContextError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ScriptContextError::MalleablePkH => write!(f, "PkH is malleable under Legacy rules"), + ScriptContextError::MalleableOrI => write!(f, "OrI is malleable under Legacy rules"), + ScriptContextError::MalleableDupIf => { + write!(f, "DupIf is malleable under Legacy rules") + } + ScriptContextError::CompressedOnly => { + write!(f, "Uncompressed pubkeys not allowed in segwit context") + } + } + } +} + +pub trait ScriptContext: + fmt::Debug + Clone + Ord + PartialOrd + Eq + PartialEq + private::Sealed +{ + /// Depending on ScriptContext, fragments can be malleable. For Example, + /// under Legacy context, PkH is malleable because it is possible to + /// estimate the cost of satisfaction because of compressed keys + /// This is currently only used in compiler code for removing malleable + /// compilations. + /// This does NOT recursively check if the children of the fragment are + /// valid or not. Since the compilation proceeds in a leaf to root fashion, + /// a recursive check is unnecessary. + fn check_frag_non_malleable( + _frag: &Terminal, + ) -> Result<(), ScriptContextError>; + + /// Depending on script Context, some of the Terminals might not be valid. + /// For example, in Segwit Context with MiniscriptKey as bitcoin::PublicKey + /// uncompressed public keys are non-standard and thus invalid. + /// Post Tapscript upgrade, this would have to consider other nodes. + /// This does not recursively check + fn check_frag_validity( + _frag: &Terminal, + ) -> Result<(), ScriptContextError>; +} + +/// Legacy ScriptContext +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Legacy {} + +impl ScriptContext for Legacy { + fn check_frag_non_malleable( + frag: &Terminal, + ) -> Result<(), ScriptContextError> { + match *frag { + Terminal::PkH(ref _pkh) => Err(ScriptContextError::MalleablePkH), + Terminal::OrI(ref _a, ref _b) => Err(ScriptContextError::MalleableOrI), + Terminal::DupIf(ref _ms) => Err(ScriptContextError::MalleableDupIf), + _ => Ok(()), + } + } + + fn check_frag_validity( + _frag: &Terminal, + ) -> Result<(), ScriptContextError> { + Ok(()) + } +} + +/// Segwitv0 ScriptContext +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Segwitv0 {} + +impl ScriptContext for Segwitv0 { + fn check_frag_non_malleable( + _frag: &Terminal, + ) -> Result<(), ScriptContextError> { + Ok(()) + } + + fn check_frag_validity( + frag: &Terminal, + ) -> Result<(), ScriptContextError> { + match *frag { + Terminal::PkK(ref pk) if pk.is_uncompressed() => { + Err(ScriptContextError::CompressedOnly) + } + _ => Ok(()), + } + } +} + +/// Any ScriptContext. None of the checks should ever be invokde from +/// under this context. +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Any {} +impl ScriptContext for Any { + fn check_frag_non_malleable( + _frag: &Terminal, + ) -> Result<(), ScriptContextError> { + unreachable!() + } + + fn check_frag_validity( + _frag: &Terminal, + ) -> Result<(), ScriptContextError> { + unreachable!() + } +} + +impl Any { + pub(crate) fn from_legacy( + ms: &Miniscript, + ) -> &Miniscript { + // The fields Miniscript and Miniscript only + // differ in PhantomData. This unsafe assumes that the unlerlying byte + // representation of the both should be the same. There is a Miri test + // checking the same. + unsafe { + use std::mem::transmute; + transmute::<&Miniscript, &Miniscript>(ms) + } + } + + pub(crate) fn from_segwitv0( + ms: &Miniscript, + ) -> &Miniscript { + // The fields Miniscript and Miniscript only + // differ in PhantomData. This unsafe assumes that the unlerlying byte + // representation of the both should be the same. There is a Miri test + // checking the same. + unsafe { + use std::mem::transmute; + transmute::<&Miniscript, &Miniscript>(ms) + } + } +} + +/// Private Mod to prevent downstream from implementing this public trait +mod private { + use super::{Any, Legacy, Segwitv0}; + + pub trait Sealed {} + + // Implement for those same types, but no others. + impl Sealed for Legacy {} + impl Sealed for Segwitv0 {} + impl Sealed for Any {} +} + +#[cfg(test)] +mod tests { + use super::{Any, Legacy, Segwitv0}; + use std::str::FromStr; + + use {DummyKey, Miniscript}; + type Segwitv0Script = Miniscript; + type LegacyScript = Miniscript; + + //miri test for unsafe code + #[test] + fn miri_test_context_transform() { + let segwit_ms = Segwitv0Script::from_str("andor(pk(),or_i(and_v(vc:pk_h(),hash160(1111111111111111111111111111111111111111)),older(1008)),pk())").unwrap(); + let legacy_ms = LegacyScript::from_str("andor(pk(),or_i(and_v(vc:pk_h(),hash160(1111111111111111111111111111111111111111)),older(1008)),pk())").unwrap(); + + let _any = Any::from_legacy(&legacy_ms); + let _any = Any::from_segwitv0(&segwit_ms); + } +} diff --git a/src/miniscript/decode.rs b/src/miniscript/decode.rs index 63dbe2047..b87884d91 100644 --- a/src/miniscript/decode.rs +++ b/src/miniscript/decode.rs @@ -18,12 +18,14 @@ //! use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; +use std::marker::PhantomData; use {bitcoin, Miniscript}; use miniscript::lex::{Token as Tk, TokenIter}; use miniscript::types::extra_props::ExtData; use miniscript::types::Property; use miniscript::types::Type; +use miniscript::ScriptContext; use std::sync::Arc; use Error; use MiniscriptKey; @@ -60,7 +62,7 @@ enum NonTerm { } /// All AST elements #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Terminal { +pub enum Terminal { /// `1` True, /// `0` @@ -86,42 +88,42 @@ pub enum Terminal { Hash160(hash160::Hash), // Wrappers /// `TOALTSTACK [E] FROMALTSTACK` - Alt(Arc>), + Alt(Arc>), /// `SWAP [E1]` - Swap(Arc>), + Swap(Arc>), /// `[Kt]/[Ke] CHECKSIG` - Check(Arc>), + Check(Arc>), /// `DUP IF [V] ENDIF` - DupIf(Arc>), + DupIf(Arc>), /// [T] VERIFY - Verify(Arc>), + Verify(Arc>), /// SIZE 0NOTEQUAL IF [Fn] ENDIF - NonZero(Arc>), + NonZero(Arc>), /// [X] 0NOTEQUAL - ZeroNotEqual(Arc>), + ZeroNotEqual(Arc>), // Conjunctions /// [V] [T]/[V]/[F]/[Kt] - AndV(Arc>, Arc>), + AndV(Arc>, Arc>), /// [E] [W] BOOLAND - AndB(Arc>, Arc>), + AndB(Arc>, Arc>), /// [various] NOTIF [various] ELSE [various] ENDIF AndOr( - Arc>, - Arc>, - Arc>, + Arc>, + Arc>, + Arc>, ), // Disjunctions /// [E] [W] BOOLOR - OrB(Arc>, Arc>), + OrB(Arc>, Arc>), /// [E] IFDUP NOTIF [T]/[E] ENDIF - OrD(Arc>, Arc>), + OrD(Arc>, Arc>), /// [E] NOTIF [V] ENDIF - OrC(Arc>, Arc>), + OrC(Arc>, Arc>), /// IF [various] ELSE [various] ENDIF - OrI(Arc>, Arc>), + OrI(Arc>, Arc>), // Thresholds /// [E] ([W] ADD)* k EQUAL - Thresh(usize, Vec>>), + Thresh(usize, Vec>>), /// k ()* n CHECKMULTISIG Multi(usize, Vec), } @@ -142,22 +144,25 @@ macro_rules! match_token { } ///Vec representing terminals stack while decoding. -struct TerminalStack(Vec>); +struct TerminalStack(Vec>); -impl TerminalStack { +impl TerminalStack { ///Wrapper around self.0.pop() - fn pop(&mut self) -> Option> { + fn pop(&mut self) -> Option> { self.0.pop() } ///reduce, type check and push a 0-arg node - fn reduce0(&mut self, ms: Terminal) -> Result<(), Error> { + fn reduce0(&mut self, ms: Terminal) -> Result<(), Error> { + Ctx::check_frag_validity(&ms)?; + let ty = Type::type_check(&ms, return_none)?; let ext = ExtData::type_check(&ms, return_none)?; self.0.push(Miniscript { node: ms, ty: ty, ext: ext, + phantom: PhantomData, }); Ok(()) } @@ -165,10 +170,11 @@ impl TerminalStack { ///reduce, type check and push a 1-arg node fn reduce1(&mut self, wrap: F) -> Result<(), Error> where - F: FnOnce(Arc>) -> Terminal, + F: FnOnce(Arc>) -> Terminal, { let top = self.pop().unwrap(); let wrapped_ms = wrap(Arc::new(top)); + Ctx::check_frag_validity(&wrapped_ms)?; let ty = Type::type_check(&wrapped_ms, return_none)?; let ext = ExtData::type_check(&wrapped_ms, return_none)?; @@ -176,6 +182,7 @@ impl TerminalStack { node: wrapped_ms, ty: ty, ext: ext, + phantom: PhantomData, }); Ok(()) } @@ -183,18 +190,21 @@ impl TerminalStack { ///reduce, type check and push a 2-arg node fn reduce2(&mut self, wrap: F) -> Result<(), Error> where - F: FnOnce(Arc>, Arc>) -> Terminal, + F: FnOnce(Arc>, Arc>) -> Terminal, { let left = self.pop().unwrap(); let right = self.pop().unwrap(); let wrapped_ms = wrap(Arc::new(left), Arc::new(right)); + Ctx::check_frag_validity(&wrapped_ms)?; + let ty = Type::type_check(&wrapped_ms, return_none)?; let ext = ExtData::type_check(&wrapped_ms, return_none)?; self.0.push(Miniscript { node: wrapped_ms, ty: ty, ext: ext, + phantom: PhantomData, }); Ok(()) } @@ -202,7 +212,9 @@ impl TerminalStack { /// Parse a script fragment into an `Terminal` #[allow(unreachable_patterns)] -pub fn parse(tokens: &mut TokenIter) -> Result, Error> { +pub fn parse( + tokens: &mut TokenIter, +) -> Result, Error> { let mut non_term = Vec::with_capacity(tokens.len()); let mut term = TerminalStack(Vec::with_capacity(tokens.len())); @@ -395,6 +407,7 @@ pub fn parse(tokens: &mut TokenIter) -> Result, E node: wrapped_ms, ty: ty, ext: ext, + phantom: PhantomData, }); } Some(NonTerm::ThreshW { n, k }) => { diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index 3be192499..958ac52a5 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -26,12 +26,17 @@ #[cfg(feature = "serde")] use serde::{de, ser}; +use std::marker::PhantomData; use std::{fmt, str}; use bitcoin; use bitcoin::blockdata::script; +pub use self::context::Legacy; +pub use self::context::Segwitv0; + pub mod astelem; +pub(crate) mod context; pub mod decode; pub mod lex; pub mod satisfy; @@ -39,8 +44,11 @@ pub mod types; use self::lex::{lex, TokenIter}; use self::types::Property; +pub use miniscript::context::ScriptContext; +use miniscript::decode::Terminal; use miniscript::types::extra_props::ExtData; use miniscript::types::Type; + use std::cmp; use std::sync::Arc; use MiniscriptKey; @@ -48,20 +56,22 @@ use {expression, Error, ToPublicKey}; /// Top-level script AST type #[derive(Clone, Hash)] -pub struct Miniscript { +pub struct Miniscript { ///A node in the Abstract Syntax Tree( - pub node: decode::Terminal, + pub node: Terminal, ///The correctness and malleability type information for the AST node pub ty: types::Type, ///Additional information helpful for extra analysis. pub ext: types::extra_props::ExtData, + /// Context PhantomData. Only accessible inside this crate + pub(crate) phantom: PhantomData, } /// `PartialOrd` of `Miniscript` must depend only on node and not the type information. /// The type information and extra_properties can be deterministically determined /// by the ast. -impl PartialOrd for Miniscript { - fn partial_cmp(&self, other: &Miniscript) -> Option { +impl PartialOrd for Miniscript { + fn partial_cmp(&self, other: &Miniscript) -> Option { Some(self.node.cmp(&other.node)) } } @@ -69,8 +79,8 @@ impl PartialOrd for Miniscript { /// `Ord` of `Miniscript` must depend only on node and not the type information. /// The type information and extra_properties can be deterministically determined /// by the ast. -impl Ord for Miniscript { - fn cmp(&self, other: &Miniscript) -> cmp::Ordering { +impl Ord for Miniscript { + fn cmp(&self, other: &Miniscript) -> cmp::Ordering { self.node.cmp(&other.node) } } @@ -78,8 +88,8 @@ impl Ord for Miniscript { /// `PartialEq` of `Miniscript` must depend only on node and not the type information. /// The type information and extra_properties can be deterministically determined /// by the ast. -impl PartialEq for Miniscript { - fn eq(&self, other: &Miniscript) -> bool { +impl PartialEq for Miniscript { + fn eq(&self, other: &Miniscript) -> bool { self.node.eq(&other.node) } } @@ -87,47 +97,48 @@ impl PartialEq for Miniscript { /// `Eq` of `Miniscript` must depend only on node and not the type information. /// The type information and extra_properties can be deterministically determined /// by the ast. -impl Eq for Miniscript {} +impl Eq for Miniscript {} -impl fmt::Debug for Miniscript { +impl fmt::Debug for Miniscript { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self.node) } } -impl Miniscript { +impl Miniscript { /// Add type information(Type and Extdata) to Miniscript based on /// `AstElem` fragment. Dependent on display and clone because of Error /// Display code of type_check. - pub fn from_ast(t: decode::Terminal) -> Result, Error> { + pub fn from_ast(t: Terminal) -> Result, Error> { Ok(Miniscript { ty: Type::type_check(&t, |_| None)?, ext: ExtData::type_check(&t, |_| None)?, node: t, + phantom: PhantomData, }) } } -impl fmt::Display for Miniscript { +impl fmt::Display for Miniscript { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.node) } } -impl Miniscript { +impl Miniscript { /// Extracts the `AstElem` representing the root of the miniscript - pub fn into_inner(self) -> decode::Terminal { + pub fn into_inner(self) -> Terminal { self.node } - pub fn as_inner(&self) -> &decode::Terminal { + pub fn as_inner(&self) -> &Terminal { &self.node } } -impl Miniscript { +impl Miniscript { /// Attempt to parse a script into a Miniscript representation - pub fn parse(script: &script::Script) -> Result, Error> { + pub fn parse(script: &script::Script) -> Result, Error> { // Transactions more than 100Kb are non-standard if script.len() > 100_000 { return Err(Error::ScriptSizeTooLarge); @@ -136,6 +147,7 @@ impl Miniscript { let mut iter = TokenIter::new(tokens); let top = decode::parse(&mut iter)?; + Ctx::check_frag_validity(&top.node)?; let type_check = types::Type::type_check(&top.node, |_| None)?; if type_check.corr.base != types::Base::B { return Err(Error::NonTopLevel(format!("{:?}", top))); @@ -148,7 +160,7 @@ impl Miniscript { } } -impl Miniscript { +impl Miniscript { /// Encode as a Bitcoin script pub fn encode(&self) -> script::Script { self.node.encode(script::Builder::new()).into_script() @@ -198,15 +210,18 @@ impl Miniscript { } } -impl Miniscript { - pub fn translate_pk( +impl Miniscript { + /// This will panic if translatefpk returns an uncompressed key when + /// converting to a Segwit descriptor. To prevent this panic, ensure + /// translatefpk returns an error in this case instead. + pub fn translate_pk( &self, translatefpk: &mut FPk, translatefpkh: &mut FPkh, - ) -> Result, Error> + ) -> Result, FuncError> where - FPk: FnMut(&Pk) -> Result, - FPkh: FnMut(&Pk::Hash) -> Result, + FPk: FnMut(&Pk) -> Result, + FPkh: FnMut(&Pk::Hash) -> Result, Q: MiniscriptKey, { let inner = self.node.translate_pk(translatefpk, translatefpkh)?; @@ -216,11 +231,12 @@ impl Miniscript { ty: self.ty, ext: self.ext, node: inner, + phantom: PhantomData, }) } } -impl Miniscript { +impl Miniscript { /// Attempt to produce a satisfying witness for the /// witness script represented by the parse tree pub fn satisfy>(&self, satisfier: S) -> Option>> { @@ -231,44 +247,48 @@ impl Miniscript { } } -impl expression::FromTree for Arc> +impl expression::FromTree for Arc> where Pk: MiniscriptKey, + Ctx: ScriptContext, ::Err: ToString, <::Hash as str::FromStr>::Err: ToString, { - fn from_tree(top: &expression::Tree) -> Result>, Error> { + fn from_tree(top: &expression::Tree) -> Result>, Error> { Ok(Arc::new(expression::FromTree::from_tree(top)?)) } } -impl expression::FromTree for Miniscript +impl expression::FromTree for Miniscript where Pk: MiniscriptKey, + Ctx: ScriptContext, ::Err: ToString, <::Hash as str::FromStr>::Err: ToString, { /// Parse an expression tree into a Miniscript. As a general rule, this /// should not be called directly; rather go through the descriptor API. - fn from_tree(top: &expression::Tree) -> Result, Error> { - let inner: decode::Terminal = expression::FromTree::from_tree(top)?; + fn from_tree(top: &expression::Tree) -> Result, Error> { + let inner: Terminal = expression::FromTree::from_tree(top)?; Ok(Miniscript { ty: Type::type_check(&inner, |_| None)?, ext: ExtData::type_check(&inner, |_| None)?, node: inner, + phantom: PhantomData, }) } } -impl str::FromStr for Miniscript +impl str::FromStr for Miniscript where Pk: MiniscriptKey, + Ctx: ScriptContext, ::Err: ToString, <::Hash as str::FromStr>::Err: ToString, { type Err = Error; - fn from_str(s: &str) -> Result, Error> { + fn from_str(s: &str) -> Result, Error> { for ch in s.as_bytes() { if *ch < 20 || *ch > 127 { return Err(Error::Unprintable(*ch)); @@ -276,7 +296,7 @@ where } let top = expression::Tree::from_str(s)?; - let ms: Miniscript = expression::FromTree::from_tree(&top)?; + let ms: Miniscript = expression::FromTree::from_tree(&top)?; if ms.ty.corr.base != types::Base::B { Err(Error::NonTopLevel(format!("{:?}", ms))) @@ -287,32 +307,33 @@ where } #[cfg(feature = "serde")] -impl ser::Serialize for Miniscript { +impl ser::Serialize for Miniscript { fn serialize(&self, s: S) -> Result { s.collect_str(self) } } #[cfg(feature = "serde")] -impl<'de, Pk> de::Deserialize<'de> for Miniscript +impl<'de, Pk, Ctx> de::Deserialize<'de> for Miniscript where Pk: MiniscriptKey, + Ctx: ScriptContext, ::Err: ToString, <::Hash as str::FromStr>::Err: ToString, { - fn deserialize>(d: D) -> Result, D::Error> { - use std::marker::PhantomData; + fn deserialize>(d: D) -> Result, D::Error> { use std::str::FromStr; - struct StrVisitor(PhantomData); + struct StrVisitor(PhantomData<(Qk, Ctx)>); - impl<'de, Qk> de::Visitor<'de> for StrVisitor + impl<'de, Qk, Ctx> de::Visitor<'de> for StrVisitor where Qk: MiniscriptKey, + Ctx: ScriptContext, ::Err: ToString, <::Hash as str::FromStr>::Err: ToString, { - type Value = Miniscript; + type Value = Miniscript; fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.write_str("an ASCII miniscript string") @@ -343,11 +364,13 @@ where #[cfg(test)] mod tests { - use super::Miniscript; + use super::Segwitv0; + use super::{Miniscript, ScriptContext}; use hex_script; - use miniscript::decode::Terminal; use miniscript::types::{self, ExtData, Property, Type}; + use miniscript::Terminal; use policy::Liftable; + use std::marker::PhantomData; use DummyKey; use DummyKeyHash; @@ -358,7 +381,7 @@ mod tests { use std::sync::Arc; use MiniscriptKey; - type BScript = Miniscript; + type Segwitv0Script = Miniscript; fn pubkeys(n: usize) -> Vec { let mut ret = Vec::with_capacity(n); @@ -381,12 +404,13 @@ mod tests { ret } - fn string_rtt( - script: Miniscript, + fn string_rtt( + script: Miniscript, expected_debug: Str1, expected_display: Str2, ) where Pk: MiniscriptKey, + Ctx: ScriptContext, ::Err: ToString, <::Hash as str::FromStr>::Err: ToString, Str1: Into>, @@ -409,23 +433,23 @@ mod tests { assert_eq!(translated, Ok(script)); } - fn script_rtt>>(script: BScript, expected_hex: Str1) { + fn script_rtt>>(script: Segwitv0Script, expected_hex: Str1) { assert_eq!(script.ty.corr.base, types::Base::B); let bitcoin_script = script.encode(); assert_eq!(bitcoin_script.len(), script.script_size()); if let Some(expected) = expected_hex.into() { assert_eq!(format!("{:x}", bitcoin_script), expected); } - let roundtrip = Miniscript::parse(&bitcoin_script).expect("parse string serialization"); + let roundtrip = Segwitv0Script::parse(&bitcoin_script).expect("parse string serialization"); assert_eq!(roundtrip, script); } - fn roundtrip(tree: &Miniscript, s: &str) { + fn roundtrip(tree: &Segwitv0Script, s: &str) { assert_eq!(tree.ty.corr.base, types::Base::B); let ser = tree.encode(); assert_eq!(ser.len(), tree.script_size()); assert_eq!(ser.to_string(), s); - let deser = Miniscript::parse(&ser).expect("deserialize result of serialize"); + let deser = Segwitv0Script::parse(&ser).expect("deserialize result of serialize"); assert_eq!(*tree, deser); } @@ -438,7 +462,7 @@ mod tests { ops: usize, _stack: usize, ) { - let ms: Result, _> = Miniscript::from_str(ms); + let ms: Result = Miniscript::from_str(ms); match (ms, valid) { (Ok(ms), true) => { assert_eq!(format!("{:x}", ms.encode()), expected_hex); @@ -510,36 +534,42 @@ mod tests { .unwrap(); let hash = hash160::Hash::from_inner([17; 20]); - let pkk_ms: Miniscript = Miniscript { + let pkk_ms: Miniscript = Miniscript { node: Terminal::Check(Arc::new(Miniscript { node: Terminal::PkK(DummyKey), ty: Type::from_pk_k(), ext: types::extra_props::ExtData::from_pk_k(), + phantom: PhantomData, })), ty: Type::cast_check(Type::from_pk_k()).unwrap(), ext: ExtData::cast_check(ExtData::from_pk_k()).unwrap(), + phantom: PhantomData, }; string_rtt(pkk_ms, "[B/onduesm]c:[K/onduesm]pk_k(DummyKey)", "pk()"); - let pkh_ms: Miniscript = Miniscript { + let pkh_ms: Miniscript = Miniscript { node: Terminal::Check(Arc::new(Miniscript { node: Terminal::PkH(DummyKeyHash), ty: Type::from_pk_h(), ext: types::extra_props::ExtData::from_pk_h(), + phantom: PhantomData, })), ty: Type::cast_check(Type::from_pk_h()).unwrap(), ext: ExtData::cast_check(ExtData::from_pk_h()).unwrap(), + phantom: PhantomData, }; string_rtt(pkh_ms, "[B/nduesm]c:[K/nduesm]pk_h(DummyKeyHash)", "pkh()"); - let pkk_ms: Miniscript = Miniscript { + let pkk_ms: Segwitv0Script = Miniscript { node: Terminal::Check(Arc::new(Miniscript { node: Terminal::PkK(pk), ty: Type::from_pk_k(), ext: types::extra_props::ExtData::from_pk_k(), + phantom: PhantomData, })), ty: Type::cast_check(Type::from_pk_k()).unwrap(), ext: ExtData::cast_check(ExtData::from_pk_k()).unwrap(), + phantom: PhantomData, }; script_rtt( @@ -548,14 +578,16 @@ mod tests { 202020202ac", ); - let pkh_ms: Miniscript = Miniscript { + let pkh_ms: Segwitv0Script = Miniscript { node: Terminal::Check(Arc::new(Miniscript { node: Terminal::PkH(hash), ty: Type::from_pk_h(), ext: types::extra_props::ExtData::from_pk_h(), + phantom: PhantomData, })), ty: Type::cast_check(Type::from_pk_h()).unwrap(), ext: ExtData::cast_check(ExtData::from_pk_h()).unwrap(), + phantom: PhantomData, }; script_rtt(pkh_ms, "76a914111111111111111111111111111111111111111188ac"); @@ -574,31 +606,31 @@ mod tests { "Script(OP_0 OP_NOTIF OP_0 OP_ELSE OP_PUSHNUM_1 OP_ENDIF)", ); - assert!(Miniscript::::from_str("1()").is_err()); - assert!(Miniscript::::from_str("tv:1()").is_err()); + assert!(Segwitv0Script::from_str("1()").is_err()); + assert!(Segwitv0Script::from_str("tv:1()").is_err()); } #[test] fn pk_alias() { let pubkey = pubkeys(1)[0]; - let script: Miniscript = ms_str!("c:pk_k({})", pubkey.to_string()); + let script: Segwitv0Script = ms_str!("c:pk_k({})", pubkey.to_string()); string_rtt( script, - "[B/onduesm]c:[K/onduesm]pk_k(PublicKey { compressed: true, key: PublicKey(aa4c32e50fb34a95a372940ae3654b692ea35294748c3dd2c08b29f87ba9288c8294efcb73dc719e45b91c45f084e77aebc07c1ff3ed8f37935130a36304a340) })", + "[B/onduesm]c:[K/onduesm]pk_k(PublicKey { compressed: true, key: PublicKey(aa4c32e50fb34a95a372940ae3654b692ea35294748c3dd2c08b29f87ba9288c8294efcb73dc719e45b91c45f084e77aebc07c1ff3ed8f37935130a36304a340) })", "pk(028c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa)" ); - let script: Miniscript = ms_str!("pk({})", pubkey.to_string()); + let script: Segwitv0Script = ms_str!("pk({})", pubkey.to_string()); string_rtt( script, - "[B/onduesm]c:[K/onduesm]pk_k(PublicKey { compressed: true, key: PublicKey(aa4c32e50fb34a95a372940ae3654b692ea35294748c3dd2c08b29f87ba9288c8294efcb73dc719e45b91c45f084e77aebc07c1ff3ed8f37935130a36304a340) })", + "[B/onduesm]c:[K/onduesm]pk_k(PublicKey { compressed: true, key: PublicKey(aa4c32e50fb34a95a372940ae3654b692ea35294748c3dd2c08b29f87ba9288c8294efcb73dc719e45b91c45f084e77aebc07c1ff3ed8f37935130a36304a340) })", "pk(028c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa)" ); - let script: Miniscript = ms_str!("tv:pk({})", pubkey.to_string()); + let script: Segwitv0Script = ms_str!("tv:pk({})", pubkey.to_string()); string_rtt( script, @@ -608,7 +640,7 @@ mod tests { let pubkey_hash = hash160::Hash::from_str("f54a5851e9372b87810a8e60cdd2e7cfd80b6e31").unwrap(); - let script: Miniscript = ms_str!("c:pk_h({})", pubkey_hash.to_string()); + let script: Segwitv0Script = ms_str!("c:pk_h({})", pubkey_hash.to_string()); string_rtt( script, @@ -616,7 +648,7 @@ mod tests { "pkh(f54a5851e9372b87810a8e60cdd2e7cfd80b6e31)", ); - let script: Miniscript = ms_str!("pkh({})", pubkey_hash.to_string()); + let script: Segwitv0Script = ms_str!("pkh({})", pubkey_hash.to_string()); string_rtt( script, @@ -624,7 +656,7 @@ mod tests { "pkh(f54a5851e9372b87810a8e60cdd2e7cfd80b6e31)", ); - let script: Miniscript = ms_str!("tv:pkh({})", pubkey_hash.to_string()); + let script: Segwitv0Script = ms_str!("tv:pkh({})", pubkey_hash.to_string()); string_rtt( script, @@ -674,7 +706,7 @@ mod tests { OP_ENDIF)" ); - let miniscript: Miniscript = ms_str!( + let miniscript: Segwitv0Script = ms_str!( "or_d(multi(3,{},{},{}),and_v(v:multi(2,{},{}),older(10000)))", keys[0].to_string(), keys[1].to_string(), @@ -725,21 +757,21 @@ mod tests { #[test] fn deserialize() { // Most of these came from fuzzing, hence the increasing lengths - assert!(Miniscript::parse(&hex_script("")).is_err()); // empty - assert!(Miniscript::parse(&hex_script("00")).is_ok()); // FALSE - assert!(Miniscript::parse(&hex_script("51")).is_ok()); // TRUE - assert!(Miniscript::parse(&hex_script("69")).is_err()); // VERIFY - assert!(Miniscript::parse(&hex_script("0000")).is_err()); //and_v(FALSE,FALSE) - assert!(Miniscript::parse(&hex_script("1001")).is_err()); // incomplete push - assert!(Miniscript::parse(&hex_script("03990300b2")).is_err()); // non-minimal # - assert!(Miniscript::parse(&hex_script("8559b2")).is_err()); // leading bytes - assert!(Miniscript::parse(&hex_script("4c0169b2")).is_err()); // non-minimal push - assert!(Miniscript::parse(&hex_script("0000af0000ae85")).is_err()); // OR not BOOLOR + assert!(Segwitv0Script::parse(&hex_script("")).is_err()); // empty + assert!(Segwitv0Script::parse(&hex_script("00")).is_ok()); // FALSE + assert!(Segwitv0Script::parse(&hex_script("51")).is_ok()); // TRUE + assert!(Segwitv0Script::parse(&hex_script("69")).is_err()); // VERIFY + assert!(Segwitv0Script::parse(&hex_script("0000")).is_err()); //and_v(FALSE,FALSE) + assert!(Segwitv0Script::parse(&hex_script("1001")).is_err()); // incomplete push + assert!(Segwitv0Script::parse(&hex_script("03990300b2")).is_err()); // non-minimal # + assert!(Segwitv0Script::parse(&hex_script("8559b2")).is_err()); // leading bytes + assert!(Segwitv0Script::parse(&hex_script("4c0169b2")).is_err()); // non-minimal push + assert!(Segwitv0Script::parse(&hex_script("0000af0000ae85")).is_err()); // OR not BOOLOR // misc fuzzer problems - assert!(Miniscript::parse(&hex_script("0000000000af")).is_err()); - assert!(Miniscript::parse(&hex_script("04009a2970af00")).is_err()); // giant CMS key num - assert!(Miniscript::parse(&hex_script( + assert!(Segwitv0Script::parse(&hex_script("0000000000af")).is_err()); + assert!(Segwitv0Script::parse(&hex_script("04009a2970af00")).is_err()); // giant CMS key num + assert!(Segwitv0Script::parse(&hex_script( "2102ffffffffffffffefefefefefefefefefefef394c0fe5b711179e124008584753ac6900" )) .is_err()); diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index 8bccd9b08..3967abe3c 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -25,6 +25,7 @@ use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; use bitcoin::{self, secp256k1}; use {MiniscriptKey, ToPublicKey}; +use ScriptContext; use Terminal; /// Type alias for a signature/hashtype pair @@ -510,8 +511,8 @@ impl Satisfaction { } /// Produce a satisfaction - pub fn satisfy>( - term: &Terminal, + pub fn satisfy>( + term: &Terminal, stfr: &Sat, ) -> Self { match *term { @@ -750,8 +751,8 @@ impl Satisfaction { } /// Produce a satisfaction - fn dissatisfy>( - term: &Terminal, + fn dissatisfy>( + term: &Terminal, stfr: &Sat, ) -> Self { match *term { diff --git a/src/miniscript/types/extra_props.rs b/src/miniscript/types/extra_props.rs index b54c7a108..1b77f3710 100644 --- a/src/miniscript/types/extra_props.rs +++ b/src/miniscript/types/extra_props.rs @@ -1,7 +1,7 @@ //! Other miscellaneous type properties which are not related to //! correctness or malleability. -use super::{Error, ErrorKind, Property}; +use super::{Error, ErrorKind, Property, ScriptContext}; use script_num_size; use std::cmp; use MiniscriptKey; @@ -9,27 +9,12 @@ use Terminal; pub const MAX_OPS_PER_SCRIPT: usize = 201; -/// Whether a fragment is OK to be used in non-segwit scripts -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -pub enum LegacySafe { - /// The fragment can be used in pre-segwit contexts without concern - /// about malleability attacks/unbounded 3rd-party fee stuffing. This - /// means it has no `pk_h` constructions (cannot estimate public key - /// size from a hash) and no `d:`/`or_i` constructions (cannot control - /// the size of the switch input to `OP_IF`) - LegacySafe, - /// This fragment can only be safely used with Segwit - SegwitOnly, -} - /// Structure representing the extra type properties of a fragment which are /// relevant to legacy(pre-segwit) safety and fee estimation. If a fragment is /// used in pre-segwit transactions it will only be malleable but still is /// correct and sound. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct ExtData { - ///enum sorting whether the fragment is safe to be in used in pre-segwit context - pub legacy_safe: LegacySafe, /// The number of bytes needed to encode its scriptpubkey pub pk_cost: usize, /// Whether this fragment can be verify-wrapped for free @@ -49,7 +34,6 @@ impl Property for ExtData { fn from_true() -> Self { ExtData { - legacy_safe: LegacySafe::LegacySafe, pk_cost: 1, has_verify_form: false, ops_count_static: 0, @@ -60,7 +44,6 @@ impl Property for ExtData { fn from_false() -> Self { ExtData { - legacy_safe: LegacySafe::LegacySafe, pk_cost: 1, has_verify_form: false, ops_count_static: 0, @@ -71,7 +54,6 @@ impl Property for ExtData { fn from_pk_k() -> Self { ExtData { - legacy_safe: LegacySafe::LegacySafe, pk_cost: 34, has_verify_form: false, ops_count_static: 0, @@ -82,7 +64,6 @@ impl Property for ExtData { fn from_pk_h() -> Self { ExtData { - legacy_safe: LegacySafe::SegwitOnly, pk_cost: 24, has_verify_form: false, ops_count_static: 3, @@ -99,7 +80,6 @@ impl Property for ExtData { (false, false) => 2, }; ExtData { - legacy_safe: LegacySafe::LegacySafe, pk_cost: num_cost + 34 * n + 1, has_verify_form: true, ops_count_static: 1, @@ -115,7 +95,6 @@ impl Property for ExtData { fn from_sha256() -> Self { ExtData { - legacy_safe: LegacySafe::LegacySafe, pk_cost: 33 + 6, has_verify_form: true, ops_count_static: 4, @@ -126,7 +105,6 @@ impl Property for ExtData { fn from_hash256() -> Self { ExtData { - legacy_safe: LegacySafe::LegacySafe, pk_cost: 33 + 6, has_verify_form: true, ops_count_static: 4, @@ -137,7 +115,6 @@ impl Property for ExtData { fn from_ripemd160() -> Self { ExtData { - legacy_safe: LegacySafe::LegacySafe, pk_cost: 21 + 6, has_verify_form: true, ops_count_static: 4, @@ -148,7 +125,6 @@ impl Property for ExtData { fn from_hash160() -> Self { ExtData { - legacy_safe: LegacySafe::LegacySafe, pk_cost: 21 + 6, has_verify_form: true, ops_count_static: 4, @@ -159,7 +135,6 @@ impl Property for ExtData { fn from_time(t: u32) -> Self { ExtData { - legacy_safe: LegacySafe::LegacySafe, pk_cost: script_num_size(t as usize) + 1, has_verify_form: false, ops_count_static: 1, @@ -169,7 +144,6 @@ impl Property for ExtData { } fn cast_alt(self) -> Result { Ok(ExtData { - legacy_safe: self.legacy_safe, pk_cost: self.pk_cost + 2, has_verify_form: false, ops_count_static: self.ops_count_static + 2, @@ -180,7 +154,6 @@ impl Property for ExtData { fn cast_swap(self) -> Result { Ok(ExtData { - legacy_safe: self.legacy_safe, pk_cost: self.pk_cost + 1, has_verify_form: self.has_verify_form, ops_count_static: self.ops_count_static + 1, @@ -191,7 +164,6 @@ impl Property for ExtData { fn cast_check(self) -> Result { Ok(ExtData { - legacy_safe: self.legacy_safe, pk_cost: self.pk_cost + 1, has_verify_form: true, ops_count_static: self.ops_count_static + 1, @@ -202,7 +174,6 @@ impl Property for ExtData { fn cast_dupif(self) -> Result { Ok(ExtData { - legacy_safe: LegacySafe::SegwitOnly, pk_cost: self.pk_cost + 3, has_verify_form: false, ops_count_static: self.ops_count_static + 3, @@ -214,7 +185,6 @@ impl Property for ExtData { fn cast_verify(self) -> Result { let verify_cost = if self.has_verify_form { 0 } else { 1 }; Ok(ExtData { - legacy_safe: self.legacy_safe, pk_cost: self.pk_cost + if self.has_verify_form { 0 } else { 1 }, has_verify_form: false, ops_count_static: self.ops_count_static + verify_cost, @@ -225,7 +195,6 @@ impl Property for ExtData { fn cast_nonzero(self) -> Result { Ok(ExtData { - legacy_safe: self.legacy_safe, pk_cost: self.pk_cost + 4, has_verify_form: false, ops_count_static: self.ops_count_static + 4, @@ -236,7 +205,6 @@ impl Property for ExtData { fn cast_zeronotequal(self) -> Result { Ok(ExtData { - legacy_safe: self.legacy_safe, pk_cost: self.pk_cost + 1, has_verify_form: false, ops_count_static: self.ops_count_static + 1, @@ -247,7 +215,6 @@ impl Property for ExtData { fn cast_true(self) -> Result { Ok(ExtData { - legacy_safe: self.legacy_safe, pk_cost: self.pk_cost + 1, has_verify_form: false, ops_count_static: self.ops_count_static, @@ -263,7 +230,6 @@ impl Property for ExtData { fn cast_unlikely(self) -> Result { Ok(ExtData { - legacy_safe: self.legacy_safe, pk_cost: self.pk_cost + 4, has_verify_form: false, ops_count_static: self.ops_count_static + 3, @@ -274,7 +240,6 @@ impl Property for ExtData { fn cast_likely(self) -> Result { Ok(ExtData { - legacy_safe: self.legacy_safe, pk_cost: self.pk_cost + 4, has_verify_form: false, ops_count_static: self.ops_count_static + 3, @@ -285,7 +250,6 @@ impl Property for ExtData { fn and_b(l: Self, r: Self) -> Result { Ok(ExtData { - legacy_safe: legacy_safe2(l.legacy_safe, r.legacy_safe), pk_cost: l.pk_cost + r.pk_cost + 1, has_verify_form: false, ops_count_static: l.ops_count_static + r.ops_count_static + 1, @@ -300,7 +264,6 @@ impl Property for ExtData { fn and_v(l: Self, r: Self) -> Result { Ok(ExtData { - legacy_safe: legacy_safe2(l.legacy_safe, r.legacy_safe), pk_cost: l.pk_cost + r.pk_cost, has_verify_form: r.has_verify_form, ops_count_static: l.ops_count_static + r.ops_count_static, @@ -311,7 +274,6 @@ impl Property for ExtData { fn or_b(l: Self, r: Self) -> Result { Ok(ExtData { - legacy_safe: legacy_safe2(l.legacy_safe, r.legacy_safe), pk_cost: l.pk_cost + r.pk_cost + 1, has_verify_form: false, ops_count_static: l.ops_count_static + r.ops_count_static + 1, @@ -329,7 +291,6 @@ impl Property for ExtData { fn or_d(l: Self, r: Self) -> Result { Ok(ExtData { - legacy_safe: LegacySafe::SegwitOnly, pk_cost: l.pk_cost + r.pk_cost + 3, has_verify_form: false, ops_count_static: l.ops_count_static + r.ops_count_static + 1, @@ -346,7 +307,6 @@ impl Property for ExtData { fn or_c(l: Self, r: Self) -> Result { Ok(ExtData { - legacy_safe: legacy_safe2(l.legacy_safe, r.legacy_safe), pk_cost: l.pk_cost + r.pk_cost + 2, has_verify_form: false, ops_count_static: l.ops_count_static + r.ops_count_static + 2, @@ -361,7 +321,6 @@ impl Property for ExtData { fn or_i(l: Self, r: Self) -> Result { Ok(ExtData { - legacy_safe: legacy_safe2(l.legacy_safe, r.legacy_safe), pk_cost: l.pk_cost + r.pk_cost + 3, has_verify_form: false, ops_count_static: l.ops_count_static + r.ops_count_static + 3, @@ -379,7 +338,6 @@ impl Property for ExtData { fn and_or(a: Self, b: Self, c: Self) -> Result { Ok(ExtData { - legacy_safe: legacy_safe2(legacy_safe2(a.legacy_safe, b.legacy_safe), c.legacy_safe), pk_cost: a.pk_cost + b.pk_cost + c.pk_cost + 3, has_verify_form: false, ops_count_static: a.ops_count_static + b.ops_count_static + c.ops_count_static + 3, @@ -400,7 +358,6 @@ impl Property for ExtData { S: FnMut(usize) -> Result, { let mut pk_cost = 1 + script_num_size(k); //Equal and k - let mut legacy_safe = LegacySafe::LegacySafe; let mut ops_count_static = 0 as usize; let mut ops_count_sat_vec = Vec::with_capacity(n); let mut ops_count_nsat_sum = 0 as usize; @@ -424,7 +381,6 @@ impl Property for ExtData { } _ => {} } - legacy_safe = legacy_safe2(legacy_safe, sub.legacy_safe); } let remaining_sat = k - sat_count; let mut sum: i32 = 0; @@ -440,7 +396,6 @@ impl Property for ExtData { .sum(); } Ok(ExtData { - legacy_safe: legacy_safe, pk_cost: pk_cost + n - 1, //all pk cost + (n-1)*ADD has_verify_form: true, ops_count_static: ops_count_static + (n - 1) + 1, //adds and equal @@ -452,9 +407,13 @@ impl Property for ExtData { /// Compute the type of a fragment assuming all the children of /// Miniscript have been computed already. - fn type_check(fragment: &Terminal, _child: C) -> Result> + fn type_check( + fragment: &Terminal, + _child: C, + ) -> Result> where C: FnMut(usize) -> Option, + Ctx: ScriptContext, Pk: MiniscriptKey, { let wrap_err = |result: Result| { @@ -578,10 +537,3 @@ impl Property for ExtData { ret } } - -fn legacy_safe2(a: LegacySafe, b: LegacySafe) -> LegacySafe { - match (a, b) { - (LegacySafe::LegacySafe, LegacySafe::LegacySafe) => LegacySafe::LegacySafe, - _ => LegacySafe::SegwitOnly, - } -} diff --git a/src/miniscript/types/mod.rs b/src/miniscript/types/mod.rs index 3cba9459e..77040818d 100644 --- a/src/miniscript/types/mod.rs +++ b/src/miniscript/types/mod.rs @@ -21,6 +21,7 @@ use std::{error, fmt}; pub use self::correctness::{Base, Correctness, Input}; pub use self::extra_props::ExtData; pub use self::malleability::{Dissat, Malleability}; +use super::ScriptContext; use MiniscriptKey; use Terminal; @@ -84,14 +85,14 @@ pub enum ErrorKind { } #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct Error { +pub struct Error { /// The fragment that failed typecheck - pub fragment: Terminal, + pub fragment: Terminal, /// The reason that typechecking failed pub error: ErrorKind, } -impl error::Error for Error { +impl error::Error for Error { fn cause(&self) -> Option<&error::Error> { None } @@ -101,7 +102,7 @@ impl error::Error for Error { } } -impl fmt::Display for Error { +impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.error { ErrorKind::ZeroTime => write!( @@ -370,10 +371,14 @@ pub trait Property: Sized { /// Compute the type of a fragment, given a function to look up /// the types of its children, if available and relevant for the /// given fragment - fn type_check(fragment: &Terminal, mut child: C) -> Result> + fn type_check( + fragment: &Terminal, + mut child: C, + ) -> Result> where C: FnMut(usize) -> Option, Pk: MiniscriptKey, + Ctx: ScriptContext, { let mut get_child = |sub, n| { child(n) @@ -748,10 +753,14 @@ impl Property for Type { /// Compute the type of a fragment assuming all the children of /// Miniscript have been computed already. - fn type_check(fragment: &Terminal, _child: C) -> Result> + fn type_check( + fragment: &Terminal, + _child: C, + ) -> Result> where C: FnMut(usize) -> Option, Pk: MiniscriptKey, + Ctx: ScriptContext, { let wrap_err = |result: Result| { result.map_err(|kind| Error { diff --git a/src/policy/compiler.rs b/src/policy/compiler.rs index 16f694b13..b28cc03f5 100644 --- a/src/policy/compiler.rs +++ b/src/policy/compiler.rs @@ -19,10 +19,12 @@ use std::collections::HashMap; use std::convert::From; +use std::marker::PhantomData; use std::{cmp, error, f64, fmt}; use miniscript::types::extra_props::MAX_OPS_PER_SCRIPT; use miniscript::types::{self, ErrorKind, ExtData, Property, Type}; +use miniscript::ScriptContext; use policy::Concrete; use std::collections::vec_deque::VecDeque; use std::hash; @@ -30,8 +32,8 @@ use std::sync::Arc; use {policy, Terminal}; use {Miniscript, MiniscriptKey}; -type PolicyCache = - HashMap<(Concrete, OrdF64, Option), HashMap>>; +type PolicyCache = + HashMap<(Concrete, OrdF64, Option), HashMap>>; ///Ordered f64 for comparison #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] @@ -437,14 +439,14 @@ impl Property for CompilerExtData { /// Miniscript AST fragment with additional data needed by the compiler #[derive(Clone, Debug)] -struct AstElemExt { +struct AstElemExt { /// The actual Miniscript fragment with type information - ms: Arc>, + ms: Arc>, /// Its "type" in terms of compiler data comp_ext_data: CompilerExtData, } -impl AstElemExt { +impl AstElemExt { /// Compute a 1-dimensional cost, given a probability of satisfaction /// and a probability of dissatisfaction; if `dissat_prob` is `None` /// then it is assumed that dissatisfaction never occurs @@ -460,8 +462,8 @@ impl AstElemExt { } } -impl AstElemExt { - fn terminal(ast: Terminal) -> AstElemExt { +impl AstElemExt { + fn terminal(ast: Terminal) -> AstElemExt { AstElemExt { comp_ext_data: CompilerExtData::type_check(&ast, |_| None).unwrap(), ms: Arc::new(Miniscript::from_ast(ast).expect("Terminal creation must always succeed")), @@ -469,10 +471,10 @@ impl AstElemExt { } fn binary( - ast: Terminal, - l: &AstElemExt, - r: &AstElemExt, - ) -> Result, types::Error> { + ast: Terminal, + l: &AstElemExt, + r: &AstElemExt, + ) -> Result, types::Error> { let lookup_ext = |n| match n { 0 => Some(l.comp_ext_data), 1 => Some(r.comp_ext_data), @@ -484,17 +486,22 @@ impl AstElemExt { let ext = types::ExtData::type_check(&ast, |_| None)?; let comp_ext_data = CompilerExtData::type_check(&ast, lookup_ext)?; Ok(AstElemExt { - ms: Arc::new(Miniscript { ty, ext, node: ast }), + ms: Arc::new(Miniscript { + ty, + ext, + node: ast, + phantom: PhantomData, + }), comp_ext_data, }) } fn ternary( - ast: Terminal, - a: &AstElemExt, - b: &AstElemExt, - c: &AstElemExt, - ) -> Result, types::Error> { + ast: Terminal, + a: &AstElemExt, + b: &AstElemExt, + c: &AstElemExt, + ) -> Result, types::Error> { let lookup_ext = |n| match n { 0 => Some(a.comp_ext_data), 1 => Some(b.comp_ext_data), @@ -511,6 +518,7 @@ impl AstElemExt { ty: ty, ext: ext, node: ast, + phantom: PhantomData, }), comp_ext_data, }) @@ -519,27 +527,28 @@ impl AstElemExt { /// Different types of casts possible for each node. #[derive(Copy, Clone)] -struct Cast { - node: fn(Arc>) -> Terminal, +struct Cast { + node: fn(Arc>) -> Terminal, ast_type: fn(types::Type) -> Result, ext_data: fn(types::ExtData) -> Result, comp_ext_data: fn(CompilerExtData) -> Result, } -impl Cast { - fn cast(&self, ast: &AstElemExt) -> Result, ErrorKind> { +impl Cast { + fn cast(&self, ast: &AstElemExt) -> Result, ErrorKind> { Ok(AstElemExt { ms: Arc::new(Miniscript { ty: (self.ast_type)(ast.ms.ty)?, ext: (self.ext_data)(ast.ms.ext)?, node: (self.node)(Arc::clone(&ast.ms)), + phantom: PhantomData, }), comp_ext_data: (self.comp_ext_data)(ast.comp_ext_data)?, }) } } -fn all_casts() -> [Cast; 10] { +fn all_casts() -> [Cast; 10] { [ Cast { ext_data: types::ExtData::cast_check, @@ -633,17 +642,18 @@ fn all_casts() -> [Cast; 10] { /// the map. /// In general, we maintain the invariant that if anything is inserted into the /// map, it's cast closure must also be considered for best compilations. -fn insert_elem( - map: &mut HashMap>, - elem: AstElemExt, +fn insert_elem( + map: &mut HashMap>, + elem: AstElemExt, sat_prob: f64, dissat_prob: Option, ) -> bool { - // return malleable types directly. If a elem is malleable, all the casts - // to it are also going to be malleable - if !elem.ms.ty.mall.non_malleable { + // return malleable types directly. If a elem is malleable under current context, + // all the casts to it are also going to be malleable + if !elem.ms.ty.mall.non_malleable && Ctx::check_frag_non_malleable(&elem.ms.node).is_ok() { return false; } + if let Some(op_count) = elem.ms.ext.ops_count_sat { if op_count > MAX_OPS_PER_SCRIPT { return false; @@ -684,18 +694,18 @@ fn insert_elem( /// At the start and end of this function, we maintain that the invariant that /// all map is smallest possible closure of all compilations of a policy with /// given sat and dissat probabilities. -fn insert_elem_closure( - map: &mut HashMap>, - astelem_ext: AstElemExt, +fn insert_elem_closure( + map: &mut HashMap>, + astelem_ext: AstElemExt, sat_prob: f64, dissat_prob: Option, ) { - let mut cast_stack: VecDeque> = VecDeque::new(); + let mut cast_stack: VecDeque> = VecDeque::new(); if insert_elem(map, astelem_ext.clone(), sat_prob, dissat_prob) { cast_stack.push_back(astelem_ext); } - let casts: [Cast; 10] = all_casts::(); + let casts: [Cast; 10] = all_casts::(); while !cast_stack.is_empty() { let current = cast_stack.pop_front().unwrap(); @@ -718,18 +728,18 @@ fn insert_elem_closure( /// given that it may be not be necessary to dissatisfy. For these elements, we /// apply the wrappers around the element once and bring them into the same /// dissat probability map and get their closure. -fn insert_best_wrapped( - policy_cache: &mut PolicyCache, +fn insert_best_wrapped( + policy_cache: &mut PolicyCache, policy: &Concrete, - map: &mut HashMap>, - data: AstElemExt, + map: &mut HashMap>, + data: AstElemExt, sat_prob: f64, dissat_prob: Option, ) -> Result<(), CompilerError> { insert_elem_closure(map, data, sat_prob, dissat_prob); if dissat_prob.is_some() { - let casts: [Cast; 10] = all_casts::(); + let casts: [Cast; 10] = all_casts::(); for i in 0..casts.len() { for x in best_compilations(policy_cache, policy, sat_prob, None)?.values() { @@ -744,14 +754,15 @@ fn insert_best_wrapped( /// Get the best compilations of a policy with a given sat and dissat /// probabilities. This functions caches the results into a global policy cache. -fn best_compilations( - policy_cache: &mut PolicyCache, +fn best_compilations( + policy_cache: &mut PolicyCache, policy: &Concrete, sat_prob: f64, dissat_prob: Option, -) -> Result>, CompilerError> +) -> Result>, CompilerError> where Pk: MiniscriptKey, + Ctx: ScriptContext, { //Check the cache for hits let ord_sat_prob = OrdF64(sat_prob); @@ -1006,12 +1017,12 @@ where /// Helper function to compile different types of binary fragments. /// `sat_prob` and `dissat_prob` represent the sat and dissat probabilities of /// root or. `weights` represent the odds for taking each sub branch -fn compile_binary( - policy_cache: &mut PolicyCache, +fn compile_binary( + policy_cache: &mut PolicyCache, policy: &Concrete, - ret: &mut HashMap>, - left_comp: &mut HashMap>, - right_comp: &mut HashMap>, + ret: &mut HashMap>, + left_comp: &mut HashMap>, + right_comp: &mut HashMap>, weights: [f64; 2], sat_prob: f64, dissat_prob: Option, @@ -1019,7 +1030,8 @@ fn compile_binary( ) -> Result<(), CompilerError> where Pk: MiniscriptKey, - F: Fn(Arc>, Arc>) -> Terminal, + Ctx: ScriptContext, + F: Fn(Arc>, Arc>) -> Terminal, { for l in left_comp.values_mut() { let lref = Arc::clone(&l.ms); @@ -1039,13 +1051,13 @@ where /// Helper function to compile different order of and_or fragments. /// `sat_prob` and `dissat_prob` represent the sat and dissat probabilities of /// root and_or node. `weights` represent the odds for taking each sub branch -fn compile_tern( - policy_cache: &mut PolicyCache, +fn compile_tern( + policy_cache: &mut PolicyCache, policy: &Concrete, - ret: &mut HashMap>, - a_comp: &mut HashMap>, - b_comp: &mut HashMap>, - c_comp: &mut HashMap>, + ret: &mut HashMap>, + a_comp: &mut HashMap>, + b_comp: &mut HashMap>, + c_comp: &mut HashMap>, weights: [f64; 2], sat_prob: f64, dissat_prob: Option, @@ -1070,10 +1082,10 @@ fn compile_tern( } /// Obtain the best compilation of for p=1.0 and q=0 -pub fn best_compilation( +pub fn best_compilation( policy: &Concrete, -) -> Result, CompilerError> { - let mut policy_cache = PolicyCache::::new(); +) -> Result, CompilerError> { + let mut policy_cache = PolicyCache::::new(); let x = &*best_t(&mut policy_cache, policy, 1.0, None)?.ms; if !x.ty.mall.safe { Err(CompilerError::TopLevelNonSafe) @@ -1085,14 +1097,15 @@ pub fn best_compilation( } /// Obtain the best B expression with given sat and dissat -fn best_t( - policy_cache: &mut PolicyCache, +fn best_t( + policy_cache: &mut PolicyCache, policy: &Concrete, sat_prob: f64, dissat_prob: Option, -) -> Result, CompilerError> +) -> Result, CompilerError> where Pk: MiniscriptKey, + Ctx: ScriptContext, { best_compilations(policy_cache, policy, sat_prob, dissat_prob)? .into_iter() @@ -1106,15 +1119,16 @@ where } /// Obtain the .deu (e.g. W.deu, B.deu) expression with the given sat and dissat -fn best( +fn best( basic_type: types::Base, - policy_cache: &mut PolicyCache, + policy_cache: &mut PolicyCache, policy: &Concrete, sat_prob: f64, dissat_prob: Option, -) -> Result, CompilerError> +) -> Result, CompilerError> where Pk: MiniscriptKey, + Ctx: ScriptContext, { best_compilations(policy_cache, policy, sat_prob, dissat_prob)? .into_iter() @@ -1135,8 +1149,9 @@ mod tests { use bitcoin::blockdata::{opcodes, script}; use bitcoin::{self, hashes, secp256k1, SigHashType}; use std::str::FromStr; + use std::string::String; - use miniscript::satisfy; + use miniscript::{satisfy, Segwitv0}; use policy::Liftable; use BitcoinSig; use DummyKey; @@ -1144,6 +1159,8 @@ mod tests { type SPolicy = Concrete; type DummyPolicy = Concrete; type BPolicy = Concrete; + type DummySegwitAstElemExt = policy::compiler::AstElemExt; + type SegwitMiniScript = Miniscript; fn pubkeys_and_a_sig(n: usize) -> (Vec, secp256k1::Signature) { let mut ret = Vec::with_capacity(n); @@ -1171,8 +1188,8 @@ mod tests { } fn policy_compile_lift_check(s: &str) -> Result<(), CompilerError> { - let policy = DummyPolicy::from_str(s).unwrap(); - let miniscript = policy.compile()?; + let policy = DummyPolicy::from_str(s).expect("parse"); + let miniscript: Miniscript = policy.compile()?; assert_eq!(policy.lift().sorted(), miniscript.lift().sorted()); Ok(()) @@ -1213,7 +1230,8 @@ mod tests { #[test] fn compile_q() { let policy = SPolicy::from_str("or(1@and(pk(),pk()),127@pk())").expect("parsing"); - let compilation = best_t(&mut HashMap::new(), &policy, 1.0, None).unwrap(); + let compilation: DummySegwitAstElemExt = + best_t(&mut HashMap::new(), &policy, 1.0, None).unwrap(); assert_eq!(compilation.cost_1d(1.0, None), 88.0 + 74.109375); assert_eq!(policy.lift().sorted(), compilation.ms.lift().sorted()); @@ -1221,7 +1239,8 @@ mod tests { let policy = SPolicy::from_str( "and(and(and(or(127@thresh(2,pk(),pk(),thresh(2,or(127@pk(),1@pk()),after(100),or(and(pk(),after(200)),and(pk(),sha256(66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925))),pk())),1@pk()),sha256(66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925)),or(127@pk(),1@after(300))),or(127@after(400),pk()))" ).expect("parsing"); - let compilation = best_t(&mut HashMap::new(), &policy, 1.0, None).unwrap(); + let compilation: DummySegwitAstElemExt = + best_t(&mut HashMap::new(), &policy, 1.0, None).unwrap(); assert_eq!(compilation.cost_1d(1.0, None), 437.0 + 299.4003295898438); assert_eq!(policy.lift().sorted(), compilation.ms.lift().sorted()); @@ -1233,9 +1252,9 @@ mod tests { let key_pol: Vec = keys.iter().map(|k| Concrete::Key(*k)).collect(); let policy: BPolicy = Concrete::Key(keys[0].clone()); - let desc = policy.compile().unwrap(); + let ms: SegwitMiniScript = policy.compile().unwrap(); assert_eq!( - desc.encode(), + ms.encode(), script::Builder::new() .push_key(&keys[0]) .push_opcode(opcodes::all::OP_CHECKSIG) @@ -1249,9 +1268,9 @@ mod tests { keys[6], keys[7] ); - let desc = policy.compile().unwrap(); + let ms: SegwitMiniScript = policy.compile().unwrap(); assert_eq!( - desc.encode(), + ms.encode(), script::Builder::new() .push_opcode(opcodes::all::OP_PUSHNUM_2) .push_key(&keys[5]) @@ -1276,9 +1295,9 @@ mod tests { ), ]); - let desc = policy.compile().unwrap(); + let ms: SegwitMiniScript = policy.compile().unwrap(); - let ms: Miniscript = ms_str!( + let ms_comp_res: Miniscript = ms_str!( "or_d(multi(3,{},{},{},{},{}),\ and_v(v:thresh(2,c:pk_h({}),\ ac:pk_h({}),ac:pk_h({})),older(10000)))", @@ -1292,7 +1311,7 @@ mod tests { keys[7].to_pubkeyhash() ); - assert_eq!(desc, ms); + assert_eq!(ms, ms_comp_res); let mut abs = policy.lift(); assert_eq!(abs.n_keys(), 8); @@ -1323,14 +1342,14 @@ mod tests { right_sat.insert(keys[i].to_pubkeyhash(), (keys[i], bitcoinsig)); } - assert!(desc.satisfy(no_sat).is_none()); - assert!(desc.satisfy(&left_sat).is_some()); - assert!(desc.satisfy((&right_sat, satisfy::Older(10001))).is_some()); + assert!(ms.satisfy(no_sat).is_none()); + assert!(ms.satisfy(&left_sat).is_some()); + assert!(ms.satisfy((&right_sat, satisfy::Older(10001))).is_some()); //timelock not met - assert!(desc.satisfy((&right_sat, satisfy::Older(9999))).is_none()); + assert!(ms.satisfy((&right_sat, satisfy::Older(9999))).is_none()); assert_eq!( - desc.satisfy((left_sat, satisfy::Older(9999))).unwrap(), + ms.satisfy((left_sat, satisfy::Older(9999))).unwrap(), vec![ // sat for left branch vec![], @@ -1341,7 +1360,7 @@ mod tests { ); assert_eq!( - desc.satisfy((right_sat, satisfy::Older(10000))).unwrap(), + ms.satisfy((right_sat, satisfy::Older(10000))).unwrap(), vec![ // sat for right branch vec![], @@ -1365,18 +1384,21 @@ mod benches { use std::str::FromStr; use test::{black_box, Bencher}; - use super::Concrete; + use super::{CompilerError, Concrete}; + use miniscript::Segwitv0; use DummyKey; + use Miniscript; + type DummySegwitMiniscriptRes = Result, CompilerError>; #[bench] - pub fn compile(bh: &mut Bencher) { + pub fn compile_basic(bh: &mut Bencher) { let h = (0..64).map(|_| "a").collect::(); - let desc = Concrete::::from_str(&format!( + let pol = Concrete::::from_str(&format!( "and(thresh(2,and(sha256({}),or(sha256({}),pk())),pk(),pk(),pk(),sha256({})),pk())", h, h, h )) .expect("parsing"); bh.iter(|| { - let pt = desc.compile(); + let pt: DummySegwitMiniscriptRes = pol.compile(); black_box(pt).unwrap(); }); } @@ -1384,22 +1406,22 @@ mod benches { #[bench] pub fn compile_large(bh: &mut Bencher) { let h = (0..64).map(|_| "a").collect::(); - let desc = Concrete::::from_str( + let pol = Concrete::::from_str( &format!("or(pk(),thresh(9,sha256({}),pk(),pk(),and(or(pk(),pk()),pk()),after(100),pk(),pk(),pk(),pk(),and(pk(),pk())))", h) ).expect("parsing"); bh.iter(|| { - let pt = desc.compile(); + let pt: DummySegwitMiniscriptRes = pol.compile(); black_box(pt).unwrap(); }); } #[bench] pub fn compile_xlarge(bh: &mut Bencher) { - let desc = Concrete::::from_str( + let pol = Concrete::::from_str( "or(pk(),thresh(4,pk(),older(100),pk(),and(after(100),or(pk(),or(pk(),and(pk(),thresh(2,pk(),or(pk(),and(thresh(5,pk(),or(pk(),pk()),pk(),pk(),pk(),pk(),pk(),pk(),pk(),pk(),pk()),pk())),pk(),or(and(pk(),pk()),pk()),after(100)))))),pk()))" ).expect("parsing"); bh.iter(|| { - let pt = desc.compile(); + let pt: DummySegwitMiniscriptRes = pol.compile(); black_box(pt).unwrap(); }); } diff --git a/src/policy/concrete.rs b/src/policy/concrete.rs index 2e23d5d09..0f82f312c 100644 --- a/src/policy/concrete.rs +++ b/src/policy/concrete.rs @@ -22,6 +22,8 @@ use std::{error, fmt, str}; use errstr; use expression::{self, FromTree}; #[cfg(feature = "compiler")] +use miniscript::ScriptContext; +#[cfg(feature = "compiler")] use policy::compiler; #[cfg(feature = "compiler")] use policy::compiler::CompilerError; @@ -102,7 +104,7 @@ impl fmt::Display for PolicyError { impl Policy { /// Compile the descriptor into an optimized `Miniscript` representation #[cfg(feature = "compiler")] - pub fn compile(&self) -> Result, CompilerError> { + pub fn compile(&self) -> Result, CompilerError> { self.is_valid()?; match self.is_safe_nonmalleable() { (false, _) => Err(CompilerError::TopLevelNonSafe), diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 03eab78d1..68b486e4d 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -28,7 +28,7 @@ pub mod concrete; pub mod semantic; use descriptor::Descriptor; -use miniscript::Miniscript; +use miniscript::{Miniscript, ScriptContext}; use Terminal; pub use self::concrete::Policy as Concrete; @@ -47,13 +47,13 @@ pub trait Liftable { fn lift(&self) -> Semantic; } -impl Liftable for Miniscript { +impl Liftable for Miniscript { fn lift(&self) -> Semantic { self.as_inner().lift() } } -impl Liftable for Terminal { +impl Liftable for Terminal { fn lift(&self) -> Semantic { match *self { Terminal::PkK(ref pk) => Semantic::KeyHash(pk.to_pubkeyhash()), @@ -103,10 +103,8 @@ impl Liftable for Terminal { impl Liftable for Descriptor { fn lift(&self) -> Semantic { match *self { - Descriptor::Bare(ref d) - | Descriptor::Sh(ref d) - | Descriptor::Wsh(ref d) - | Descriptor::ShWsh(ref d) => d.node.lift(), + Descriptor::Bare(ref d) | Descriptor::Sh(ref d) => d.node.lift(), + Descriptor::Wsh(ref d) | Descriptor::ShWsh(ref d) => d.node.lift(), Descriptor::Pk(ref p) | Descriptor::Pkh(ref p) | Descriptor::Wpkh(ref p) diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index 01392bb00..544398b31 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -25,6 +25,7 @@ use bitcoin::util::psbt; use bitcoin::util::psbt::PartiallySignedTransaction as Psbt; use bitcoin::{self, secp256k1}; +use miniscript::{Legacy, Segwitv0}; use BitcoinSig; use Miniscript; use Satisfier; @@ -160,8 +161,13 @@ pub fn finalize(psbt: &mut Psbt) -> Result<(), super::Error> { // Actually construct the witnesses for (n, input) in psbt.inputs.iter_mut().enumerate() { + // Only one of PSBT redeem script or witness script must be set in the + // PSBT input if let Some(script) = input.witness_script.as_ref() { - let miniscript = Miniscript::parse(script)?; + let miniscript = Miniscript::<_, Segwitv0>::parse(script)?; + input.final_script_witness = miniscript.satisfy(&*input); + } else if let Some(script) = input.redeem_script.as_ref() { + let miniscript = Miniscript::<_, Legacy>::parse(script)?; input.final_script_witness = miniscript.satisfy(&*input); } else { return Err(Error::MissingWitnessScript(n).into());