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 eb74eef2b..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; @@ -46,14 +45,18 @@ 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, @@ -86,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; @@ -111,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> @@ -147,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)) @@ -157,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; } @@ -175,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 @@ -200,6 +211,8 @@ pub enum DescriptorType { WshSortedMulti, /// Sh Wsh Sorted Multi ShWshSortedMulti, + /// Tr Descriptor + Tr, } impl Descriptor { @@ -286,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 { @@ -305,6 +324,7 @@ impl Descriptor { WshInner::SortedMulti(ref _smv) => DescriptorType::WshSortedMulti, WshInner::Ms(ref _ms) => DescriptorType::Wsh, }, + Descriptor::Tr(ref _tr) => DescriptorType::Tr, } } } @@ -341,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) } @@ -362,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 @@ -375,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), } } @@ -389,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(), } } @@ -410,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(), } } @@ -417,7 +444,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, { @@ -427,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(), } } @@ -444,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), } } @@ -461,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), } } @@ -475,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(), } } @@ -482,7 +515,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, { @@ -492,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(), } } } @@ -508,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), } } } @@ -598,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)?), }) } @@ -627,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), } } } @@ -639,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), } } } @@ -648,11 +687,12 @@ 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; 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}; @@ -1095,7 +1135,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(); @@ -1106,7 +1146,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(); @@ -1114,6 +1154,56 @@ 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 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"); @@ -1208,7 +1298,7 @@ mod tests { ) .unwrap(); assert_eq!( - *descriptor.script_code().as_bytes(), + *descriptor.script_code().unwrap().as_bytes(), Vec::::from_hex("76a9141d0f172a0ecb48aee1be1f2687d2963ae33f71a188ac").unwrap()[..] ); @@ -1218,7 +1308,7 @@ mod tests { ) .unwrap(); assert_eq!( - *descriptor.script_code().as_bytes(), + *descriptor.script_code().unwrap().as_bytes(), Vec::::from_hex("76a91479091972186c449eb1ded22b78e40d009bdf008988ac").unwrap()[..] ); @@ -1229,7 +1319,7 @@ mod tests { .unwrap(); assert_eq!( *descriptor - .script_code() + .script_code().unwrap() .as_bytes(), Vec::::from_hex("522103789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd2103dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a6162652ae").unwrap()[..] ); @@ -1238,7 +1328,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()) } } diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs new file mode 100644 index 000000000..dc730c3be --- /dev/null +++ b/src/descriptor/tr.rs @@ -0,0 +1,733 @@ +// 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}; +use miniscript::{limits::TAPROOT_MAX_NODE_COUNT, Miniscript}; +use std::cmp::{self, max}; +use std::hash; +use std::sync::{Arc, Mutex}; +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 + // 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 +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 + // + // 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(), + spend_info: Mutex::new(self.spend_info.lock().expect("Lock poisoned").clone()), + } + } +} + +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 { + // 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, + } + } + + /// Iterate over all miniscripts + pub fn iter(&self) -> TapTreeIter { + 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) + } +} + +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 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, + spend_info: Mutex::new(None), + }) + } 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 + } + + /// 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![] }, + } + } + + // 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 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") + } + 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.lock().expect("Lock poisoned") = Some(spend_info.clone()); + 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 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<(usize, &'a TapTree)>, +} + +impl<'a, Pk> Iterator for TapTreeIter<'a, Pk> +where + Pk: MiniscriptKey + 'a, +{ + type Item = (usize, &'a Miniscript); + + fn next(&mut self) -> Option { + while !self.stack.is_empty() { + let (depth, last) = self.stack.pop().expect("Size checked above"); + match &*last { + TapTree::Tree(l, r) => { + self.stack.push((depth + 1, &r)); + self.stack.push((depth + 1, &l)); + } + TapTree::Leaf(ref ms) => return Some((depth, ms)), + } + } + None + } +} + +impl FromTree for Tr +where + Pk: MiniscriptKey + FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, +{ + fn from_tree(top: &expression::Tree) -> Result { + // Helper function to parse taproot script path + fn parse_tr_script_spend( + tree: &expression::Tree, + ) -> Result, Error> + where + Pk: MiniscriptKey + FromStr, + Pk::Hash: FromStr, + ::Err: ToString, + <::Hash as FromStr>::Err: ToString, + { + match tree { + expression::Tree { name, args } if !name.is_empty() && args.is_empty() => { + let script = Miniscript::::from_str(name)?; + Ok(TapTree::Leaf(Arc::new(script))) + } + 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))) + } + _ => { + 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.is_empty() { + 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, + spend_info: Mutex::new(None), + }) + } + 2 => { + let ref key = top.args[0]; + if !key.args.is_empty() { + 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), + spend_info: Mutex::new(None), + }) + } + _ => { + 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::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(); + 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.is_ascii() { + 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 = expression::Tree { + name: rest, + args: vec![], + }; + return Ok(expression::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 = expression::Tree { + name: key, + args: vec![], + }; + if script.is_empty() { + 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(expression::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.is_empty() { + 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; +} + +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: Mutex::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/expression.rs b/src/expression.rs index fd31862a0..59dd7acc0 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,12 +119,101 @@ 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 // 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)); } } @@ -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/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/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; 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, ) 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!(), } } } diff --git a/src/psbt/finalizer.rs b/src/psbt/finalizer.rs index fbf0fffa5..73a94037d 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 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}; 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..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}; @@ -45,6 +47,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 +167,9 @@ impl fmt::Display for InputError { sighashflag {:?} rather than required {:?}", pubkey.key, got, required ), + InputError::CouldNotSatisfyTr => { + write!(f, "Could not satisfy Tr descriptor") + } } } } @@ -224,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 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; +}