From 6a717bf9eba0bb8b1d7e4d8f05859ee2642b6b4f Mon Sep 17 00:00:00 2001 From: SarcasticNastik Date: Tue, 17 Aug 2021 11:35:15 +0530 Subject: [PATCH 01/12] Implement Taproot descriptor tree parsing fixups to Tr descriptor code from SarcasticNastik --- src/descriptor/mod.rs | 3 + src/descriptor/tr.rs | 262 +++++++++++++++++++++++++++++++++++++++ src/expression.rs | 102 ++++++++++++++- src/miniscript/limits.rs | 3 + 4 files changed, 366 insertions(+), 4 deletions(-) create mode 100644 src/descriptor/tr.rs diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index eb74eef2b..1f0cefdde 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -46,14 +46,17 @@ mod bare; mod segwitv0; mod sh; mod sortedmulti; +mod tr; // Descriptor Exports pub use self::bare::{Bare, Pkh}; pub use self::segwitv0::{Wpkh, Wsh, WshInner}; pub use self::sh::{Sh, ShInner}; pub use self::sortedmulti::SortedMultiVec; +pub use self::tr::{TapTree, Tr}; mod checksum; mod key; + pub use self::key::{ ConversionError, DescriptorKeyParseError, DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorSinglePub, DescriptorXKey, InnerXKey, Wildcard, diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs new file mode 100644 index 000000000..3b945692d --- /dev/null +++ b/src/descriptor/tr.rs @@ -0,0 +1,262 @@ +// Tapscript + +use super::checksum::{desc_checksum, verify_checksum}; +use errstr; +use expression::{self, FromTree, Tree}; +use miniscript::{limits::TAPROOT_MAX_NODE_COUNT, Miniscript}; +use std::cmp::max; +use std::sync::Arc; +use std::{fmt, str::FromStr}; +use Tap; +use {Error, MiniscriptKey}; + +/// A Taproot Tree representation. +// Hidden leaves are not yet supported in descriptor spec. Conceptually, it should +// be simple to integrate those here, but it is best to wait on core for the exact syntax. +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub enum TapTree { + /// A taproot tree structure + Tree(Arc>, Arc>), + /// A taproot leaf denoting a spending condition + Leaf(Arc>), +} + +/// A taproot descriptor +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub struct Tr { + /// A taproot internal key + internal_key: Pk, + /// Optional Taproot Tree with spending conditions + tree: Option>, +} + +impl TapTree { + // Helper function to compute height + // TODO: Instead of computing this every time we add a new leaf, we should + // add height as a separate field in taptree + fn taptree_height(&self) -> usize { + match *self { + TapTree::Tree(ref left_tree, ref right_tree) => { + 1 + max(left_tree.taptree_height(), right_tree.taptree_height()) + } + TapTree::Leaf(_) => 1, + } + } +} + +impl fmt::Display for TapTree { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TapTree::Tree(ref left, ref right) => write!(f, "{{{},{}}}", *left, *right), + TapTree::Leaf(ref script) => write!(f, "{}", *script), + } + } +} + +impl Tr { + /// Create a new [`Tr`] descriptor from internal key and [`TapTree`] + pub fn new(internal_key: Pk, tree: Option>) -> Result { + let nodes = tree.as_ref().map(|t| t.taptree_height()).unwrap_or(0); + + if nodes <= TAPROOT_MAX_NODE_COUNT { + Ok(Self { internal_key, tree }) + } else { + Err(Error::MaxRecursiveDepthExceeded) + } + } + + fn to_string_no_checksum(&self) -> String { + let key = &self.internal_key; + match self.tree { + Some(ref s) => format!("tr({},{})", key, s), + None => format!("tr({})", key), + } + } + + /// Obtain the internal key of [`Tr`] descriptor + pub fn internal_key(&self) -> &Pk { + &self.internal_key + } + + /// Obtain the [`TapTree`] of the [`Tr`] descriptor + pub fn taptree(&self) -> &Option> { + &self.tree + } +} + +impl FromTree for Tr +where + Pk: MiniscriptKey + FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + fn from_tree(top: &Tree) -> Result { + // Helper function to parse taproot script path + fn parse_tr_script_spend(tree: &Tree) -> Result, Error> + where + Pk: MiniscriptKey + FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, + { + match tree { + Tree { name, args } if name.len() > 0 && args.len() == 0 => { + let script = Miniscript::::from_str(name)?; + Ok(TapTree::Leaf(Arc::new(script))) + } + Tree { name, args } if name.len() == 0 && args.len() == 2 => { + let left = parse_tr_script_spend(&args[0])?; + let right = parse_tr_script_spend(&args[1])?; + Ok(TapTree::Tree(Arc::new(left), Arc::new(right))) + } + _ => { + return Err(Error::Unexpected( + "unknown format for script spending paths while parsing taproot descriptor" + .to_string(), + )); + } + } + } + + if top.name == "tr" { + match top.args.len() { + 1 => { + let key = &top.args[0]; + if key.args.len() > 0 { + return Err(Error::Unexpected(format!( + "#{} script associated with `key-path` while parsing taproot descriptor", + key.args.len() + ))); + } + Ok(Tr { + internal_key: expression::terminal(key, Pk::from_str)?, + tree: None, + }) + } + 2 => { + let ref key = top.args[0]; + if key.args.len() > 0 { + return Err(Error::Unexpected(format!( + "#{} script associated with `key-path` while parsing taproot descriptor", + key.args.len() + ))); + } + let ref tree = top.args[1]; + let ret = parse_tr_script_spend(tree)?; + Ok(Tr { + internal_key: expression::terminal(key, Pk::from_str)?, + tree: Some(ret), + }) + } + _ => { + return Err(Error::Unexpected(format!( + "{}[#{} args] while parsing taproot descriptor", + top.name, + top.args.len() + ))); + } + } + } else { + return Err(Error::Unexpected(format!( + "{}[#{} args] while parsing taproot descriptor", + top.name, + top.args.len() + ))); + } + } +} + +impl FromStr for Tr +where + Pk: MiniscriptKey + FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + type Err = Error; + + fn from_str(s: &str) -> Result { + let desc_str = verify_checksum(s)?; + let top = parse_tr_tree(desc_str)?; + Self::from_tree(&top) + } +} + +impl fmt::Display for Tr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let desc = self.to_string_no_checksum(); + let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?; + write!(f, "{}#{}", &desc, &checksum) + } +} + +// Helper function to parse string into miniscript tree form +fn parse_tr_tree(s: &str) -> Result { + for ch in s.bytes() { + if ch > 0x7f { + return Err(Error::Unprintable(ch)); + } + } + + let ret = if s.len() > 3 && &s[..3] == "tr(" && s.as_bytes()[s.len() - 1] == b')' { + let rest = &s[3..s.len() - 1]; + if !rest.contains(',') { + let internal_key = Tree { + name: rest, + args: vec![], + }; + return Ok(Tree { + name: "tr", + args: vec![internal_key], + }); + } + // use str::split_once() method to refactor this when compiler version bumps up + let (key, script) = split_once(rest, ',') + .ok_or_else(|| Error::BadDescriptor("invalid taproot descriptor".to_string()))?; + + let internal_key = Tree { + name: key, + args: vec![], + }; + if script.is_empty() { + return Ok(Tree { + name: "tr", + args: vec![internal_key], + }); + } + let (tree, rest) = expression::Tree::from_slice_helper_curly(script, 1)?; + if rest.is_empty() { + Ok(Tree { + name: "tr", + args: vec![internal_key, tree], + }) + } else { + Err(errstr(rest)) + } + } else { + Err(Error::Unexpected("invalid taproot descriptor".to_string())) + }; + return ret; +} + +fn split_once(inp: &str, delim: char) -> Option<(&str, &str)> { + let ret = if inp.len() == 0 { + None + } else { + let mut found = inp.len(); + for (idx, ch) in inp.chars().enumerate() { + if ch == delim { + found = idx; + break; + } + } + // No comma or trailing comma found + if found >= inp.len() - 1 { + Some((&inp[..], "")) + } else { + Some((&inp[..found], &inp[found + 1..])) + } + }; + return ret; +} diff --git a/src/expression.rs b/src/expression.rs index fd31862a0..1cef61409 100644 --- a/src/expression.rs +++ b/src/expression.rs @@ -38,11 +38,13 @@ pub trait FromTree: Sized { } impl<'a> Tree<'a> { - fn from_slice(sl: &'a str) -> Result<(Tree<'a>, &'a str), Error> { - Self::from_slice_helper(sl, 0u32) + /// Parse an expression with round brackets + pub fn from_slice(sl: &'a str) -> Result<(Tree<'a>, &'a str), Error> { + // Parsing TapTree or just miniscript + Self::from_slice_helper_round(sl, 0u32) } - fn from_slice_helper(mut sl: &'a str, depth: u32) -> Result<(Tree<'a>, &'a str), Error> { + fn from_slice_helper_round(mut sl: &'a str, depth: u32) -> Result<(Tree<'a>, &'a str), Error> { if depth >= MAX_RECURSION_DEPTH { return Err(Error::MaxRecursiveDepthExceeded); } @@ -98,7 +100,7 @@ impl<'a> Tree<'a> { sl = &sl[n + 1..]; loop { - let (arg, new_sl) = Tree::from_slice_helper(sl, depth + 1)?; + let (arg, new_sl) = Tree::from_slice_helper_round(sl, depth + 1)?; ret.args.push(arg); if new_sl.is_empty() { @@ -117,6 +119,95 @@ impl<'a> Tree<'a> { } } + // Helper function to parse expressions with curly braces + pub(crate) fn from_slice_helper_curly( + mut sl: &'a str, + depth: u32, + ) -> Result<(Tree<'a>, &'a str), Error> { + // contain the context of brackets + if depth >= MAX_RECURSION_DEPTH { + return Err(Error::MaxRecursiveDepthExceeded); + } + enum Found { + Nothing, + Lbrace(usize), + Comma(usize), + Rbrace(usize), + } + + let mut found = Found::Nothing; + let mut new_count = 0; + for (n, ch) in sl.char_indices() { + match ch { + '{' => { + found = Found::Lbrace(n); + break; + } + '(' => { + new_count += 1; + } + ',' => { + if new_count == 0 { + found = Found::Comma(n); + break; + } + } + ')' => { + new_count -= 1; + } + '}' => { + found = Found::Rbrace(n); + break; + } + _ => {} + } + } + + match found { + // String-ending terminal + Found::Nothing => Ok(( + Tree { + name: &sl[..], + args: vec![], + }, + "", + )), + // Terminal + Found::Comma(n) | Found::Rbrace(n) => Ok(( + Tree { + name: &sl[..n], + args: vec![], + }, + &sl[n..], + )), + // Function call + Found::Lbrace(n) => { + let mut ret = Tree { + name: &sl[..n], // Would be empty for left and right assignments + args: vec![], + }; + + sl = &sl[n + 1..]; + loop { + let (arg, new_sl) = Tree::from_slice_helper_curly(sl, depth + 1)?; + ret.args.push(arg); + + if new_sl.is_empty() { + return Err(Error::ExpectedChar('}')); + } + + sl = &new_sl[1..]; + match new_sl.as_bytes()[0] { + b',' => {} + b'}' => break, + _ => return Err(Error::ExpectedChar(',')), + } + } + Ok((ret, sl)) + } + } + } + /// Parses a tree from a string pub fn from_str(s: &'a str) -> Result, Error> { // Filter out non-ASCII because we byte-index strings all over the @@ -206,4 +297,7 @@ mod tests { assert!(parse_num("+6").is_err()); assert!(parse_num("-6").is_err()); } + + // Add tests for tapscript parsing + // tr(D,{or_i(pk(A),pk(B)),{after(9),pk(C)}}) } diff --git a/src/miniscript/limits.rs b/src/miniscript/limits.rs index 3b357afef..d018cd1a7 100644 --- a/src/miniscript/limits.rs +++ b/src/miniscript/limits.rs @@ -50,3 +50,6 @@ pub const MAX_BLOCK_WEIGHT: usize = 4000000; /// Maximum pubkeys as arguments to CHECKMULTISIG // https://github.com/bitcoin/bitcoin/blob/6acda4b00b3fc1bfac02f5de590e1a5386cbc779/src/script/script.h#L30 pub const MAX_PUBKEYS_PER_MULTISIG: usize = 20; +/// Maximum number of scripts allowed by standardness rules in TapTree format +// https://github.com/bitcoin/bitcoin/blob/81f4a3e84d6f30e7b12a9605dabc3359f614da93/src/script/interpreter.h#L229 +pub const TAPROOT_MAX_NODE_COUNT: usize = 128; From 81d1658d035a5843726210bbd00785d760772ed8 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Thu, 21 Oct 2021 17:36:22 -0700 Subject: [PATCH 02/12] Add roundtrip tests Co-authored-by: SarcasticNastik --- src/descriptor/mod.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 1f0cefdde..d4f24f139 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -651,6 +651,7 @@ serde_string_impl_pk!(Descriptor, "a script descriptor"); #[cfg(test)] mod tests { use super::checksum::desc_checksum; + use super::tr::Tr; use super::*; use bitcoin::blockdata::opcodes::all::{OP_CLTV, OP_CSV}; use bitcoin::blockdata::script::Instruction; @@ -1117,6 +1118,44 @@ mod tests { assert_eq!(check, &Ok(Instruction::Op(OP_CSV))) } + #[test] + fn tr_roundtrip_key() { + let script = Tr::::from_str("tr()").unwrap().to_string(); + assert_eq!(script, format!("tr()#x4ml3kxd")) + } + + #[test] + fn tr_roundtrip_script() { + let descriptor = Tr::::from_str("tr(,{pk(),pk()})") + .unwrap() + .to_string(); + + assert_eq!(descriptor, "tr(,{pk(),pk()})#7dqr6v8r") + } + + #[test] + fn tr_roundtrip_tree() { + let p1 = "020000000000000000000000000000000000000000000000000000000000000001"; + let p2 = "020000000000000000000000000000000000000000000000000000000000000002"; + let p3 = "020000000000000000000000000000000000000000000000000000000000000003"; + let p4 = "020000000000000000000000000000000000000000000000000000000000000004"; + let p5 = "f54a5851e9372b87810a8e60cdd2e7cfd80b6e31"; + let descriptor = Tr::::from_str(&format!( + "tr({},{{pk({}),{{pk({}),or_d(pk({}),pkh({}))}}}})", + p1, p2, p3, p4, p5 + )) + .unwrap() + .to_string(); + + assert_eq!( + descriptor, + format!( + "tr({},{{pk({}),{{pk({}),or_d(pk({}),pkh({}))}}}})#fdhmu4fj", + p1, p2, p3, p4, p5 + ) + ) + } + #[test] fn roundtrip_tests() { let descriptor = Descriptor::::from_str("multi"); From a0ef37f57fbd2f235ed7b0d81b082a29b21e99bb Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Fri, 22 Oct 2021 02:12:01 -0700 Subject: [PATCH 03/12] Add iterator for taptree --- src/descriptor/tr.rs | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 3b945692d..3cf03eb2c 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -13,7 +13,7 @@ use {Error, MiniscriptKey}; /// A Taproot Tree representation. // Hidden leaves are not yet supported in descriptor spec. Conceptually, it should // be simple to integrate those here, but it is best to wait on core for the exact syntax. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] pub enum TapTree { /// A taproot tree structure Tree(Arc>, Arc>), @@ -42,6 +42,11 @@ impl TapTree { TapTree::Leaf(_) => 1, } } + + /// Iterate over all miniscripts + pub fn iter(&self) -> TapTreeIter { + TapTreeIter { stack: vec![self] } + } } impl fmt::Display for TapTree { @@ -82,6 +87,43 @@ impl Tr { pub fn taptree(&self) -> &Option> { &self.tree } + + /// Iterate over all scripts in merkle tree. If there is no script path, the iterator + /// yields [`None`] + pub fn iter_scripts(&self) -> TapTreeIter { + match self.tree { + Some(ref t) => t.iter(), + None => TapTreeIter { stack: vec![] }, + } + } +} + +/// Iterator for Taproot structures +/// Yields the miniscript in a depth first walk +#[derive(Debug, Clone)] +pub struct TapTreeIter<'a, Pk: MiniscriptKey> where Pk: 'a { + stack: Vec<&'a TapTree>, +} + +impl<'a, Pk> Iterator for TapTreeIter<'a, Pk> +where + Pk: MiniscriptKey + 'a, +{ + type Item = &'a Miniscript; + + fn next(&mut self) -> Option { + while !self.stack.is_empty() { + let last = self.stack.pop().expect("Size checked above"); + match &*last { + TapTree::Tree(l, r) => { + self.stack.push(&r); + self.stack.push(&l); + } + TapTree::Leaf(ref ms) => return Some(ms), + } + } + None + } } impl FromTree for Tr From 74460e7e8b69cc6c83c13ec17ce23d6259b170c6 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Thu, 28 Oct 2021 06:28:18 -0700 Subject: [PATCH 04/12] Update descriptor trait --- examples/htlc.rs | 9 +-- examples/parse.rs | 31 +++++++++-- examples/sign_multisig.rs | 7 ++- src/descriptor/bare.rs | 70 ++++++++++++++++++++---- src/descriptor/mod.rs | 33 +++++++---- src/descriptor/segwitv0.rs | 109 +++++++++++++++++++++++++++---------- src/descriptor/sh.rs | 94 +++++++++++++++++++++++--------- 7 files changed, 263 insertions(+), 90 deletions(-) diff --git a/examples/htlc.rs b/examples/htlc.rs index 8bdce8dcd..727c7fc11 100644 --- a/examples/htlc.rs +++ b/examples/htlc.rs @@ -18,8 +18,9 @@ extern crate bitcoin; extern crate miniscript; use bitcoin::Network; +use miniscript::descriptor::Wsh; use miniscript::policy::{Concrete, Liftable}; -use miniscript::{Descriptor, DescriptorTrait}; +use miniscript::DescriptorTrait; use std::str::FromStr; fn main() { @@ -31,7 +32,7 @@ fn main() { expiry = "4444" )).unwrap(); - let htlc_descriptor = Descriptor::new_wsh( + let htlc_descriptor = Wsh::new( htlc_policy .compile() .expect("Policy compilation only fails on resource limits or mixed timelocks"), @@ -54,12 +55,12 @@ fn main() { ); assert_eq!( - format!("{:x}", htlc_descriptor.script_pubkey()), + format!("{:x}", htlc_descriptor.spk()), "0020d853877af928a8d2a569c9c0ed14bd16f6a80ce9cccaf8a6150fd8f7f8867ae2" ); assert_eq!( - format!("{:x}", htlc_descriptor.explicit_script()), + format!("{:x}", htlc_descriptor.inner_script()), "21022222222222222222222222222222222222222222222222222222222222222222ac6476a91451814f108670aced2d77c1805ddd6634bc9d473188ad025c11b26782012088a82011111111111111111111111111111111111111111111111111111111111111118768" ); diff --git a/examples/parse.rs b/examples/parse.rs index f64d366c4..5f4f79a2a 100644 --- a/examples/parse.rs +++ b/examples/parse.rs @@ -17,7 +17,7 @@ extern crate bitcoin; extern crate miniscript; -use miniscript::{descriptor::DescriptorType, DescriptorTrait}; +use miniscript::{descriptor::DescriptorType, Descriptor, DescriptorTrait}; use std::str::FromStr; fn main() { @@ -32,17 +32,36 @@ fn main() { // Or they contain a combination of timelock and heightlock. assert!(my_descriptor.sanity_check().is_ok()); - // Sometimes it is necesarry to have additional information to get the bitcoin::PublicKey - // from the MiniscriptKey which can supplied by `to_pk_ctx` parameter. For example, - // when calculating the script pubkey of a descriptor with xpubs, the secp context and - // child information maybe required. + // Compute the script pubkey. As mentioned in the documentation, script_pubkey only fails + // for Tr descriptors that don't have some pre-computed data assert_eq!( format!("{:x}", my_descriptor.script_pubkey()), "0020daef16dd7c946a3e735a6e43310cb2ce33dfd14a04f76bf8241a16654cb2f0f9" ); + // Another way to compute script pubkey + // We can also compute the type of descriptor + let desc_type = my_descriptor.desc_type(); + assert_eq!(desc_type, DescriptorType::Wsh); + // Since we know the type of descriptor, we can get the Wsh struct from Descriptor + // This allows us to call infallible methods for getting script pubkey + if let Descriptor::Wsh(wsh) = &my_descriptor { + assert_eq!( + format!("{:x}", wsh.spk()), + "0020daef16dd7c946a3e735a6e43310cb2ce33dfd14a04f76bf8241a16654cb2f0f9" + ); + } else { + // We checked for the descriptor type earlier + } + + // Get the inner script inside the descriptor assert_eq!( - format!("{:x}", my_descriptor.explicit_script()), + format!( + "{:x}", + my_descriptor + .explicit_script() + .expect("Wsh descriptors have inner scripts") + ), "21020202020202020202020202020202020202020202020202020202020202020202ac" ); diff --git a/examples/sign_multisig.rs b/examples/sign_multisig.rs index 16cdd137c..b9ce59281 100644 --- a/examples/sign_multisig.rs +++ b/examples/sign_multisig.rs @@ -101,7 +101,12 @@ fn main() { ); assert_eq!( - format!("{:x}", my_descriptor.explicit_script()), + format!( + "{:x}", + my_descriptor + .explicit_script() + .expect("wsh descriptors have unique inner script") + ), "52\ 21020202020202020202020202020202020202020202020202020202020202020202\ 21020102030405060708010203040506070801020304050607080000000000000000\ diff --git a/src/descriptor/bare.rs b/src/descriptor/bare.rs index 153fee15d..af265c88c 100644 --- a/src/descriptor/bare.rs +++ b/src/descriptor/bare.rs @@ -63,6 +63,26 @@ impl Bare { } } +impl Bare { + /// Obtain the corresponding script pubkey for this descriptor + /// Non failing verion of [`DescriptorTrait::script_pubkey`] for this descriptor + pub fn spk(&self) -> Script { + self.ms.encode() + } + + /// Obtain the underlying miniscript for this descriptor + /// Non failing verion of [`DescriptorTrait::explicit_script`] for this descriptor + pub fn inner_script(&self) -> Script { + self.spk() + } + + /// Obtain the pre bip-340 signature script code for this descriptor + /// Non failing verion of [`DescriptorTrait::script_code`] for this descriptor + pub fn ecdsa_sighash_script_code(&self) -> Script { + self.spk() + } +} + impl fmt::Debug for Bare { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self.ms) @@ -130,7 +150,7 @@ impl DescriptorTrait for Bare { where Pk: ToPublicKey, { - self.ms.encode() + self.spk() } fn unsigned_script_sig(&self) -> Script @@ -140,11 +160,11 @@ impl DescriptorTrait for Bare { Script::new() } - fn explicit_script(&self) -> Script + fn explicit_script(&self) -> Result where Pk: ToPublicKey, { - self.ms.encode() + Ok(self.inner_script()) } fn get_satisfaction(&self, satisfier: S) -> Result<(Vec>, Script), Error> @@ -174,11 +194,11 @@ impl DescriptorTrait for Bare { Ok(4 * (varint_len(scriptsig_len) + scriptsig_len)) } - fn script_code(&self) -> Script + fn script_code(&self) -> Result where Pk: ToPublicKey, { - self.script_pubkey() + Ok(self.ecdsa_sighash_script_code()) } } @@ -238,6 +258,33 @@ impl Pkh { } } +impl Pkh { + /// Obtain the corresponding script pubkey for this descriptor + /// Non failing verion of [`DescriptorTrait::script_pubkey`] for this descriptor + pub fn spk(&self) -> Script { + let addr = bitcoin::Address::p2pkh(&self.pk.to_public_key(), bitcoin::Network::Bitcoin); + addr.script_pubkey() + } + + /// Obtain the corresponding script pubkey for this descriptor + /// Non failing verion of [`DescriptorTrait::address`] for this descriptor + pub fn addr(&self, network: bitcoin::Network) -> bitcoin::Address { + bitcoin::Address::p2pkh(&self.pk.to_public_key(), network) + } + + /// Obtain the underlying miniscript for this descriptor + /// Non failing verion of [`DescriptorTrait::explicit_script`] for this descriptor + pub fn inner_script(&self) -> Script { + self.spk() + } + + /// Obtain the pre bip-340 signature script code for this descriptor + /// Non failing verion of [`DescriptorTrait::script_code`] for this descriptor + pub fn ecdsa_sighash_script_code(&self) -> Script { + self.spk() + } +} + impl fmt::Debug for Pkh { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "pkh({:?})", self.pk) @@ -305,15 +352,14 @@ impl DescriptorTrait for Pkh { where Pk: ToPublicKey, { - Ok(bitcoin::Address::p2pkh(&self.pk.to_public_key(), network)) + Ok(self.addr(network)) } fn script_pubkey(&self) -> Script where Pk: ToPublicKey, { - let addr = bitcoin::Address::p2pkh(&self.pk.to_public_key(), bitcoin::Network::Bitcoin); - addr.script_pubkey() + self.spk() } fn unsigned_script_sig(&self) -> Script @@ -323,11 +369,11 @@ impl DescriptorTrait for Pkh { Script::new() } - fn explicit_script(&self) -> Script + fn explicit_script(&self) -> Result where Pk: ToPublicKey, { - self.script_pubkey() + Ok(self.inner_script()) } fn get_satisfaction(&self, satisfier: S) -> Result<(Vec>, Script), Error> @@ -360,11 +406,11 @@ impl DescriptorTrait for Pkh { Ok(4 * (1 + 73 + BareCtx::pk_len(&self.pk))) } - fn script_code(&self) -> Script + fn script_code(&self) -> Result where Pk: ToPublicKey, { - self.script_pubkey() + Ok(self.ecdsa_sighash_script_code()) } } diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index d4f24f139..372a19734 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -89,6 +89,9 @@ pub trait DescriptorTrait { /// Computes the Bitcoin address of the descriptor, if one exists /// Some descriptors like pk() don't have any address. + /// Errors: + /// - On raw/bare descriptors that don't have any address + /// - In Tr descriptors where the precomputed spend data is not available fn address(&self, network: bitcoin::Network) -> Result where Pk: ToPublicKey; @@ -114,11 +117,12 @@ pub trait DescriptorTrait { /// script before any hashing is done. For `Bare`, `Pkh` and `Wpkh` this /// is the scriptPubkey; for `ShWpkh` and `Sh` this is the redeemScript; /// for the others it is the witness script. - fn explicit_script(&self) -> Script + /// For `Tr` descriptors, this will error as there is no underlying script + fn explicit_script(&self) -> Result where Pk: ToPublicKey; - /// Returns satisfying non-malleable witness and scriptSig to spend an + /// Returns satisfying non-malleable witness and scriptSig with minimum weight to spend an /// output controlled by the given descriptor if it possible to /// construct one using the satisfier S. fn get_satisfaction(&self, satisfier: S) -> Result<(Vec>, Script), Error> @@ -150,7 +154,7 @@ pub trait DescriptorTrait { } /// Computes an upper bound on the weight of a satisfying witness to the - /// transaction. Assumes all signatures are 73 bytes, including push opcode + /// transaction. Assumes all ec-signatures are 73 bytes, including push opcode /// and sighash suffix. Includes the weight of the VarInts encoding the /// scriptSig and witness stack length. /// Returns Error when the descriptor is impossible to safisfy (ex: sh(OP_FALSE)) @@ -160,7 +164,9 @@ pub trait DescriptorTrait { /// /// The `scriptCode` is the Script of the previous transaction output being serialized in the /// sighash when evaluating a `CHECKSIG` & co. OP code. - fn script_code(&self) -> Script + /// Errors: + /// - When the descriptor is Tr + fn script_code(&self) -> Result where Pk: ToPublicKey; } @@ -420,7 +426,9 @@ impl DescriptorTrait for Descriptor { /// script before any hashing is done. For `Bare`, `Pkh` and `Wpkh` this /// is the scriptPubkey; for `ShWpkh` and `Sh` this is the redeemScript; /// for the others it is the witness script. - fn explicit_script(&self) -> Script + /// Errors: + /// - When the descriptor is Tr + fn explicit_script(&self) -> Result where Pk: ToPublicKey, { @@ -485,7 +493,8 @@ impl DescriptorTrait for Descriptor { /// /// The `scriptCode` is the Script of the previous transaction output being serialized in the /// sighash when evaluating a `CHECKSIG` & co. OP code. - fn script_code(&self) -> Script + /// Returns Error for Tr descriptors + fn script_code(&self) -> Result where Pk: ToPublicKey, { @@ -1099,7 +1108,7 @@ mod tests { #[test] fn after_is_cltv() { let descriptor = Descriptor::::from_str("wsh(after(1000))").unwrap(); - let script = descriptor.explicit_script(); + let script = descriptor.explicit_script().unwrap(); let actual_instructions: Vec<_> = script.instructions().collect(); let check = actual_instructions.last().unwrap(); @@ -1110,7 +1119,7 @@ mod tests { #[test] fn older_is_csv() { let descriptor = Descriptor::::from_str("wsh(older(1000))").unwrap(); - let script = descriptor.explicit_script(); + let script = descriptor.explicit_script().unwrap(); let actual_instructions: Vec<_> = script.instructions().collect(); let check = actual_instructions.last().unwrap(); @@ -1250,7 +1259,7 @@ mod tests { ) .unwrap(); assert_eq!( - *descriptor.script_code().as_bytes(), + *descriptor.script_code().unwrap().as_bytes(), Vec::::from_hex("76a9141d0f172a0ecb48aee1be1f2687d2963ae33f71a188ac").unwrap()[..] ); @@ -1260,7 +1269,7 @@ mod tests { ) .unwrap(); assert_eq!( - *descriptor.script_code().as_bytes(), + *descriptor.script_code().unwrap().as_bytes(), Vec::::from_hex("76a91479091972186c449eb1ded22b78e40d009bdf008988ac").unwrap()[..] ); @@ -1271,7 +1280,7 @@ mod tests { .unwrap(); assert_eq!( *descriptor - .script_code() + .script_code().unwrap() .as_bytes(), Vec::::from_hex("522103789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd2103dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a6162652ae").unwrap()[..] ); @@ -1280,7 +1289,7 @@ mod tests { let descriptor = Descriptor::::from_str("sh(wsh(multi(2,03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd,03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626)))").unwrap(); assert_eq!( *descriptor - .script_code() + .script_code().unwrap() .as_bytes(), Vec::::from_hex("522103789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd2103dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a6162652ae") .unwrap()[..] diff --git a/src/descriptor/segwitv0.rs b/src/descriptor/segwitv0.rs index be656cda1..55d87bd05 100644 --- a/src/descriptor/segwitv0.rs +++ b/src/descriptor/segwitv0.rs @@ -78,6 +78,38 @@ impl Wsh { } } +impl Wsh { + /// Obtain the corresponding script pubkey for this descriptor + /// Non failing verion of [`DescriptorTrait::script_pubkey`] for this descriptor + pub fn spk(&self) -> Script { + self.inner_script().to_v0_p2wsh() + } + + /// Obtain the corresponding script pubkey for this descriptor + /// Non failing verion of [`DescriptorTrait::address`] for this descriptor + pub fn addr(&self, network: bitcoin::Network) -> bitcoin::Address { + match self.inner { + WshInner::SortedMulti(ref smv) => bitcoin::Address::p2wsh(&smv.encode(), network), + WshInner::Ms(ref ms) => bitcoin::Address::p2wsh(&ms.encode(), network), + } + } + + /// Obtain the underlying miniscript for this descriptor + /// Non failing verion of [`DescriptorTrait::explicit_script`] for this descriptor + pub fn inner_script(&self) -> Script { + match self.inner { + WshInner::SortedMulti(ref smv) => smv.encode(), + WshInner::Ms(ref ms) => ms.encode(), + } + } + + /// Obtain the pre bip-340 signature script code for this descriptor + /// Non failing verion of [`DescriptorTrait::script_code`] for this descriptor + pub fn ecdsa_sighash_script_code(&self) -> Script { + self.inner_script() + } +} + /// Wsh Inner #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum WshInner { @@ -171,17 +203,14 @@ impl DescriptorTrait for Wsh { where Pk: ToPublicKey, { - match self.inner { - WshInner::SortedMulti(ref smv) => Ok(bitcoin::Address::p2wsh(&smv.encode(), network)), - WshInner::Ms(ref ms) => Ok(bitcoin::Address::p2wsh(&ms.encode(), network)), - } + Ok(self.addr(network)) } fn script_pubkey(&self) -> Script where Pk: ToPublicKey, { - self.explicit_script().to_v0_p2wsh() + self.spk() } fn unsigned_script_sig(&self) -> Script @@ -191,14 +220,11 @@ impl DescriptorTrait for Wsh { Script::new() } - fn explicit_script(&self) -> Script + fn explicit_script(&self) -> Result where Pk: ToPublicKey, { - match self.inner { - WshInner::SortedMulti(ref smv) => smv.encode(), - WshInner::Ms(ref ms) => ms.encode(), - } + Ok(self.inner_script()) } fn get_satisfaction(&self, satisfier: S) -> Result<(Vec>, Script), Error> @@ -210,7 +236,8 @@ impl DescriptorTrait for Wsh { WshInner::SortedMulti(ref smv) => smv.satisfy(satisfier)?, WshInner::Ms(ref ms) => ms.satisfy(satisfier)?, }; - witness.push(self.explicit_script().into_bytes()); + let witness_script = self.inner_script(); + witness.push(witness_script.into_bytes()); let script_sig = Script::new(); Ok((witness, script_sig)) } @@ -224,7 +251,7 @@ impl DescriptorTrait for Wsh { WshInner::SortedMulti(ref smv) => smv.satisfy(satisfier)?, WshInner::Ms(ref ms) => ms.satisfy_malleable(satisfier)?, }; - witness.push(self.explicit_script().into_bytes()); + witness.push(self.inner_script().into_bytes()); let script_sig = Script::new(); Ok((witness, script_sig)) } @@ -249,11 +276,11 @@ impl DescriptorTrait for Wsh { max_sat_size) } - fn script_code(&self) -> Script + fn script_code(&self) -> Result where Pk: ToPublicKey, { - self.explicit_script() + Ok(self.ecdsa_sighash_script_code()) } } @@ -331,6 +358,40 @@ impl Wpkh { } } +impl Wpkh { + /// Obtain the corresponding script pubkey for this descriptor + /// Non failing verion of [`DescriptorTrait::script_pubkey`] for this descriptor + pub fn spk(&self) -> Script { + let addr = bitcoin::Address::p2wpkh(&self.pk.to_public_key(), bitcoin::Network::Bitcoin) + .expect("wpkh descriptors have compressed keys"); + addr.script_pubkey() + } + + /// Obtain the corresponding script pubkey for this descriptor + /// Non failing verion of [`DescriptorTrait::address`] for this descriptor + pub fn addr(&self, network: bitcoin::Network) -> bitcoin::Address { + bitcoin::Address::p2wpkh(&self.pk.to_public_key(), network) + .expect("Rust Miniscript types don't allow uncompressed pks in segwit descriptors") + } + + /// Obtain the underlying miniscript for this descriptor + /// Non failing verion of [`DescriptorTrait::explicit_script`] for this descriptor + pub fn inner_script(&self) -> Script { + self.spk() + } + + /// Obtain the pre bip-340 signature script code for this descriptor + /// Non failing verion of [`DescriptorTrait::script_code`] for this descriptor + pub fn ecdsa_sighash_script_code(&self) -> Script { + // For SegWit outputs, it is defined by bip-0143 (quoted below) and is different from + // the previous txo's scriptPubKey. + // The item 5: + // - For P2WPKH witness program, the scriptCode is `0x1976a914{20-byte-pubkey-hash}88ac`. + let addr = bitcoin::Address::p2pkh(&self.pk.to_public_key(), bitcoin::Network::Bitcoin); + addr.script_pubkey() + } +} + impl fmt::Debug for Wpkh { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "wpkh({:?})", self.pk) @@ -404,17 +465,14 @@ impl DescriptorTrait for Wpkh { where Pk: ToPublicKey, { - Ok(bitcoin::Address::p2wpkh(&self.pk.to_public_key(), network) - .expect("Rust Miniscript types don't allow uncompressed pks in segwit descriptors")) + Ok(self.addr(network)) } fn script_pubkey(&self) -> Script where Pk: ToPublicKey, { - let addr = bitcoin::Address::p2wpkh(&self.pk.to_public_key(), bitcoin::Network::Bitcoin) - .expect("wpkh descriptors have compressed keys"); - addr.script_pubkey() + self.spk() } fn unsigned_script_sig(&self) -> Script @@ -424,11 +482,11 @@ impl DescriptorTrait for Wpkh { Script::new() } - fn explicit_script(&self) -> Script + fn explicit_script(&self) -> Result where Pk: ToPublicKey, { - self.script_pubkey() + Ok(self.inner_script()) } fn get_satisfaction(&self, satisfier: S) -> Result<(Vec>, Script), Error> @@ -458,16 +516,11 @@ impl DescriptorTrait for Wpkh { Ok(4 + 1 + 73 + Segwitv0::pk_len(&self.pk)) } - fn script_code(&self) -> Script + fn script_code(&self) -> Result where Pk: ToPublicKey, { - // For SegWit outputs, it is defined by bip-0143 (quoted below) and is different from - // the previous txo's scriptPubKey. - // The item 5: - // - For P2WPKH witness program, the scriptCode is `0x1976a914{20-byte-pubkey-hash}88ac`. - let addr = bitcoin::Address::p2pkh(&self.pk.to_public_key(), bitcoin::Network::Bitcoin); - addr.script_pubkey() + Ok(self.ecdsa_sighash_script_code()) } } diff --git a/src/descriptor/sh.rs b/src/descriptor/sh.rs index 7b39bd33d..fb3c4e398 100644 --- a/src/descriptor/sh.rs +++ b/src/descriptor/sh.rs @@ -193,6 +193,63 @@ impl Sh { } } +impl Sh { + /// Obtain the corresponding script pubkey for this descriptor + /// Non failing verion of [`DescriptorTrait::script_pubkey`] for this descriptor + pub fn spk(&self) -> Script { + match self.inner { + ShInner::Wsh(ref wsh) => wsh.spk().to_p2sh(), + ShInner::Wpkh(ref wpkh) => wpkh.spk().to_p2sh(), + ShInner::SortedMulti(ref smv) => smv.encode().to_p2sh(), + ShInner::Ms(ref ms) => ms.encode().to_p2sh(), + } + } + + /// Obtain the corresponding script pubkey for this descriptor + /// Non failing verion of [`DescriptorTrait::address`] for this descriptor + pub fn addr(&self, network: bitcoin::Network) -> bitcoin::Address { + match self.inner { + ShInner::Wsh(ref wsh) => { + bitcoin::Address::p2sh(&wsh.spk(), network).expect("Size checked in Miniscript") + } + ShInner::Wpkh(ref wpkh) => { + bitcoin::Address::p2sh(&wpkh.spk(), network).expect("Size checked in Miniscript") + } + ShInner::SortedMulti(ref smv) => { + bitcoin::Address::p2sh(&smv.encode(), network).expect("Size checked in Miniscript") + } + ShInner::Ms(ref ms) => { + bitcoin::Address::p2sh(&ms.encode(), network).expect("Size checked in Miniscript") + } + } + } + + /// Obtain the underlying miniscript for this descriptor + /// Non failing verion of [`DescriptorTrait::explicit_script`] for this descriptor + pub fn inner_script(&self) -> Script { + match self.inner { + ShInner::Wsh(ref wsh) => wsh.inner_script(), + ShInner::Wpkh(ref wpkh) => wpkh.spk(), + ShInner::SortedMulti(ref smv) => smv.encode(), + ShInner::Ms(ref ms) => ms.encode(), + } + } + + /// Obtain the pre bip-340 signature script code for this descriptor + /// Non failing verion of [`DescriptorTrait::script_code`] for this descriptor + pub fn ecdsa_sighash_script_code(&self) -> Script { + match self.inner { + // - For P2WSH witness program, if the witnessScript does not contain any `OP_CODESEPARATOR`, + // the `scriptCode` is the `witnessScript` serialized as scripts inside CTxOut. + ShInner::Wsh(ref wsh) => wsh.ecdsa_sighash_script_code(), + ShInner::SortedMulti(ref smv) => smv.encode(), + ShInner::Wpkh(ref wpkh) => wpkh.ecdsa_sighash_script_code(), + // For "legacy" P2SH outputs, it is defined as the txo's redeemScript. + ShInner::Ms(ref ms) => ms.encode(), + } + } +} + impl DescriptorTrait for Sh { fn sanity_check(&self) -> Result<(), Error> { match self.inner { @@ -209,8 +266,8 @@ impl DescriptorTrait for Sh { Pk: ToPublicKey, { match self.inner { - ShInner::Wsh(ref wsh) => Ok(bitcoin::Address::p2sh(&wsh.script_pubkey(), network)?), - ShInner::Wpkh(ref wpkh) => Ok(bitcoin::Address::p2sh(&wpkh.script_pubkey(), network)?), + ShInner::Wsh(ref wsh) => Ok(bitcoin::Address::p2sh(&wsh.spk(), network)?), + ShInner::Wpkh(ref wpkh) => Ok(bitcoin::Address::p2sh(&wpkh.spk(), network)?), ShInner::SortedMulti(ref smv) => Ok(bitcoin::Address::p2sh(&smv.encode(), network)?), ShInner::Ms(ref ms) => Ok(bitcoin::Address::p2sh(&ms.encode(), network)?), } @@ -220,12 +277,7 @@ impl DescriptorTrait for Sh { where Pk: ToPublicKey, { - match self.inner { - ShInner::Wsh(ref wsh) => wsh.script_pubkey().to_p2sh(), - ShInner::Wpkh(ref wpkh) => wpkh.script_pubkey().to_p2sh(), - ShInner::SortedMulti(ref smv) => smv.encode().to_p2sh(), - ShInner::Ms(ref ms) => ms.encode().to_p2sh(), - } + self.spk() } fn unsigned_script_sig(&self) -> Script @@ -234,13 +286,14 @@ impl DescriptorTrait for Sh { { match self.inner { ShInner::Wsh(ref wsh) => { - let witness_script = wsh.explicit_script(); + // wsh explicit must contain exactly 1 element + let witness_script = wsh.inner_script(); script::Builder::new() .push_slice(&witness_script.to_v0_p2wsh()[..]) .into_script() } ShInner::Wpkh(ref wpkh) => { - let redeem_script = wpkh.script_pubkey(); + let redeem_script = wpkh.spk(); script::Builder::new() .push_slice(&redeem_script[..]) .into_script() @@ -249,16 +302,11 @@ impl DescriptorTrait for Sh { } } - fn explicit_script(&self) -> Script + fn explicit_script(&self) -> Result where Pk: ToPublicKey, { - match self.inner { - ShInner::Wsh(ref wsh) => wsh.explicit_script(), - ShInner::Wpkh(ref wpkh) => wpkh.script_pubkey(), - ShInner::SortedMulti(ref smv) => smv.encode(), - ShInner::Ms(ref ms) => ms.encode(), - } + Ok(self.inner_script()) } fn get_satisfaction(&self, satisfier: S) -> Result<(Vec>, Script), Error> @@ -336,19 +384,11 @@ impl DescriptorTrait for Sh { }) } - fn script_code(&self) -> Script + fn script_code(&self) -> Result where Pk: ToPublicKey, { - match self.inner { - // - For P2WSH witness program, if the witnessScript does not contain any `OP_CODESEPARATOR`, - // the `scriptCode` is the `witnessScript` serialized as scripts inside CTxOut. - ShInner::Wsh(ref wsh) => wsh.script_code(), - ShInner::SortedMulti(ref smv) => smv.encode(), - ShInner::Wpkh(ref wpkh) => wpkh.script_code(), - // For "legacy" P2SH outputs, it is defined as the txo's redeemScript. - ShInner::Ms(ref ms) => ms.encode(), - } + Ok(self.ecdsa_sighash_script_code()) } } From dc21a794315651a62001e3e982a936d4d35007cb Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Fri, 29 Oct 2021 07:17:30 -0700 Subject: [PATCH 05/12] Update satisfy API for taproot support --- src/miniscript/mod.rs | 24 ++- src/miniscript/satisfy.rs | 363 +++++++++++++++++++++++++++++--------- 2 files changed, 303 insertions(+), 84 deletions(-) diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index 541087fb7..5aae74020 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -28,6 +28,7 @@ use std::marker::PhantomData; use std::{fmt, str}; use bitcoin::blockdata::script; +use bitcoin::util::taproot::{LeafVersion, TapLeafHash}; pub use self::context::{BareCtx, Legacy, Segwitv0, Tap}; @@ -358,7 +359,11 @@ impl Miniscript { where Pk: ToPublicKey, { - match satisfy::Satisfaction::satisfy(&self.node, &satisfier, self.ty.mall.safe).stack { + // Only satisfactions for default versions (0xc0) are allowed. + let leaf_hash = TapLeafHash::from_script(&self.encode(), LeafVersion::TapScript); + match satisfy::Satisfaction::satisfy(&self.node, &satisfier, self.ty.mall.safe, &leaf_hash) + .stack + { satisfy::Witness::Stack(stack) => { Ctx::check_witness::(&stack)?; Ok(stack) @@ -378,7 +383,15 @@ impl Miniscript { where Pk: ToPublicKey, { - match satisfy::Satisfaction::satisfy_mall(&self.node, &satisfier, self.ty.mall.safe).stack { + let leaf_hash = TapLeafHash::from_script(&self.encode(), LeafVersion::TapScript); + match satisfy::Satisfaction::satisfy_mall( + &self.node, + &satisfier, + self.ty.mall.safe, + &leaf_hash, + ) + .stack + { satisfy::Witness::Stack(stack) => { Ctx::check_witness::(&stack)?; Ok(stack) @@ -449,6 +462,7 @@ serde_string_impl_pk!(Miniscript, "a miniscript", Ctx; ScriptContext); #[cfg(test)] mod tests { + use bitcoin::util::taproot::TapLeafHash; use {Satisfier, ToPublicKey}; use super::{Miniscript, ScriptContext}; @@ -1039,7 +1053,11 @@ mod tests { // a simple satisfier that always outputs the same signature impl Satisfier for SimpleSatisfier { - fn lookup_schnorr_sig(&self, _pk: &Pk) -> Option { + fn lookup_tap_leaf_script_sig( + &self, + _pk: &Pk, + _h: &TapLeafHash, + ) -> Option { Some(bitcoin::SchnorrSig { sig: self.0, hash_ty: bitcoin::SchnorrSigHashType::Default, diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index e430414ab..547005585 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -18,14 +18,16 @@ //! scriptpubkeys. //! -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; use std::{cmp, i64, mem}; use bitcoin; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; +use bitcoin::util::taproot::{ControlBlock, LeafVersion, TapLeafHash}; use {MiniscriptKey, ToPublicKey}; +use bitcoin::secp256k1::XOnlyPublicKey; use miniscript::limits::{ HEIGHT_TIME_THRESHOLD, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_TYPE_FLAG, }; @@ -46,8 +48,20 @@ pub trait Satisfier { None } - /// Given a public key, look up an schnorr signature with that key - fn lookup_schnorr_sig(&self, _: &Pk) -> Option { + /// Lookup the tap key spend sig + fn lookup_tap_key_spend_sig(&self) -> Option { + None + } + + /// Given a public key and a associated leaf hash, look up an schnorr signature with that key + fn lookup_tap_leaf_script_sig(&self, _: &Pk, _: &TapLeafHash) -> Option { + None + } + + /// Obtain a reference to the control block for a ver and script + fn lookup_tap_control_block_map( + &self, + ) -> Option<&BTreeMap> { None } @@ -71,10 +85,10 @@ pub trait Satisfier { /// Even if signatures for public key Hashes are not available, the users /// can use this map to provide pkh -> pk mapping which can be useful /// for dissatisfying pkh. - fn lookup_pkh_schnorr_sig( + fn lookup_pkh_tap_leaf_script_sig( &self, - _: &Pk::Hash, - ) -> Option<(bitcoin::PublicKey, bitcoin::SchnorrSig)> { + _: &(Pk::Hash, TapLeafHash), + ) -> Option<(XOnlyPublicKey, bitcoin::SchnorrSig)> { None } @@ -158,9 +172,15 @@ impl Satisfier for HashMap Satisfier for HashMap { - fn lookup_schnorr_sig(&self, key: &Pk) -> Option { - self.get(key).map(|x| *x) +impl Satisfier + for HashMap<(Pk, TapLeafHash), bitcoin::SchnorrSig> +{ + fn lookup_tap_leaf_script_sig(&self, key: &Pk, h: &TapLeafHash) -> Option { + // Unfortunately, there is no way to get a &(a, b) from &a and &b without allocating + // If we change the signature the of lookup_tap_leaf_script_sig to accept a tuple. We would + // face the same problem while satisfying PkK. + // We use this signature to optimize for the psbt common use case. + self.get(&(key.clone(), *h)).map(|x| *x) } } @@ -185,24 +205,21 @@ where } } -impl Satisfier for HashMap +impl Satisfier + for HashMap<(Pk::Hash, TapLeafHash), (Pk, bitcoin::SchnorrSig)> where Pk: MiniscriptKey + ToPublicKey, { - fn lookup_schnorr_sig(&self, key: &Pk) -> Option { - self.get(&key.to_pubkeyhash()).map(|x| x.1) - } - - fn lookup_pkh_pk(&self, pk_hash: &Pk::Hash) -> Option { - self.get(pk_hash).map(|x| x.0.clone()) + fn lookup_tap_leaf_script_sig(&self, key: &Pk, h: &TapLeafHash) -> Option { + self.get(&(key.to_pubkeyhash(), *h)).map(|x| x.1) } - fn lookup_pkh_schnorr_sig( + fn lookup_pkh_tap_leaf_script_sig( &self, - pk_hash: &Pk::Hash, - ) -> Option<(bitcoin::PublicKey, bitcoin::SchnorrSig)> { + pk_hash: &(Pk::Hash, TapLeafHash), + ) -> Option<(XOnlyPublicKey, bitcoin::SchnorrSig)> { self.get(pk_hash) - .map(|&(ref pk, sig)| (pk.to_public_key(), sig)) + .map(|&(ref pk, sig)| (pk.to_x_only_pubkey(), sig)) } } @@ -211,8 +228,8 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' (**self).lookup_ecdsa_sig(p) } - fn lookup_schnorr_sig(&self, p: &Pk) -> Option { - (**self).lookup_schnorr_sig(p) + fn lookup_tap_leaf_script_sig(&self, p: &Pk, h: &TapLeafHash) -> Option { + (**self).lookup_tap_leaf_script_sig(p, h) } fn lookup_pkh_pk(&self, pkh: &Pk::Hash) -> Option { @@ -226,11 +243,21 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' (**self).lookup_pkh_ecdsa_sig(pkh) } - fn lookup_pkh_schnorr_sig( + fn lookup_tap_key_spend_sig(&self) -> Option { + (**self).lookup_tap_key_spend_sig() + } + + fn lookup_pkh_tap_leaf_script_sig( &self, - pkh: &Pk::Hash, - ) -> Option<(bitcoin::PublicKey, bitcoin::SchnorrSig)> { - (**self).lookup_pkh_schnorr_sig(pkh) + pkh: &(Pk::Hash, TapLeafHash), + ) -> Option<(XOnlyPublicKey, bitcoin::SchnorrSig)> { + (**self).lookup_pkh_tap_leaf_script_sig(pkh) + } + + fn lookup_tap_control_block_map( + &self, + ) -> Option<&BTreeMap> { + (**self).lookup_tap_control_block_map() } fn lookup_sha256(&self, h: sha256::Hash) -> Option { @@ -263,8 +290,12 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' (**self).lookup_ecdsa_sig(p) } - fn lookup_schnorr_sig(&self, p: &Pk) -> Option { - (**self).lookup_schnorr_sig(p) + fn lookup_tap_leaf_script_sig(&self, p: &Pk, h: &TapLeafHash) -> Option { + (**self).lookup_tap_leaf_script_sig(p, h) + } + + fn lookup_tap_key_spend_sig(&self) -> Option { + (**self).lookup_tap_key_spend_sig() } fn lookup_pkh_pk(&self, pkh: &Pk::Hash) -> Option { @@ -278,11 +309,17 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' (**self).lookup_pkh_ecdsa_sig(pkh) } - fn lookup_pkh_schnorr_sig( + fn lookup_pkh_tap_leaf_script_sig( &self, - pkh: &Pk::Hash, - ) -> Option<(bitcoin::PublicKey, bitcoin::SchnorrSig)> { - (**self).lookup_pkh_schnorr_sig(pkh) + pkh: &(Pk::Hash, TapLeafHash), + ) -> Option<(XOnlyPublicKey, bitcoin::SchnorrSig)> { + (**self).lookup_pkh_tap_leaf_script_sig(pkh) + } + + fn lookup_tap_control_block_map( + &self, + ) -> Option<&BTreeMap> { + (**self).lookup_tap_control_block_map() } fn lookup_sha256(&self, h: sha256::Hash) -> Option { @@ -328,10 +365,20 @@ macro_rules! impl_tuple_satisfier { None } - fn lookup_schnorr_sig(&self, key: &Pk) -> Option { + fn lookup_tap_key_spend_sig(&self) -> Option { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_tap_key_spend_sig() { + return Some(result); + } + )* + None + } + + fn lookup_tap_leaf_script_sig(&self, key: &Pk, h: &TapLeafHash) -> Option { let &($(ref $ty,)*) = self; $( - if let Some(result) = $ty.lookup_schnorr_sig(key) { + if let Some(result) = $ty.lookup_tap_leaf_script_sig(key, h) { return Some(result); } )* @@ -351,13 +398,13 @@ macro_rules! impl_tuple_satisfier { None } - fn lookup_pkh_schnorr_sig( + fn lookup_pkh_tap_leaf_script_sig( &self, - key_hash: &Pk::Hash, - ) -> Option<(bitcoin::PublicKey, bitcoin::SchnorrSig)> { + key_hash: &(Pk::Hash, TapLeafHash), + ) -> Option<(XOnlyPublicKey, bitcoin::SchnorrSig)> { let &($(ref $ty,)*) = self; $( - if let Some(result) = $ty.lookup_pkh_schnorr_sig(key_hash) { + if let Some(result) = $ty.lookup_pkh_tap_leaf_script_sig(key_hash) { return Some(result); } )* @@ -377,6 +424,18 @@ macro_rules! impl_tuple_satisfier { None } + fn lookup_tap_control_block_map( + &self, + ) -> Option<&BTreeMap> { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_tap_control_block_map() { + return Some(result); + } + )* + None + } + fn lookup_sha256(&self, h: sha256::Hash) -> Option { let &($(ref $ty,)*) = self; $( @@ -488,14 +547,19 @@ impl Ord for Witness { impl Witness { /// Turn a signature into (part of) a satisfaction - fn signature, Ctx: ScriptContext>(sat: S, pk: &Pk) -> Self { + fn signature, Ctx: ScriptContext>( + sat: S, + pk: &Pk, + leaf_hash: &TapLeafHash, + ) -> Self { match Ctx::sig_type() { super::context::SigType::Ecdsa => match sat.lookup_ecdsa_sig(pk) { Some(sig) => Witness::Stack(vec![sig.to_vec()]), // Signatures cannot be forged None => Witness::Impossible, }, - super::context::SigType::Schnorr => match sat.lookup_schnorr_sig(pk) { + super::context::SigType::Schnorr => match sat.lookup_tap_leaf_script_sig(pk, leaf_hash) + { Some(sig) => Witness::Stack(vec![sig.to_vec()]), // Signatures cannot be forged None => Witness::Impossible, @@ -609,6 +673,7 @@ impl Satisfaction { subs: &[Arc>], stfr: &Sat, root_has_sig: bool, + leaf_hash: &TapLeafHash, min_fn: &mut F, ) -> Self where @@ -619,13 +684,29 @@ impl Satisfaction { { let mut sats = subs .iter() - .map(|s| Self::satisfy_helper(&s.node, stfr, root_has_sig, min_fn, &mut Self::thresh)) + .map(|s| { + Self::satisfy_helper( + &s.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + &mut Self::thresh, + ) + }) .collect::>(); // Start with the to-return stack set to all dissatisfactions let mut ret_stack = subs .iter() .map(|s| { - Self::dissatisfy_helper(&s.node, stfr, root_has_sig, min_fn, &mut Self::thresh) + Self::dissatisfy_helper( + &s.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + &mut Self::thresh, + ) }) .collect::>(); @@ -710,6 +791,7 @@ impl Satisfaction { subs: &[Arc>], stfr: &Sat, root_has_sig: bool, + leaf_hash: &TapLeafHash, min_fn: &mut F, ) -> Self where @@ -721,14 +803,28 @@ impl Satisfaction { let mut sats = subs .iter() .map(|s| { - Self::satisfy_helper(&s.node, stfr, root_has_sig, min_fn, &mut Self::thresh_mall) + Self::satisfy_helper( + &s.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + &mut Self::thresh_mall, + ) }) .collect::>(); // Start with the to-return stack set to all dissatisfactions let mut ret_stack = subs .iter() .map(|s| { - Self::dissatisfy_helper(&s.node, stfr, root_has_sig, min_fn, &mut Self::thresh_mall) + Self::dissatisfy_helper( + &s.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + &mut Self::thresh_mall, + ) }) .collect::>(); @@ -823,6 +919,7 @@ impl Satisfaction { term: &Terminal, stfr: &Sat, root_has_sig: bool, + leaf_hash: &TapLeafHash, min_fn: &mut F, thresh_fn: &mut G, ) -> Self @@ -831,11 +928,18 @@ impl Satisfaction { Ctx: ScriptContext, Sat: Satisfier, F: FnMut(Satisfaction, Satisfaction) -> Satisfaction, - G: FnMut(usize, &[Arc>], &Sat, bool, &mut F) -> Satisfaction, + G: FnMut( + usize, + &[Arc>], + &Sat, + bool, + &TapLeafHash, + &mut F, + ) -> Satisfaction, { match *term { Terminal::PkK(ref pk) => Satisfaction { - stack: Witness::signature::<_, _, Ctx>(stfr, pk), + stack: Witness::signature::<_, _, Ctx>(stfr, pk, leaf_hash), has_sig: true, }, Terminal::PkH(ref pkh) => Satisfaction { @@ -903,29 +1007,47 @@ impl Satisfaction { | Terminal::Verify(ref sub) | Terminal::NonZero(ref sub) | Terminal::ZeroNotEqual(ref sub) => { - Self::satisfy_helper(&sub.node, stfr, root_has_sig, min_fn, thresh_fn) + Self::satisfy_helper(&sub.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn) } Terminal::DupIf(ref sub) => { - let sat = Self::satisfy_helper(&sub.node, stfr, root_has_sig, min_fn, thresh_fn); + let sat = Self::satisfy_helper( + &sub.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + thresh_fn, + ); Satisfaction { stack: Witness::combine(sat.stack, Witness::push_1()), has_sig: sat.has_sig, } } Terminal::AndV(ref l, ref r) | Terminal::AndB(ref l, ref r) => { - let l_sat = Self::satisfy_helper(&l.node, stfr, root_has_sig, min_fn, thresh_fn); - let r_sat = Self::satisfy_helper(&r.node, stfr, root_has_sig, min_fn, thresh_fn); + let l_sat = + Self::satisfy_helper(&l.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + let r_sat = + Self::satisfy_helper(&r.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); Satisfaction { stack: Witness::combine(r_sat.stack, l_sat.stack), has_sig: l_sat.has_sig || r_sat.has_sig, } } Terminal::AndOr(ref a, ref b, ref c) => { - let a_sat = Self::satisfy_helper(&a.node, stfr, root_has_sig, min_fn, thresh_fn); - let a_nsat = - Self::dissatisfy_helper(&a.node, stfr, root_has_sig, min_fn, thresh_fn); - let b_sat = Self::satisfy_helper(&b.node, stfr, root_has_sig, min_fn, thresh_fn); - let c_sat = Self::satisfy_helper(&c.node, stfr, root_has_sig, min_fn, thresh_fn); + let a_sat = + Self::satisfy_helper(&a.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + let a_nsat = Self::dissatisfy_helper( + &a.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + thresh_fn, + ); + let b_sat = + Self::satisfy_helper(&b.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + let c_sat = + Self::satisfy_helper(&c.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); min_fn( Satisfaction { @@ -939,12 +1061,26 @@ impl Satisfaction { ) } Terminal::OrB(ref l, ref r) => { - let l_sat = Self::satisfy_helper(&l.node, stfr, root_has_sig, min_fn, thresh_fn); - let r_sat = Self::satisfy_helper(&r.node, stfr, root_has_sig, min_fn, thresh_fn); - let l_nsat = - Self::dissatisfy_helper(&l.node, stfr, root_has_sig, min_fn, thresh_fn); - let r_nsat = - Self::dissatisfy_helper(&r.node, stfr, root_has_sig, min_fn, thresh_fn); + let l_sat = + Self::satisfy_helper(&l.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + let r_sat = + Self::satisfy_helper(&r.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + let l_nsat = Self::dissatisfy_helper( + &l.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + thresh_fn, + ); + let r_nsat = Self::dissatisfy_helper( + &r.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + thresh_fn, + ); assert!(!l_nsat.has_sig); assert!(!r_nsat.has_sig); @@ -961,10 +1097,18 @@ impl Satisfaction { ) } Terminal::OrD(ref l, ref r) | Terminal::OrC(ref l, ref r) => { - let l_sat = Self::satisfy_helper(&l.node, stfr, root_has_sig, min_fn, thresh_fn); - let r_sat = Self::satisfy_helper(&r.node, stfr, root_has_sig, min_fn, thresh_fn); - let l_nsat = - Self::dissatisfy_helper(&l.node, stfr, root_has_sig, min_fn, thresh_fn); + let l_sat = + Self::satisfy_helper(&l.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + let r_sat = + Self::satisfy_helper(&r.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + let l_nsat = Self::dissatisfy_helper( + &l.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + thresh_fn, + ); assert!(!l_nsat.has_sig); @@ -977,8 +1121,10 @@ impl Satisfaction { ) } Terminal::OrI(ref l, ref r) => { - let l_sat = Self::satisfy_helper(&l.node, stfr, root_has_sig, min_fn, thresh_fn); - let r_sat = Self::satisfy_helper(&r.node, stfr, root_has_sig, min_fn, thresh_fn); + let l_sat = + Self::satisfy_helper(&l.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + let r_sat = + Self::satisfy_helper(&r.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); min_fn( Satisfaction { stack: Witness::combine(l_sat.stack, Witness::push_1()), @@ -990,13 +1136,15 @@ impl Satisfaction { }, ) } - Terminal::Thresh(k, ref subs) => thresh_fn(k, subs, stfr, root_has_sig, min_fn), + Terminal::Thresh(k, ref subs) => { + thresh_fn(k, subs, stfr, root_has_sig, leaf_hash, min_fn) + } Terminal::Multi(k, ref keys) => { // Collect all available signatures let mut sig_count = 0; let mut sigs = Vec::with_capacity(k); for pk in keys { - match Witness::signature::<_, _, Ctx>(stfr, pk) { + match Witness::signature::<_, _, Ctx>(stfr, pk, leaf_hash) { Witness::Stack(sig) => { sigs.push(sig); sig_count += 1; @@ -1038,7 +1186,7 @@ impl Satisfaction { let mut sig_count = 0; let mut sigs = vec![vec![vec![]]; keys.len()]; for (i, pk) in keys.iter().rev().enumerate() { - match Witness::signature::<_, _, Ctx>(stfr, pk) { + match Witness::signature::<_, _, Ctx>(stfr, pk, leaf_hash) { Witness::Stack(sig) => { sigs[i] = sig; sig_count += 1; @@ -1079,6 +1227,7 @@ impl Satisfaction { term: &Terminal, stfr: &Sat, root_has_sig: bool, + leaf_hash: &TapLeafHash, min_fn: &mut F, thresh_fn: &mut G, ) -> Self @@ -1087,7 +1236,14 @@ impl Satisfaction { Ctx: ScriptContext, Sat: Satisfier, F: FnMut(Satisfaction, Satisfaction) -> Satisfaction, - G: FnMut(usize, &[Arc>], &Sat, bool, &mut F) -> Satisfaction, + G: FnMut( + usize, + &[Arc>], + &Sat, + bool, + &TapLeafHash, + &mut F, + ) -> Satisfaction, { match *term { Terminal::PkK(..) => Satisfaction { @@ -1125,7 +1281,7 @@ impl Satisfaction { | Terminal::Swap(ref sub) | Terminal::Check(ref sub) | Terminal::ZeroNotEqual(ref sub) => { - Self::dissatisfy_helper(&sub.node, stfr, root_has_sig, min_fn, thresh_fn) + Self::dissatisfy_helper(&sub.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn) } Terminal::DupIf(_) | Terminal::NonZero(_) => Satisfaction { stack: Witness::push_0(), @@ -1136,9 +1292,16 @@ impl Satisfaction { has_sig: false, }, Terminal::AndV(ref v, ref other) => { - let vsat = Self::satisfy_helper(&v.node, stfr, root_has_sig, min_fn, thresh_fn); - let odissat = - Self::dissatisfy_helper(&other.node, stfr, root_has_sig, min_fn, thresh_fn); + let vsat = + Self::satisfy_helper(&v.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + let odissat = Self::dissatisfy_helper( + &other.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + thresh_fn, + ); Satisfaction { stack: Witness::combine(odissat.stack, vsat.stack), has_sig: vsat.has_sig || odissat.has_sig, @@ -1148,8 +1311,22 @@ impl Satisfaction { | Terminal::OrB(ref l, ref r) | Terminal::OrD(ref l, ref r) | Terminal::AndOr(ref l, _, ref r) => { - let lnsat = Self::dissatisfy_helper(&l.node, stfr, root_has_sig, min_fn, thresh_fn); - let rnsat = Self::dissatisfy_helper(&r.node, stfr, root_has_sig, min_fn, thresh_fn); + let lnsat = Self::dissatisfy_helper( + &l.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + thresh_fn, + ); + let rnsat = Self::dissatisfy_helper( + &r.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + thresh_fn, + ); Satisfaction { stack: Witness::combine(rnsat.stack, lnsat.stack), has_sig: rnsat.has_sig || lnsat.has_sig, @@ -1160,13 +1337,27 @@ impl Satisfaction { has_sig: false, }, Terminal::OrI(ref l, ref r) => { - let lnsat = Self::dissatisfy_helper(&l.node, stfr, root_has_sig, min_fn, thresh_fn); + let lnsat = Self::dissatisfy_helper( + &l.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + thresh_fn, + ); let dissat_1 = Satisfaction { stack: Witness::combine(lnsat.stack, Witness::push_1()), has_sig: lnsat.has_sig, }; - let rnsat = Self::dissatisfy_helper(&r.node, stfr, root_has_sig, min_fn, thresh_fn); + let rnsat = Self::dissatisfy_helper( + &r.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + thresh_fn, + ); let dissat_2 = Satisfaction { stack: Witness::combine(rnsat.stack, Witness::push_0()), has_sig: rnsat.has_sig, @@ -1177,8 +1368,14 @@ impl Satisfaction { } Terminal::Thresh(_, ref subs) => Satisfaction { stack: subs.iter().fold(Witness::empty(), |acc, sub| { - let nsat = - Self::dissatisfy_helper(&sub.node, stfr, root_has_sig, min_fn, thresh_fn); + let nsat = Self::dissatisfy_helper( + &sub.node, + stfr, + root_has_sig, + leaf_hash, + min_fn, + thresh_fn, + ); assert!(!nsat.has_sig); Witness::combine(nsat.stack, acc) }), @@ -1204,11 +1401,13 @@ impl Satisfaction { term: &Terminal, stfr: &Sat, root_has_sig: bool, + leaf_hash: &TapLeafHash, ) -> Self { Self::satisfy_helper( term, stfr, root_has_sig, + leaf_hash, &mut Satisfaction::minimum, &mut Satisfaction::thresh, ) @@ -1223,11 +1422,13 @@ impl Satisfaction { term: &Terminal, stfr: &Sat, root_has_sig: bool, + leaf_hash: &TapLeafHash, ) -> Self { Self::satisfy_helper( term, stfr, root_has_sig, + leaf_hash, &mut Satisfaction::minimum_mall, &mut Satisfaction::thresh_mall, ) From 48f6bb0ee4c5abaa1c3c7953fe40167d9c989f25 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Mon, 27 Dec 2021 04:49:00 +0530 Subject: [PATCH 06/12] Add Tr descriptor Fix TapTree iter depth Fix tr spend_info lock * Attempt to read cache prior to matching on spend_info Option * This bug would cause function to hang * Also added test tr_script_pubkey Co-authored-by: nickfarrow --- src/descriptor/mod.rs | 45 +++- src/descriptor/tr.rs | 465 ++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 15 ++ src/policy/mod.rs | 1 + 4 files changed, 508 insertions(+), 18 deletions(-) diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 372a19734..cb9760a8a 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -30,8 +30,7 @@ use std::{ }; use bitcoin::blockdata::witness::Witness; -use bitcoin::secp256k1; -use bitcoin::{self, Script}; +use bitcoin::{self, secp256k1, Script}; use self::checksum::verify_checksum; use expression; @@ -47,6 +46,7 @@ mod segwitv0; mod sh; mod sortedmulti; mod tr; + // Descriptor Exports pub use self::bare::{Bare, Pkh}; pub use self::segwitv0::{Wpkh, Wsh, WshInner}; @@ -184,6 +184,8 @@ pub enum Descriptor { Sh(Sh), /// Pay-to-Witness-ScriptHash with Segwitv0 context Wsh(Wsh), + /// Pay-to-Taproot + Tr(Tr), } /// Descriptor Type of the descriptor @@ -209,6 +211,8 @@ pub enum DescriptorType { WshSortedMulti, /// Sh Wsh Sorted Multi ShWshSortedMulti, + /// Tr Descriptor + Tr, } impl Descriptor { @@ -295,6 +299,12 @@ impl Descriptor { Ok(Descriptor::Wsh(Wsh::new_sortedmulti(k, pks)?)) } + /// Create new tr descriptor + /// Errors when miniscript exceeds resource limits under Tap context + pub fn new_tr(key: Pk, script: Option>) -> Result { + Ok(Descriptor::Tr(Tr::new(key, script)?)) + } + /// Get the [DescriptorType] of [Descriptor] pub fn desc_type(&self) -> DescriptorType { match *self { @@ -314,6 +324,7 @@ impl Descriptor { WshInner::SortedMulti(ref _smv) => DescriptorType::WshSortedMulti, WshInner::Ms(ref _ms) => DescriptorType::Wsh, }, + Descriptor::Tr(ref _tr) => DescriptorType::Tr, } } } @@ -350,6 +361,9 @@ impl TranslatePk for Descriptor

{ Descriptor::Wsh(ref wsh) => { Descriptor::Wsh(wsh.translate_pk(&mut translatefpk, &mut translatefpkh)?) } + Descriptor::Tr(ref tr) => { + Descriptor::Tr(tr.translate_pk(&mut translatefpk, &mut translatefpkh)?) + } }; Ok(desc) } @@ -371,6 +385,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.sanity_check(), Descriptor::Wsh(ref wsh) => wsh.sanity_check(), Descriptor::Sh(ref sh) => sh.sanity_check(), + Descriptor::Tr(ref tr) => tr.sanity_check(), } } /// Computes the Bitcoin address of the descriptor, if one exists @@ -384,6 +399,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.address(network), Descriptor::Wsh(ref wsh) => wsh.address(network), Descriptor::Sh(ref sh) => sh.address(network), + Descriptor::Tr(ref tr) => tr.address(network), } } @@ -398,6 +414,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.script_pubkey(), Descriptor::Wsh(ref wsh) => wsh.script_pubkey(), Descriptor::Sh(ref sh) => sh.script_pubkey(), + Descriptor::Tr(ref tr) => tr.script_pubkey(), } } @@ -419,6 +436,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.unsigned_script_sig(), Descriptor::Wsh(ref wsh) => wsh.unsigned_script_sig(), Descriptor::Sh(ref sh) => sh.unsigned_script_sig(), + Descriptor::Tr(ref tr) => tr.unsigned_script_sig(), } } @@ -438,6 +456,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.explicit_script(), Descriptor::Wsh(ref wsh) => wsh.explicit_script(), Descriptor::Sh(ref sh) => sh.explicit_script(), + Descriptor::Tr(ref tr) => tr.explicit_script(), } } @@ -455,6 +474,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.get_satisfaction(satisfier), Descriptor::Wsh(ref wsh) => wsh.get_satisfaction(satisfier), Descriptor::Sh(ref sh) => sh.get_satisfaction(satisfier), + Descriptor::Tr(ref tr) => tr.get_satisfaction(satisfier), } } @@ -472,6 +492,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.get_satisfaction_mall(satisfier), Descriptor::Wsh(ref wsh) => wsh.get_satisfaction_mall(satisfier), Descriptor::Sh(ref sh) => sh.get_satisfaction_mall(satisfier), + Descriptor::Tr(ref tr) => tr.get_satisfaction_mall(satisfier), } } @@ -486,6 +507,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.max_satisfaction_weight(), Descriptor::Wsh(ref wsh) => wsh.max_satisfaction_weight(), Descriptor::Sh(ref sh) => sh.max_satisfaction_weight(), + Descriptor::Tr(ref tr) => tr.max_satisfaction_weight(), } } @@ -504,6 +526,7 @@ impl DescriptorTrait for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.script_code(), Descriptor::Wsh(ref wsh) => wsh.script_code(), Descriptor::Sh(ref sh) => sh.script_code(), + Descriptor::Tr(ref tr) => tr.script_code(), } } } @@ -520,6 +543,7 @@ impl ForEachKey for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.for_each_key(pred), Descriptor::Wsh(ref wsh) => wsh.for_each_key(pred), Descriptor::Sh(ref sh) => sh.for_each_key(pred), + Descriptor::Tr(ref tr) => tr.for_each_key(pred), } } } @@ -610,6 +634,7 @@ where ("wpkh", 1) => Descriptor::Wpkh(Wpkh::from_tree(top)?), ("sh", 1) => Descriptor::Sh(Sh::from_tree(top)?), ("wsh", 1) => Descriptor::Wsh(Wsh::from_tree(top)?), + ("tr", _) => Descriptor::Tr(Tr::from_tree(top)?), _ => Descriptor::Bare(Bare::from_tree(top)?), }) } @@ -639,6 +664,7 @@ impl fmt::Debug for Descriptor { Descriptor::Wpkh(ref wpkh) => write!(f, "{:?}", wpkh), Descriptor::Sh(ref sub) => write!(f, "{:?}", sub), Descriptor::Wsh(ref sub) => write!(f, "{:?}", sub), + Descriptor::Tr(ref tr) => write!(f, "{:?}", tr), } } } @@ -651,6 +677,7 @@ impl fmt::Display for Descriptor { Descriptor::Wpkh(ref wpkh) => write!(f, "{}", wpkh), Descriptor::Sh(ref sub) => write!(f, "{}", sub), Descriptor::Wsh(ref sub) => write!(f, "{}", sub), + Descriptor::Tr(ref tr) => write!(f, "{}", tr), } } } @@ -665,7 +692,7 @@ mod tests { use bitcoin::blockdata::opcodes::all::{OP_CLTV, OP_CSV}; use bitcoin::blockdata::script::Instruction; use bitcoin::blockdata::{opcodes, script}; - use bitcoin::hashes::hex::FromHex; + use bitcoin::hashes::hex::{FromHex, ToHex}; use bitcoin::hashes::{hash160, sha256}; use bitcoin::util::bip32; use bitcoin::{self, secp256k1, EcdsaSigHashType, PublicKey}; @@ -1165,6 +1192,18 @@ mod tests { ) } + #[test] + fn tr_script_pubkey() { + let key = Descriptor::::from_str( + "tr(02e20e746af365e86647826397ba1c0e0d5cb685752976fe2f326ab76bdc4d6ee9)", + ) + .unwrap(); + assert_eq!( + key.script_pubkey().to_hex(), + "51209c19294f03757da3dc235a5960631e3c55751632f5889b06b7a053bdc0bcfbcb" + ) + } + #[test] fn roundtrip_tests() { let descriptor = Descriptor::::from_str("multi"); diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 3cf03eb2c..ec1619b67 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -1,11 +1,23 @@ // Tapscript +use policy::semantic::Policy; +use policy::Liftable; +use util::{varint_len, witness_size}; +use {DescriptorTrait, ForEach, ForEachKey, Satisfier, ToPublicKey, TranslatePk}; + use super::checksum::{desc_checksum, verify_checksum}; +use bitcoin::blockdata::opcodes; +use bitcoin::util::taproot::{ + LeafVersion, TaprootBuilder, TaprootBuilderError, TaprootSpendInfo, TAPROOT_CONTROL_BASE_SIZE, + TAPROOT_CONTROL_NODE_SIZE, +}; +use bitcoin::{self, secp256k1, Script}; use errstr; use expression::{self, FromTree, Tree}; use miniscript::{limits::TAPROOT_MAX_NODE_COUNT, Miniscript}; -use std::cmp::max; -use std::sync::Arc; +use std::cmp::{self, max}; +use std::hash; +use std::sync::{Arc, RwLock}; use std::{fmt, str::FromStr}; use Tap; use {Error, MiniscriptKey}; @@ -13,21 +25,77 @@ use {Error, MiniscriptKey}; /// A Taproot Tree representation. // Hidden leaves are not yet supported in descriptor spec. Conceptually, it should // be simple to integrate those here, but it is best to wait on core for the exact syntax. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum TapTree { /// A taproot tree structure Tree(Arc>, Arc>), /// A taproot leaf denoting a spending condition + // A new leaf version would require a new Context, therefore there is no point + // in adding a LeafVersion with Leaf type here. All Miniscripts right now + // are of Leafversion::default Leaf(Arc>), } /// A taproot descriptor -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct Tr { /// A taproot internal key internal_key: Pk, /// Optional Taproot Tree with spending conditions tree: Option>, + /// Optional spending information associated with the descriptor + /// This will be [`None`] when the descriptor is not derived. + /// This information will be cached automatically when it is required + // + // It is also possible to wrap this in a further Arc so that the cache + // is shared across clones of the this descriptor. But this (slightly) complicates + // the code, and it might be desirable for clone to keep separate caches + spend_info: RwLock>>, +} + +impl Clone for Tr { + fn clone(&self) -> Self { + Self { + internal_key: self.internal_key.clone(), + tree: self.tree.clone(), + // Cloning creates a new instance of RwLock + spend_info: RwLock::new(None), + } + } +} + +impl PartialEq for Tr { + fn eq(&self, other: &Self) -> bool { + self.internal_key == other.internal_key && self.tree == other.tree + } +} + +impl Eq for Tr {} + +impl PartialOrd for Tr { + fn partial_cmp(&self, other: &Self) -> Option { + match self.internal_key.partial_cmp(&other.internal_key) { + Some(cmp::Ordering::Equal) => {} + ord => return ord, + } + self.tree.partial_cmp(&other.tree) + } +} + +impl Ord for Tr { + fn cmp(&self, other: &Self) -> cmp::Ordering { + match self.internal_key.cmp(&other.internal_key) { + cmp::Ordering::Equal => {} + ord => return ord, + } + self.tree.cmp(&other.tree) + } +} + +impl hash::Hash for Tr { + fn hash(&self, state: &mut H) { + self.internal_key.hash(state); + self.tree.hash(state); + } } impl TapTree { @@ -39,13 +107,38 @@ impl TapTree { TapTree::Tree(ref left_tree, ref right_tree) => { 1 + max(left_tree.taptree_height(), right_tree.taptree_height()) } - TapTree::Leaf(_) => 1, + TapTree::Leaf(..) => 1, } } /// Iterate over all miniscripts pub fn iter(&self) -> TapTreeIter { - TapTreeIter { stack: vec![self] } + TapTreeIter { + stack: vec![(0, self)], + } + } + + // Helper function to translate keys + fn translate_helper( + &self, + translatefpk: &mut FPk, + translatefpkh: &mut FPkh, + ) -> Result, Error> + where + FPk: FnMut(&Pk) -> Result, + FPkh: FnMut(&Pk::Hash) -> Result, + Q: MiniscriptKey, + { + let frag = match self { + TapTree::Tree(l, r) => TapTree::Tree( + Arc::new(l.translate_helper(translatefpk, translatefpkh)?), + Arc::new(r.translate_helper(translatefpk, translatefpkh)?), + ), + TapTree::Leaf(ms) => { + TapTree::Leaf(Arc::new(ms.translate_pk(translatefpk, translatefpkh)?)) + } + }; + Ok(frag) } } @@ -58,13 +151,26 @@ impl fmt::Display for TapTree { } } +impl fmt::Debug for TapTree { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TapTree::Tree(ref left, ref right) => write!(f, "{{{:?},{:?}}}", *left, *right), + TapTree::Leaf(ref script) => write!(f, "{:?}", *script), + } + } +} + impl Tr { /// Create a new [`Tr`] descriptor from internal key and [`TapTree`] pub fn new(internal_key: Pk, tree: Option>) -> Result { let nodes = tree.as_ref().map(|t| t.taptree_height()).unwrap_or(0); if nodes <= TAPROOT_MAX_NODE_COUNT { - Ok(Self { internal_key, tree }) + Ok(Self { + internal_key, + tree, + spend_info: RwLock::new(None), + }) } else { Err(Error::MaxRecursiveDepthExceeded) } @@ -96,30 +202,135 @@ impl Tr { None => TapTreeIter { stack: vec![] }, } } + + // Compute the [`TaprootSpendInfo`] associated with this descriptor if spend data is [None] + // If spend data is already computed (i.e it is not None), this does not recompute it + // TaprootSpendInfo is only required for spending via the script paths. + fn spend_info(&self) -> Arc + where + Pk: ToPublicKey, + { + // If the value is already cache, read it + // read only panics if the lock is poisoned (meaning other thread having a lock panicked) + let spend_info = self + .spend_info + .read() + .expect("Lock poisoned") + .as_ref() + .map(Arc::clone); + + match spend_info { + Some(spend_info) => spend_info, + None => { + // Get a new secp context + // This would be cheap operation after static context support from upstream + let secp = secp256k1::Secp256k1::verification_only(); + // Key spend path with no merkle root + let data = if self.tree.is_none() { + TaprootSpendInfo::new_key_spend( + &secp, + self.internal_key.to_x_only_pubkey(), + None, + ) + } else { + let mut builder = TaprootBuilder::new(); + for (depth, ms) in self.iter_scripts() { + let script = ms.encode(); + builder = builder + .add_leaf(depth, script) + .expect("Computing spend data on a valid Tree should always succeed"); + } + // Assert builder cannot error here because we have a well formed descriptor + match builder.finalize(&secp, self.internal_key.to_x_only_pubkey()) { + Ok(data) => data, + Err(e) => match e { + TaprootBuilderError::InvalidMerkleTreeDepth(_) => { + unreachable!("Depth checked in struct construction") + } + TaprootBuilderError::NodeNotInDfsOrder => { + unreachable!("Insertion is called in DFS order") + } + TaprootBuilderError::OverCompleteTree => { + unreachable!("Taptree is a well formed tree") + } + TaprootBuilderError::InvalidInternalKey(_) => { + unreachable!("Internal key checked for validity") + } + TaprootBuilderError::IncompleteTree => { + unreachable!("Taptree is a well formed tree") + } + TaprootBuilderError::EmptyTree => { + unreachable!("Taptree is a well formed tree with atleast 1 element") + } + }, + } + }; + let spend_info = Arc::new(data); + *self.spend_info.write().expect("Lock poisoned") = Some(Arc::clone(&spend_info)); + spend_info + } + } + } +} + +impl Tr { + /// Obtain the corresponding script pubkey for this descriptor + /// Same as[`DescriptorTrait::script_pubkey`] for this descriptor + pub fn spk(&self) -> Script { + let output_key = self.spend_info().output_key(); + let builder = bitcoin::blockdata::script::Builder::new(); + builder + .push_opcode(opcodes::all::OP_PUSHNUM_1) + .push_slice(&output_key.serialize()) + .into_script() + } + + /// Obtain the corresponding script pubkey for this descriptor + /// Same as[`DescriptorTrait::address`] for this descriptor + pub fn addr(&self, network: bitcoin::Network) -> Result { + let spend_info = self.spend_info(); + Ok(bitcoin::Address::p2tr_tweaked( + spend_info.output_key(), + network, + )) + } } /// Iterator for Taproot structures -/// Yields the miniscript in a depth first walk +/// Yields a pair of (depth, miniscript) in a depth first walk +/// For example, this tree: +/// - N0 - +/// / \\ +/// N1 N2 +/// / \ / \\ +/// A B C N3 +/// / \\ +/// D E +/// would yield (2, A), (2, B), (2,C), (3, D), (3, E). +/// #[derive(Debug, Clone)] -pub struct TapTreeIter<'a, Pk: MiniscriptKey> where Pk: 'a { - stack: Vec<&'a TapTree>, +pub struct TapTreeIter<'a, Pk: MiniscriptKey> +where + Pk: 'a, +{ + stack: Vec<(usize, &'a TapTree)>, } impl<'a, Pk> Iterator for TapTreeIter<'a, Pk> where Pk: MiniscriptKey + 'a, { - type Item = &'a Miniscript; + type Item = (usize, &'a Miniscript); fn next(&mut self) -> Option { while !self.stack.is_empty() { - let last = self.stack.pop().expect("Size checked above"); + let (depth, last) = self.stack.pop().expect("Size checked above"); match &*last { TapTree::Tree(l, r) => { - self.stack.push(&r); - self.stack.push(&l); + self.stack.push((depth + 1, &r)); + self.stack.push((depth + 1, &l)); } - TapTree::Leaf(ref ms) => return Some(ms), + TapTree::Leaf(ref ms) => return Some((depth, ms)), } } None @@ -174,6 +385,7 @@ where Ok(Tr { internal_key: expression::terminal(key, Pk::from_str)?, tree: None, + spend_info: RwLock::new(None), }) } 2 => { @@ -189,6 +401,7 @@ where Ok(Tr { internal_key: expression::terminal(key, Pk::from_str)?, tree: Some(ret), + spend_info: RwLock::new(None), }) } _ => { @@ -225,6 +438,15 @@ where } } +impl fmt::Debug for Tr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.tree { + Some(ref s) => write!(f, "tr({:?},{:?})", self.internal_key, s), + None => write!(f, "tr({:?})", self.internal_key), + } + } +} + impl fmt::Display for Tr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let desc = self.to_string_no_checksum(); @@ -302,3 +524,216 @@ fn split_once(inp: &str, delim: char) -> Option<(&str, &str)> { }; return ret; } + +impl Liftable for TapTree { + fn lift(&self) -> Result, Error> { + fn lift_helper(s: &TapTree) -> Result, Error> { + match s { + TapTree::Tree(ref l, ref r) => { + Ok(Policy::Threshold(1, vec![lift_helper(l)?, lift_helper(r)?])) + } + TapTree::Leaf(ref leaf) => leaf.lift(), + } + } + + let pol = lift_helper(&self)?; + Ok(pol.normalized()) + } +} + +impl Liftable for Tr { + fn lift(&self) -> Result, Error> { + match &self.tree { + Some(root) => root.lift(), + None => Ok(Policy::KeyHash(self.internal_key.to_pubkeyhash())), + } + } +} + +impl DescriptorTrait for Tr { + fn sanity_check(&self) -> Result<(), Error> { + for (_depth, ms) in self.iter_scripts() { + ms.sanity_check()?; + } + Ok(()) + } + + fn address(&self, network: bitcoin::Network) -> Result + where + Pk: ToPublicKey, + { + self.addr(network) + } + + fn script_pubkey(&self) -> Script + where + Pk: ToPublicKey, + { + self.spk() + } + + fn unsigned_script_sig(&self) -> Script + where + Pk: ToPublicKey, + { + Script::new() + } + + fn explicit_script(&self) -> Result + where + Pk: ToPublicKey, + { + Err(Error::TrNoScriptCode) + } + + fn get_satisfaction(&self, satisfier: S) -> Result<(Vec>, Script), Error> + where + Pk: ToPublicKey, + S: Satisfier, + { + best_tap_spend(&self, satisfier, false /* allow_mall */) + } + + fn get_satisfaction_mall(&self, satisfier: S) -> Result<(Vec>, Script), Error> + where + Pk: ToPublicKey, + S: Satisfier, + { + best_tap_spend(&self, satisfier, true /* allow_mall */) + } + + fn max_satisfaction_weight(&self) -> Result { + let mut max_wieght = Some(65); + for (depth, ms) in self.iter_scripts() { + let script_size = ms.script_size(); + let max_sat_elems = match ms.max_satisfaction_witness_elements() { + Ok(elem) => elem, + Err(..) => continue, + }; + let max_sat_size = match ms.max_satisfaction_size() { + Ok(sz) => sz, + Err(..) => continue, + }; + let control_block_sz = control_block_len(depth); + let wit_size = 4 + // scriptSig len byte + control_block_sz + // first element control block + varint_len(script_size) + + script_size + // second element script len with prefix + varint_len(max_sat_elems) + + max_sat_size; // witness + max_wieght = cmp::max(max_wieght, Some(wit_size)); + } + max_wieght.ok_or(Error::ImpossibleSatisfaction) + } + + fn script_code(&self) -> Result + where + Pk: ToPublicKey, + { + Err(Error::TrNoScriptCode) + } +} + +impl ForEachKey for Tr { + fn for_each_key<'a, F: FnMut(ForEach<'a, Pk>) -> bool>(&'a self, mut pred: F) -> bool + where + Pk: 'a, + Pk::Hash: 'a, + { + let script_keys_res = self + .iter_scripts() + .all(|(_d, ms)| ms.for_any_key(&mut pred)); + script_keys_res && pred(ForEach::Key(&self.internal_key)) + } +} + +impl TranslatePk for Tr

{ + type Output = Tr; + + fn translate_pk( + &self, + mut translatefpk: Fpk, + mut translatefpkh: Fpkh, + ) -> Result + where + Fpk: FnMut(&P) -> Result, + Fpkh: FnMut(&P::Hash) -> Result, + Q: MiniscriptKey, + { + let translate_desc = Tr { + internal_key: translatefpk(&self.internal_key)?, + tree: match &self.tree { + Some(tree) => Some(tree.translate_helper(&mut translatefpk, &mut translatefpkh)?), + None => None, + }, + spend_info: RwLock::new(None), + }; + Ok(translate_desc) + } +} + +// Helper function to compute the len of control block at a given depth +fn control_block_len(depth: usize) -> usize { + TAPROOT_CONTROL_BASE_SIZE + depth * TAPROOT_CONTROL_NODE_SIZE +} + +// Helper function to get a script spend satisfaction +// try script spend +fn best_tap_spend( + desc: &Tr, + satisfier: S, + allow_mall: bool, +) -> Result<(Vec>, Script), Error> +where + Pk: ToPublicKey, + S: Satisfier, +{ + let spend_info = desc.spend_info(); + // First try the key spend path + if let Some(sig) = satisfier.lookup_tap_key_spend_sig() { + Ok((vec![sig.to_vec()], Script::new())) + } else { + // Since we have the complete descriptor we can ignore the satisfier. We don't use the control block + // map (lookup_control_block) from the satisfier here. + let (mut min_wit, mut min_wit_len) = (None, None); + for (depth, ms) in desc.iter_scripts() { + let mut wit = if allow_mall { + match ms.satisfy_malleable(&satisfier) { + Ok(wit) => wit, + Err(..) => continue, // No witness for this script in tr descriptor, look for next one + } + } else { + match ms.satisfy(&satisfier) { + Ok(wit) => wit, + Err(..) => continue, // No witness for this script in tr descriptor, look for next one + } + }; + // Compute the final witness size + // Control block len + script len + witnesssize + varint(wit.len + 2) + // The extra +2 elements are control block and script itself + let wit_size = witness_size(&wit) + + control_block_len(depth) + + ms.script_size() + + varint_len(ms.script_size()); + if min_wit_len.is_some() && Some(wit_size) > min_wit_len { + continue; + } else { + let leaf_script = (ms.encode(), LeafVersion::TapScript); + let control_block = spend_info + .control_block(&leaf_script) + .expect("Control block must exist in script map for every known leaf"); + wit.push(leaf_script.0.into_bytes()); // Push the leaf script + // There can be multiple control blocks for a (script, ver) pair + // Find the smallest one amongst those + wit.push(control_block.serialize()); + // Finally, save the minimum + min_wit = Some(wit); + min_wit_len = Some(wit_size); + } + } + match min_wit { + Some(wit) => Ok((wit, Script::new())), + None => Err(Error::CouldNotSatisfy), // Could not satisfy all miniscripts inside Tr + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 272908c43..39003e00b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -545,6 +545,12 @@ pub enum Error { BareDescriptorAddr, /// PubKey invalid under current context PubKeyCtxError(miniscript::decode::KeyParseError, &'static str), + /// Attempted to call function that requires PreComputed taproot info + TaprootSpendInfoUnavialable, + /// No script code for Tr descriptors + TrNoScriptCode, + /// No explicit script for Tr descriptors + TrNoExplicitScript, } #[doc(hidden)] @@ -676,6 +682,15 @@ impl fmt::Display for Error { Error::MultiATooManyKeys(k) => { write!(f, "MultiA too many keys {}", k) } + Error::TaprootSpendInfoUnavialable => { + write!(f, "Taproot Spend Info not computed. Hint: Did you call `compute_spend_info` before calling methods from DescriptorTrait") + } + Error::TrNoScriptCode => { + write!(f, "No script code for Tr descriptors") + } + Error::TrNoExplicitScript => { + write!(f, "No script code for Tr descriptors") + } } } } diff --git a/src/policy/mod.rs b/src/policy/mod.rs index ac36846de..fe1fef3eb 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -181,6 +181,7 @@ impl Liftable for Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.lift(), Descriptor::Wsh(ref wsh) => wsh.lift(), Descriptor::Sh(ref sh) => sh.lift(), + Descriptor::Tr(ref _tr) => unimplemented!(), } } } From 82ab565f6ba45ac37f67b334171d3b5b8a9d11e9 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Mon, 27 Dec 2021 04:53:17 +0530 Subject: [PATCH 07/12] Add psbt finalizer support --- src/psbt/finalizer.rs | 122 ++++++++++++++++++++++++++++++++++++------ src/psbt/mod.rs | 8 +++ src/util.rs | 14 +++++ 3 files changed, 128 insertions(+), 16 deletions(-) diff --git a/src/psbt/finalizer.rs b/src/psbt/finalizer.rs index fbf0fffa5..8f8e2b9cb 100644 --- a/src/psbt/finalizer.rs +++ b/src/psbt/finalizer.rs @@ -19,16 +19,82 @@ //! `https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki` //! +use bitcoin::schnorr::XOnlyPublicKey; +use util::{script_is_v1_tr, witness_size}; + use super::{sanity_check, Psbt}; use super::{Error, InputError, PsbtInputSatisfier}; use bitcoin::blockdata::witness::Witness; use bitcoin::secp256k1::{self, Secp256k1}; +use bitcoin::util::taproot::LeafVersion; use bitcoin::{self, PublicKey, Script}; use descriptor::DescriptorTrait; use interpreter; use Descriptor; use Miniscript; -use {BareCtx, Legacy, Segwitv0}; +use Satisfier; +use {BareCtx, Legacy, Segwitv0, Tap}; + +// Satisfy the taproot descriptor. It is not possible to infer the complete +// descriptor from psbt because the information about all the scripts might not +// be present. Also, currently the spec does not support hidden branches, so +// inferring a descriptor is not possible +fn construct_tap_witness( + spk: &Script, + sat: &PsbtInputSatisfier, + allow_mall: bool, +) -> Result>, InputError> { + assert!(script_is_v1_tr(&spk)); + + // try the script spend path first + if let Some(sig) = + >::lookup_tap_key_spend_sig(sat) + { + return Ok(vec![sig.to_vec()]); + } + // Next script spends + let (mut min_wit, mut min_wit_len) = (None, None); + if let Some(block_map) = + >::lookup_tap_control_block_map(sat) + { + for (control_block, (script, ver)) in block_map { + if *ver != LeafVersion::TapScript { + // We don't know how to satisfy non default version scripts yet + continue; + } + let ms = match Miniscript::::parse_insane(script) { + Ok(ms) => ms, + Err(..) => continue, // try another script + }; + let mut wit = if allow_mall { + match ms.satisfy_malleable(sat) { + Ok(ms) => ms, + Err(..) => continue, + } + } else { + match ms.satisfy(sat) { + Ok(ms) => ms, + Err(..) => continue, + } + }; + wit.push(ms.encode().into_bytes()); + wit.push(control_block.serialize()); + let wit_len = Some(witness_size(&wit)); + if min_wit_len.is_some() && wit_len > min_wit_len { + continue; + } else { + // store the minimum + min_wit = Some(wit); + min_wit_len = wit_len; + } + } + min_wit.ok_or(InputError::CouldNotSatisfyTr) + } else { + // No control blocks found + Err(InputError::CouldNotSatisfyTr) + } +} + // Get the scriptpubkey for the psbt input fn get_scriptpubkey(psbt: &Psbt, index: usize) -> Result<&Script, InputError> { let script_pubkey; @@ -299,16 +365,28 @@ pub fn finalize_helper( // Actually construct the witnesses for index in 0..psbt.inputs.len() { - // Get a descriptor for this input - let desc = get_descriptor(&psbt, index).map_err(|e| Error::InputError(e, index))?; + let (witness, script_sig) = { + let spk = get_scriptpubkey(psbt, index).map_err(|e| Error::InputError(e, index))?; + let sat = PsbtInputSatisfier::new(&psbt, index); - //generate the satisfaction witness and scriptsig - let (witness, script_sig) = if !allow_mall { - desc.get_satisfaction(PsbtInputSatisfier::new(&psbt, index)) - } else { - desc.get_satisfaction_mall(PsbtInputSatisfier::new(&psbt, index)) - } - .map_err(|e| Error::InputError(InputError::MiniscriptError(e), index))?; + if script_is_v1_tr(spk) { + // Deal with tr case separately, unfortunately we cannot infer the full descriptor for Tr + let wit = construct_tap_witness(spk, &sat, allow_mall) + .map_err(|e| Error::InputError(e, index))?; + (wit, Script::new()) + } else { + // Get a descriptor for this input. + let desc = get_descriptor(&psbt, index).map_err(|e| Error::InputError(e, index))?; + + //generate the satisfaction witness and scriptsig + if !allow_mall { + desc.get_satisfaction(PsbtInputSatisfier::new(&psbt, index)) + } else { + desc.get_satisfaction_mall(PsbtInputSatisfier::new(&psbt, index)) + } + .map_err(|e| Error::InputError(InputError::MiniscriptError(e), index))? + } + }; let input = &mut psbt.inputs[index]; //Fill in the satisfactions @@ -323,12 +401,24 @@ pub fn finalize_helper( Some(witness) }; //reset everything - input.redeem_script = None; - input.partial_sigs.clear(); - input.sighash_type = None; - input.redeem_script = None; - input.bip32_derivation.clear(); - input.witness_script = None; + input.partial_sigs.clear(); // 0x02 + input.sighash_type = None; // 0x03 + input.redeem_script = None; // 0x04 + input.witness_script = None; // 0x05 + input.bip32_derivation.clear(); // 0x05 + // finalized witness 0x06 and 0x07 are not clear + // 0x09 Proof of reserves not yet supported + input.ripemd160_preimages.clear(); // 0x0a + input.sha256_preimages.clear(); // 0x0b + input.hash160_preimages.clear(); // 0x0c + input.hash256_preimages.clear(); // 0x0d + // psbt v2 fields till 0x012 not supported + input.tap_key_sig = None; // 0x013 + input.tap_script_sigs.clear(); // 0x014 + input.tap_scripts.clear(); // 0x015 + input.tap_key_origins.clear(); // 0x16 + input.tap_internal_key = None; // x017 + input.tap_merkle_root = None; // 0x018 } // Double check everything with the interpreter // This only checks whether the script will be executed diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index fcf64b539..c1bc56e86 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -45,6 +45,11 @@ pub enum InputError { SecpErr(bitcoin::secp256k1::Error), /// Key errors KeyErr(bitcoin::util::key::Error), + /// Could not satisfy taproot descriptor + /// This error is returned when both script path and key paths could not be + /// satisfied. We cannot return a detailed error because we try all miniscripts + /// in script spend path, we cannot know which miniscript failed. + CouldNotSatisfyTr, /// Error doing an interpreter-check on a finalized psbt Interpreter(interpreter::Error), /// Redeem script does not match the p2sh hash @@ -160,6 +165,9 @@ impl fmt::Display for InputError { sighashflag {:?} rather than required {:?}", pubkey.key, got, required ), + InputError::CouldNotSatisfyTr => { + write!(f, "Could not satisfy Tr descriptor") + } } } } diff --git a/src/util.rs b/src/util.rs index 16feeeaa3..a353fc46a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,7 @@ use bitcoin; +use bitcoin::blockdata::opcodes; use bitcoin::blockdata::script; +use bitcoin::blockdata::script::Instruction; use bitcoin::Script; use miniscript::context; @@ -46,3 +48,15 @@ impl MsKeyBuilder for script::Builder { } } } + +// This belongs in rust-bitcoin, make this upstream later +pub(crate) fn script_is_v1_tr(s: &Script) -> bool { + let mut iter = s.instructions_minimal(); + if s.len() != 32 + || iter.next() != Some(Ok(Instruction::Op(opcodes::all::OP_PUSHNUM_1))) + || iter.next() != Some(Ok(Instruction::Op(opcodes::all::OP_PUSHBYTES_32))) + { + return false; + } + return true; +} From f5c8d7ff5dd885508a2d4fbed6a4876b972a7a1c Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Mon, 27 Dec 2021 05:36:29 +0530 Subject: [PATCH 08/12] implement satisfier for psbt --- src/psbt/finalizer.rs | 2 +- src/psbt/mod.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/psbt/finalizer.rs b/src/psbt/finalizer.rs index 8f8e2b9cb..73a94037d 100644 --- a/src/psbt/finalizer.rs +++ b/src/psbt/finalizer.rs @@ -19,12 +19,12 @@ //! `https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki` //! -use bitcoin::schnorr::XOnlyPublicKey; use util::{script_is_v1_tr, witness_size}; use super::{sanity_check, Psbt}; use super::{Error, InputError, PsbtInputSatisfier}; use bitcoin::blockdata::witness::Witness; +use bitcoin::schnorr::XOnlyPublicKey; use bitcoin::secp256k1::{self, Secp256k1}; use bitcoin::util::taproot::LeafVersion; use bitcoin::{self, PublicKey, Script}; diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index c1bc56e86..498c68096 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -19,6 +19,7 @@ //! `https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki` //! +use std::collections::BTreeMap; use std::{error, fmt}; use bitcoin; @@ -28,6 +29,7 @@ use bitcoin::secp256k1::{self, Secp256k1}; use bitcoin::util::psbt::PartiallySignedTransaction as Psbt; use bitcoin::Script; +use bitcoin::util::taproot::{ControlBlock, LeafVersion, TapLeafHash}; use interpreter; use miniscript::limits::SEQUENCE_LOCKTIME_DISABLE_FLAG; use miniscript::satisfy::{After, Older}; @@ -232,6 +234,37 @@ impl<'psbt> PsbtInputSatisfier<'psbt> { } impl<'psbt, Pk: MiniscriptKey + ToPublicKey> Satisfier for PsbtInputSatisfier<'psbt> { + fn lookup_tap_key_spend_sig(&self) -> Option { + self.psbt.inputs[self.index].tap_key_sig + } + + fn lookup_tap_leaf_script_sig(&self, pk: &Pk, lh: &TapLeafHash) -> Option { + self.psbt.inputs[self.index] + .tap_script_sigs + .get(&(pk.to_x_only_pubkey(), *lh)) + .map(|x| *x) // replace by copied in 1.36 + } + + fn lookup_tap_control_block_map( + &self, + ) -> Option<&BTreeMap> { + Some(&self.psbt.inputs[self.index].tap_scripts) + } + + fn lookup_pkh_tap_leaf_script_sig( + &self, + pkh: &(Pk::Hash, TapLeafHash), + ) -> Option<(bitcoin::secp256k1::XOnlyPublicKey, bitcoin::SchnorrSig)> { + self.psbt.inputs[self.index] + .tap_script_sigs + .iter() + .filter(|&((pubkey, lh), _sig)| { + pubkey.to_pubkeyhash() == Pk::hash_to_hash160(&pkh.0) && *lh == pkh.1 + }) + .next() + .map(|((x_only_pk, _leaf_hash), sig)| (*x_only_pk, *sig)) + } + fn lookup_ecdsa_sig(&self, pk: &Pk) -> Option { self.psbt.inputs[self.index] .partial_sigs From b1cb8e081f0dcb2deb20a83e8b520a9b17f8e462 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 7 Feb 2022 23:59:08 +0000 Subject: [PATCH 09/12] replace length checks with is_empty --- src/descriptor/tr.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index ec1619b67..61a7a00cf 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -354,11 +354,11 @@ where <::Hash as FromStr>::Err: ToString, { match tree { - Tree { name, args } if name.len() > 0 && args.len() == 0 => { + Tree { name, args } if !name.is_empty() && args.is_empty() => { let script = Miniscript::::from_str(name)?; Ok(TapTree::Leaf(Arc::new(script))) } - Tree { name, args } if name.len() == 0 && args.len() == 2 => { + Tree { name, args } if name.is_empty() && args.len() == 2 => { let left = parse_tr_script_spend(&args[0])?; let right = parse_tr_script_spend(&args[1])?; Ok(TapTree::Tree(Arc::new(left), Arc::new(right))) @@ -376,7 +376,7 @@ where match top.args.len() { 1 => { let key = &top.args[0]; - if key.args.len() > 0 { + if !key.args.is_empty() { return Err(Error::Unexpected(format!( "#{} script associated with `key-path` while parsing taproot descriptor", key.args.len() @@ -390,7 +390,7 @@ where } 2 => { let ref key = top.args[0]; - if key.args.len() > 0 { + if !key.args.is_empty() { return Err(Error::Unexpected(format!( "#{} script associated with `key-path` while parsing taproot descriptor", key.args.len() @@ -505,7 +505,7 @@ fn parse_tr_tree(s: &str) -> Result { } fn split_once(inp: &str, delim: char) -> Option<(&str, &str)> { - let ret = if inp.len() == 0 { + let ret = if inp.is_empty() { None } else { let mut found = inp.len(); From 5b5bf4654f817a895f2ce0e872461dd396d44587 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 7 Feb 2022 23:59:51 +0000 Subject: [PATCH 10/12] replace manual 'ch > 0x7f' checks with `is_ascii` --- src/descriptor/tr.rs | 2 +- src/expression.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 61a7a00cf..099e5677e 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -458,7 +458,7 @@ impl fmt::Display for Tr { // Helper function to parse string into miniscript tree form fn parse_tr_tree(s: &str) -> Result { for ch in s.bytes() { - if ch > 0x7f { + if !ch.is_ascii() { return Err(Error::Unprintable(ch)); } } diff --git a/src/expression.rs b/src/expression.rs index 1cef61409..59dd7acc0 100644 --- a/src/expression.rs +++ b/src/expression.rs @@ -213,7 +213,7 @@ impl<'a> Tree<'a> { // Filter out non-ASCII because we byte-index strings all over the // place and Rust gets very upset when you splinch a string. for ch in s.bytes() { - if ch > 0x7f { + if !ch.is_ascii() { return Err(Error::Unprintable(ch)); } } From 954b12ff31005297cfd455266f5725ea027bce61 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 8 Feb 2022 00:01:47 +0000 Subject: [PATCH 11/12] qualify `Tree` symbol There are multiple `Tree`s in this file and the expression Tree is a somewhat obscure one. It is confusing to have it referenced without any qualification. --- src/descriptor/tr.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 099e5677e..0b676984d 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -13,7 +13,7 @@ use bitcoin::util::taproot::{ }; use bitcoin::{self, secp256k1, Script}; use errstr; -use expression::{self, FromTree, Tree}; +use expression::{self, FromTree}; use miniscript::{limits::TAPROOT_MAX_NODE_COUNT, Miniscript}; use std::cmp::{self, max}; use std::hash; @@ -344,9 +344,11 @@ where ::Err: ToString, <::Hash as FromStr>::Err: ToString, { - fn from_tree(top: &Tree) -> Result { + fn from_tree(top: &expression::Tree) -> Result { // Helper function to parse taproot script path - fn parse_tr_script_spend(tree: &Tree) -> Result, Error> + fn parse_tr_script_spend( + tree: &expression::Tree, + ) -> Result, Error> where Pk: MiniscriptKey + FromStr, Pk::Hash: FromStr, @@ -354,11 +356,11 @@ where <::Hash as FromStr>::Err: ToString, { match tree { - Tree { name, args } if !name.is_empty() && args.is_empty() => { + expression::Tree { name, args } if !name.is_empty() && args.is_empty() => { let script = Miniscript::::from_str(name)?; Ok(TapTree::Leaf(Arc::new(script))) } - Tree { name, args } if name.is_empty() && args.len() == 2 => { + expression::Tree { name, args } if name.is_empty() && args.len() == 2 => { let left = parse_tr_script_spend(&args[0])?; let right = parse_tr_script_spend(&args[1])?; Ok(TapTree::Tree(Arc::new(left), Arc::new(right))) @@ -456,7 +458,7 @@ impl fmt::Display for Tr { } // Helper function to parse string into miniscript tree form -fn parse_tr_tree(s: &str) -> Result { +fn parse_tr_tree(s: &str) -> Result { for ch in s.bytes() { if !ch.is_ascii() { return Err(Error::Unprintable(ch)); @@ -466,11 +468,11 @@ fn parse_tr_tree(s: &str) -> Result { let ret = if s.len() > 3 && &s[..3] == "tr(" && s.as_bytes()[s.len() - 1] == b')' { let rest = &s[3..s.len() - 1]; if !rest.contains(',') { - let internal_key = Tree { + let internal_key = expression::Tree { name: rest, args: vec![], }; - return Ok(Tree { + return Ok(expression::Tree { name: "tr", args: vec![internal_key], }); @@ -479,19 +481,19 @@ fn parse_tr_tree(s: &str) -> Result { let (key, script) = split_once(rest, ',') .ok_or_else(|| Error::BadDescriptor("invalid taproot descriptor".to_string()))?; - let internal_key = Tree { + let internal_key = expression::Tree { name: key, args: vec![], }; if script.is_empty() { - return Ok(Tree { + return Ok(expression::Tree { name: "tr", args: vec![internal_key], }); } let (tree, rest) = expression::Tree::from_slice_helper_curly(script, 1)?; if rest.is_empty() { - Ok(Tree { + Ok(expression::Tree { name: "tr", args: vec![internal_key, tree], }) From 7195bd733e5fd92e2ea7e7d64d038b3e0a22dbbb Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 8 Feb 2022 19:13:17 +0000 Subject: [PATCH 12/12] tr: clean up TaprootSpendInfo locking logic 1. `RwLock` is typically much slower than `Mutex`, so the Rust developers discourage its use unless you really need lots of simultaneous readers. We do not, we only need to unlock this mutex for tiny amounts of time locking logic 2. The `clone` implementation missed an opportunity to clone an internal `Arc`, instead using `Mutex::new(None)` and forcing the clone to recompute the spend info. 3. Minor idiomatic cleanups. --- src/descriptor/tr.rs | 124 ++++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 66 deletions(-) diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 0b676984d..dc730c3be 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -17,7 +17,7 @@ use expression::{self, FromTree}; use miniscript::{limits::TAPROOT_MAX_NODE_COUNT, Miniscript}; use std::cmp::{self, max}; use std::hash; -use std::sync::{Arc, RwLock}; +use std::sync::{Arc, Mutex}; use std::{fmt, str::FromStr}; use Tap; use {Error, MiniscriptKey}; @@ -46,19 +46,21 @@ pub struct Tr { /// This will be [`None`] when the descriptor is not derived. /// This information will be cached automatically when it is required // - // It is also possible to wrap this in a further Arc so that the cache - // is shared across clones of the this descriptor. But this (slightly) complicates - // the code, and it might be desirable for clone to keep separate caches - spend_info: RwLock>>, + // The inner `Arc` here is because Rust does not allow us to return a reference + // to the contents of the `Option` from inside a `MutexGuard`. There is no outer + // `Arc` because when this structure is cloned, we create a whole new mutex. + spend_info: Mutex>>, } impl Clone for Tr { fn clone(&self) -> Self { + // When cloning, construct a new Mutex so that distinct clones don't + // cause blocking between each other. We clone only the internal `Arc`, + // so the clone is always cheap (in both time and space) Self { internal_key: self.internal_key.clone(), tree: self.tree.clone(), - // Cloning creates a new instance of RwLock - spend_info: RwLock::new(None), + spend_info: Mutex::new(self.spend_info.lock().expect("Lock poisoned").clone()), } } } @@ -169,7 +171,7 @@ impl Tr { Ok(Self { internal_key, tree, - spend_info: RwLock::new(None), + spend_info: Mutex::new(None), }) } else { Err(Error::MaxRecursiveDepthExceeded) @@ -212,64 +214,54 @@ impl Tr { { // If the value is already cache, read it // read only panics if the lock is poisoned (meaning other thread having a lock panicked) - let spend_info = self - .spend_info - .read() - .expect("Lock poisoned") - .as_ref() - .map(Arc::clone); - - match spend_info { - Some(spend_info) => spend_info, - None => { - // Get a new secp context - // This would be cheap operation after static context support from upstream - let secp = secp256k1::Secp256k1::verification_only(); - // Key spend path with no merkle root - let data = if self.tree.is_none() { - TaprootSpendInfo::new_key_spend( - &secp, - self.internal_key.to_x_only_pubkey(), - None, - ) - } else { - let mut builder = TaprootBuilder::new(); - for (depth, ms) in self.iter_scripts() { - let script = ms.encode(); - builder = builder - .add_leaf(depth, script) - .expect("Computing spend data on a valid Tree should always succeed"); + let read_lock = self.spend_info.lock().expect("Lock poisoned"); + if let Some(ref spend_info) = *read_lock { + return spend_info.clone(); + } + drop(read_lock); + + // Get a new secp context + // This would be cheap operation after static context support from upstream + let secp = secp256k1::Secp256k1::verification_only(); + // Key spend path with no merkle root + let data = if self.tree.is_none() { + TaprootSpendInfo::new_key_spend(&secp, self.internal_key.to_x_only_pubkey(), None) + } else { + let mut builder = TaprootBuilder::new(); + for (depth, ms) in self.iter_scripts() { + let script = ms.encode(); + builder = builder + .add_leaf(depth, script) + .expect("Computing spend data on a valid Tree should always succeed"); + } + // Assert builder cannot error here because we have a well formed descriptor + match builder.finalize(&secp, self.internal_key.to_x_only_pubkey()) { + Ok(data) => data, + Err(e) => match e { + TaprootBuilderError::InvalidMerkleTreeDepth(_) => { + unreachable!("Depth checked in struct construction") + } + TaprootBuilderError::NodeNotInDfsOrder => { + unreachable!("Insertion is called in DFS order") + } + TaprootBuilderError::OverCompleteTree => { + unreachable!("Taptree is a well formed tree") } - // Assert builder cannot error here because we have a well formed descriptor - match builder.finalize(&secp, self.internal_key.to_x_only_pubkey()) { - Ok(data) => data, - Err(e) => match e { - TaprootBuilderError::InvalidMerkleTreeDepth(_) => { - unreachable!("Depth checked in struct construction") - } - TaprootBuilderError::NodeNotInDfsOrder => { - unreachable!("Insertion is called in DFS order") - } - TaprootBuilderError::OverCompleteTree => { - unreachable!("Taptree is a well formed tree") - } - TaprootBuilderError::InvalidInternalKey(_) => { - unreachable!("Internal key checked for validity") - } - TaprootBuilderError::IncompleteTree => { - unreachable!("Taptree is a well formed tree") - } - TaprootBuilderError::EmptyTree => { - unreachable!("Taptree is a well formed tree with atleast 1 element") - } - }, + TaprootBuilderError::InvalidInternalKey(_) => { + unreachable!("Internal key checked for validity") } - }; - let spend_info = Arc::new(data); - *self.spend_info.write().expect("Lock poisoned") = Some(Arc::clone(&spend_info)); - spend_info + TaprootBuilderError::IncompleteTree => { + unreachable!("Taptree is a well formed tree") + } + TaprootBuilderError::EmptyTree => { + unreachable!("Taptree is a well formed tree with atleast 1 element") + } + }, } - } + }; + let spend_info = Arc::new(data); + *self.spend_info.lock().expect("Lock poisoned") = Some(spend_info.clone()); + spend_info } } @@ -387,7 +379,7 @@ where Ok(Tr { internal_key: expression::terminal(key, Pk::from_str)?, tree: None, - spend_info: RwLock::new(None), + spend_info: Mutex::new(None), }) } 2 => { @@ -403,7 +395,7 @@ where Ok(Tr { internal_key: expression::terminal(key, Pk::from_str)?, tree: Some(ret), - spend_info: RwLock::new(None), + spend_info: Mutex::new(None), }) } _ => { @@ -668,7 +660,7 @@ impl TranslatePk for Tr

{ Some(tree) => Some(tree.translate_helper(&mut translatefpk, &mut translatefpkh)?), None => None, }, - spend_info: RwLock::new(None), + spend_info: Mutex::new(None), }; Ok(translate_desc) }