From 581da098d28c223446c6a1b8f1061329088126f0 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Tue, 11 Jul 2023 14:34:30 +0200 Subject: [PATCH 01/11] descriptor: Lazy spend info computation This copies ideas from the Tr descriptor in rust-miniscript: The spend info is inside an Arc inside a Mutex. Ranged descriptors can be created but have to be converted (todo) before they can be spent. Removed the cmr() method because it is no longer used. --- src/policy/descriptor.rs | 99 ++++++++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 34 deletions(-) diff --git a/src/policy/descriptor.rs b/src/policy/descriptor.rs index b4762c46..a76323f7 100644 --- a/src/policy/descriptor.rs +++ b/src/policy/descriptor.rs @@ -1,5 +1,5 @@ use crate::policy::satisfy::PolicySatisfier; -use crate::{Cmr, Error, Policy}; +use crate::{miniscript, Error, Policy}; use bitcoin_hashes::Hash; use elements::schnorr::{TapTweak, XOnlyPublicKey}; use elements::secp256k1_zkp; @@ -10,6 +10,7 @@ use elements::taproot::{ use elements_miniscript::{MiniscriptKey, ToPublicKey}; use std::fmt; use std::str::FromStr; +use std::sync::{Arc, Mutex}; pub trait UnspendableKey: MiniscriptKey { fn unspendable() -> Self; @@ -49,43 +50,54 @@ pub fn leaf_version() -> LeafVersion { /// /// The internal key can be a normal public key (p2pk), a MuSig aggregate public key (multisig) /// or an unspendable public key in case this feature is undesirable. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug)] pub struct Descriptor { internal_key: Pk, - spend_info: TaprootSpendInfo, policy: Policy, - cmr: Cmr, + spend_info: Mutex>>, } -impl Descriptor { - /// Create a new descriptor from the given internal key and - /// policy which will become a single tap leaf - pub fn new(internal_key: Pk, policy: Policy) -> Result { - let commit = policy.serialize_no_witness(); - let cmr = commit.cmr(); - let script = elements::Script::from(Vec::from(cmr.as_ref())); - let version = leaf_version(); +impl Clone for Descriptor { + fn clone(&self) -> Self { + Self { + internal_key: self.internal_key.clone(), + policy: self.policy.clone(), + // New mutex so clones don't block each other + // Cloning the contained spending info is cheap + spend_info: Mutex::new( + self.spend_info + .lock() + .expect("Lock poisoned") + .as_ref() + .map(Arc::clone), + ), + } + } +} - let builder = TaprootBuilder::new() - .add_leaf_with_ver(0, script, version) - .expect("constant leaf"); - let secp = secp256k1_zkp::Secp256k1::verification_only(); - let spend_info = builder - .finalize(&secp, internal_key.to_x_only_pubkey()) - .expect("constant tree"); +impl PartialEq for Descriptor { + fn eq(&self, other: &Self) -> bool { + self.internal_key == other.internal_key && self.policy == other.policy + } +} + +impl Eq for Descriptor {} - Ok(Self { +impl Descriptor { + /// Create a new descriptor from the given internal key and + /// policy which will become a single tap leaf + pub fn new(internal_key: Pk, policy: Policy) -> Self { + Self { internal_key, - spend_info, policy, - cmr, - }) + spend_info: Mutex::new(None), + } } /// Create a new descriptor from the given policy which will become a single tap leaf /// /// The internal key is set to a constant that is provably not spendable - pub fn single_leaf(policy: Policy) -> Result + pub fn single_leaf(policy: Policy) -> Self where Pk: UnspendableKey, { @@ -97,10 +109,32 @@ impl Descriptor { pub fn internal_key(&self) -> &Pk { &self.internal_key } +} +impl Descriptor { /// Return the spend data - pub fn spend_info(&self) -> &TaprootSpendInfo { - &self.spend_info + pub fn spend_info(&self) -> Arc { + // Return the spending info if it is already cached + // Panic if lock is poisoned (another thread with the lock panicked) + let read_lock = self.spend_info.lock().expect("Lock poisoned"); + if let Some(ref spend_info) = *read_lock { + return Arc::clone(spend_info); + } + drop(read_lock); + + let (script, version) = self.leaf(); + let builder = TaprootBuilder::new() + .add_leaf_with_ver(0, script, version) + .expect("constant leaf"); + let secp = secp256k1_zkp::Secp256k1::verification_only(); + let data = builder + .finalize(&secp, self.internal_key.to_x_only_pubkey()) + .expect("constant tree"); + + // Cache spending info + let spend_info = Arc::new(data); + *self.spend_info.lock().expect("Lock poisoned") = Some(Arc::clone(&spend_info)); + spend_info } /// Return the script pubkey @@ -119,15 +153,12 @@ impl Descriptor { elements::Address::p2tr_tweaked(output_key, None, params) } - /// Return the CMR of the program inside the single tap leaf - pub fn cmr(&self) -> Cmr { - self.cmr - } - /// Return the single tap leaf pub fn leaf(&self) -> (elements::Script, LeafVersion) { - let script = elements::Script::from(Vec::from(self.cmr.as_ref())); + let commit = self.policy.serialize_no_witness(); + let script = elements::Script::from(commit.cmr().as_ref().to_vec()); let version = leaf_version(); + (script, version) } @@ -192,10 +223,10 @@ where ::Err: ToString, ::Err: ToString, { - type Err = Error; + type Err = miniscript::Error; fn from_str(s: &str) -> Result { let policy = Policy::from_str(s)?; - Self::single_leaf(policy) + Ok(Self::single_leaf(policy)) } } From ef5779c3a361374cf638edd1bd15a509e4359834 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Tue, 11 Jul 2023 15:07:35 +0200 Subject: [PATCH 02/11] descriptor: UnspendableKey for DescriptorPublicKey This might be useful when creating xpub descriptors with unspendable internal key. --- src/policy/descriptor.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/policy/descriptor.rs b/src/policy/descriptor.rs index a76323f7..8bd596ec 100644 --- a/src/policy/descriptor.rs +++ b/src/policy/descriptor.rs @@ -7,7 +7,7 @@ use elements::taproot::{ ControlBlock, LeafVersion, TapBranchHash, TapLeafHash, TaprootBuilder, TaprootMerkleBranch, TaprootSpendInfo, }; -use elements_miniscript::{MiniscriptKey, ToPublicKey}; +use miniscript::{DescriptorPublicKey, MiniscriptKey, ToPublicKey}; use std::fmt; use std::str::FromStr; use std::sync::{Arc, Mutex}; @@ -22,6 +22,15 @@ impl UnspendableKey for XOnlyPublicKey { } } +impl UnspendableKey for DescriptorPublicKey { + fn unspendable() -> Self { + Self::Single(miniscript::descriptor::SinglePub { + origin: None, + key: miniscript::descriptor::SinglePubKey::XOnly(XOnlyPublicKey::unspendable()), + }) + } +} + /// Bytes of x-only public key whose discrete logarithm (secret key) is unknown /// /// Taken from BIP 341 From e7216524a8d7d12c259c90332d1471c051652b1c Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Tue, 11 Jul 2023 15:02:21 +0200 Subject: [PATCH 03/11] descriptor: at_derivation_index This enables us to derive children descriptors from parent descriptors with ranged xpubs. Copied from rust_miniscript. Added a unit test. --- src/policy/descriptor.rs | 74 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/src/policy/descriptor.rs b/src/policy/descriptor.rs index 8bd596ec..89cf951c 100644 --- a/src/policy/descriptor.rs +++ b/src/policy/descriptor.rs @@ -7,7 +7,11 @@ use elements::taproot::{ ControlBlock, LeafVersion, TapBranchHash, TapLeafHash, TaprootBuilder, TaprootMerkleBranch, TaprootSpendInfo, }; -use miniscript::{DescriptorPublicKey, MiniscriptKey, ToPublicKey}; +use miniscript::descriptor::ConversionError; +use miniscript::{ + translate_hash_clone, DefiniteDescriptorKey, DescriptorPublicKey, MiniscriptKey, ToPublicKey, + Translator, +}; use std::fmt; use std::str::FromStr; use std::sync::{Arc, Mutex}; @@ -118,6 +122,16 @@ impl Descriptor { pub fn internal_key(&self) -> &Pk { &self.internal_key } + + fn translate_pk(&self, translator: &mut T) -> Result, E> + where + T: Translator, + Q: MiniscriptKey, + { + let internal_key = translator.pk(self.internal_key())?; + let policy = self.policy.translate(translator)?; + Ok(Descriptor::new(internal_key, policy)) + } } impl Descriptor { @@ -219,6 +233,33 @@ impl Descriptor { } } +impl Descriptor { + /// Replaces all wildcards (i.e. `/*`) in the descriptor with a particular derivation index, + /// turning it into a *definite* descriptor. + /// + /// # Errors + /// - If index ≥ 2^31 + pub fn at_derivation_index( + &self, + index: u32, + ) -> Result, ConversionError> { + struct Derivator(u32); + + impl Translator for Derivator { + fn pk( + &mut self, + pk: &DescriptorPublicKey, + ) -> Result { + pk.clone().at_derivation_index(self.0) + } + + translate_hash_clone!(DescriptorPublicKey, DescriptorPublicKey, ConversionError); + } + + self.translate_pk(&mut Derivator(index)) + } +} + impl fmt::Display for Descriptor { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.policy, f) @@ -239,3 +280,34 @@ where Ok(Self::single_leaf(policy)) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn derived_descriptor() { + let key = "[78412e3a/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*"; + let parent_key = DescriptorPublicKey::from_str(key).expect("constant key"); + let policy = Policy::Key(parent_key.clone()); + // Use the same xpub for internal key and for policy + // Unrealistic, but sufficient for testing + let parent_descriptor = Descriptor::new(parent_key.clone(), policy); + + for index in 0..10 { + let child_descriptor = parent_descriptor + .at_derivation_index(index) + .expect("derive descriptor"); + let child_key = parent_key + .clone() + .at_derivation_index(index) + .expect("derive key"); + + assert_eq!(child_key, child_descriptor.internal_key); + + if let Policy::Key(key) = child_descriptor.policy { + assert_eq!(child_key, key); + } + } + } +} From 2ee1d2e55cc9297c38ee8d6b14123c74825ea1d4 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Tue, 11 Jul 2023 15:53:24 +0200 Subject: [PATCH 04/11] descriptor: Implement ForEachKey This enables us to iterate over all keys of a policy or descriptor. Iterative implementation for Policy. Descriptor uses this to implement the convenience method has_wildcard(). --- src/policy/ast.rs | 31 ++++++++++++++++++++++++++++++- src/policy/descriptor.rs | 18 ++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/policy/ast.rs b/src/policy/ast.rs index 2d754012..f46af16f 100644 --- a/src/policy/ast.rs +++ b/src/policy/ast.rs @@ -25,7 +25,7 @@ use std::sync::Arc; use std::{fmt, iter, mem}; use crate::jet::Elements; -use crate::miniscript::{MiniscriptKey, ToPublicKey, Translator}; +use crate::miniscript::{ForEachKey, MiniscriptKey, ToPublicKey, Translator}; use crate::node::{ConstructNode, NoWitness}; use crate::FailEntropy; @@ -217,6 +217,35 @@ impl Policy { } } +impl ForEachKey for Policy { + fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, mut pred: F) -> bool + where + Pk: 'a, + { + let mut stack = vec![self]; + + while let Some(top) = stack.pop() { + match top { + Policy::Key(key) => { + if !pred(key) { + return false; + } + } + Policy::And { left, right } | Policy::Or { left, right } => { + stack.push(right); + stack.push(left); + } + Policy::Threshold(_, sub_policies) => { + stack.extend(sub_policies.iter()); + } + _ => {} + } + } + + true + } +} + impl fmt::Debug for Policy { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { diff --git a/src/policy/descriptor.rs b/src/policy/descriptor.rs index 89cf951c..2e1de727 100644 --- a/src/policy/descriptor.rs +++ b/src/policy/descriptor.rs @@ -9,8 +9,8 @@ use elements::taproot::{ }; use miniscript::descriptor::ConversionError; use miniscript::{ - translate_hash_clone, DefiniteDescriptorKey, DescriptorPublicKey, MiniscriptKey, ToPublicKey, - Translator, + translate_hash_clone, DefiniteDescriptorKey, DescriptorPublicKey, ForEachKey, MiniscriptKey, + ToPublicKey, Translator, }; use std::fmt; use std::str::FromStr; @@ -233,7 +233,21 @@ impl Descriptor { } } +impl ForEachKey for Descriptor { + fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, mut pred: F) -> bool + where + Pk: 'a, + { + pred(&self.internal_key) && self.policy.for_each_key(pred) + } +} + impl Descriptor { + /// Whether or not the descriptor has any wildcards i.e. `/*`. + pub fn has_wildcard(&self) -> bool { + self.for_any_key(|key| key.has_wildcard()) + } + /// Replaces all wildcards (i.e. `/*`) in the descriptor with a particular derivation index, /// turning it into a *definite* descriptor. /// From 45d8c425ba4c4d536b67fcd2155635ee670c5865 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Wed, 12 Jul 2023 17:29:25 +0200 Subject: [PATCH 05/11] policy: Decode any MiniscriptKey The ToPublic bound was simply unnecessary. (Is there a tool to check this?) Now we can decode policies with xpubs, which I also added a test for. This also enables the decoding of descriptors with general keys. The next commit will drop UnspendableKey to make FromStr even more flexible. --- src/policy/descriptor.rs | 2 +- src/policy/embed.rs | 24 +++++++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/policy/descriptor.rs b/src/policy/descriptor.rs index 2e1de727..92d25098 100644 --- a/src/policy/descriptor.rs +++ b/src/policy/descriptor.rs @@ -282,7 +282,7 @@ impl fmt::Display for Descriptor { impl FromStr for Descriptor where - Pk: UnspendableKey + ToPublicKey + FromStr, + Pk: UnspendableKey + MiniscriptKey + FromStr, Pk::Sha256: FromStr, ::Err: ToString, ::Err: ToString, diff --git a/src/policy/embed.rs b/src/policy/embed.rs index c9e701d9..3563857c 100644 --- a/src/policy/embed.rs +++ b/src/policy/embed.rs @@ -3,8 +3,7 @@ use std::str::FromStr; use std::sync::Arc; use crate::miniscript::Error as msError; -use crate::miniscript::ToPublicKey; -use crate::miniscript::{expression, Miniscript, ScriptContext, Terminal}; +use crate::miniscript::{expression, Miniscript, MiniscriptKey, ScriptContext, Terminal}; use crate::{miniscript, policy}; use crate::policy::Policy; @@ -14,7 +13,7 @@ serde_string_impl_pk!(Policy, "a Simplicity policy"); impl FromStr for Policy where - Pk: ToPublicKey + FromStr, + Pk: MiniscriptKey + FromStr, Pk::Sha256: FromStr, ::Err: ToString, ::Err: ToString, @@ -35,7 +34,7 @@ where impl expression::FromTree for Policy where - Pk: ToPublicKey + FromStr, + Pk: MiniscriptKey + FromStr, Pk::Sha256: FromStr, ::Err: ToString, ::Err: ToString, @@ -100,7 +99,7 @@ where } } -impl<'a, Pk: ToPublicKey, Ctx: ScriptContext> TryFrom<&'a Miniscript> for Policy { +impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TryFrom<&'a Miniscript> for Policy { type Error = Error; fn try_from(top: &Miniscript) -> Result { @@ -179,6 +178,7 @@ impl<'a, Pk: ToPublicKey, Ctx: ScriptContext> TryFrom<&'a Miniscript> f mod tests { use super::*; use crate::miniscript::bitcoin::XOnlyPublicKey; + use crate::miniscript::DescriptorPublicKey; #[test] fn parse_bad_thresh() { @@ -216,4 +216,18 @@ mod tests { Err(msError::Unexpected("3".to_string())), ); } + + #[test] + fn decode_xpub() { + let s = "[78412e3a/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*"; + let decoded_key = DescriptorPublicKey::from_str(s).expect("constant key"); + let s = format!("pk({})", s); + let decoded_policy = Policy::::from_str(&s).expect("decode policy"); + + if let Policy::Key(key) = decoded_policy { + assert_eq!(decoded_key, key); + } else { + panic!("Decoded policy should be public key") + } + } } From ca046fbde05203a42411b3d73232bc31e4953d5f Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Wed, 12 Jul 2023 22:53:19 +0200 Subject: [PATCH 06/11] descriptor: Include internal key in serialization We want to focus on the Simplicity leaves, but Simplicity descriptors are first and foremost Taproot descriptors with an internal key. We have the trait UnspendableKey to construct outputs with unspendable internal key, but for serialization we don't want to be this limited. In particular, it will be annoying to parse a descriptor with xprivs if the unspendable internal key requires special treatment because its private component is unknown. --- src/policy/descriptor.rs | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/policy/descriptor.rs b/src/policy/descriptor.rs index 92d25098..b95aeea8 100644 --- a/src/policy/descriptor.rs +++ b/src/policy/descriptor.rs @@ -8,6 +8,7 @@ use elements::taproot::{ TaprootSpendInfo, }; use miniscript::descriptor::ConversionError; +use miniscript::Error as MSError; use miniscript::{ translate_hash_clone, DefiniteDescriptorKey, DescriptorPublicKey, ForEachKey, MiniscriptKey, ToPublicKey, Translator, @@ -276,22 +277,35 @@ impl Descriptor { impl fmt::Display for Descriptor { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.policy, f) + write!(f, "sim({},{})", self.internal_key, self.policy) } } impl FromStr for Descriptor where - Pk: UnspendableKey + MiniscriptKey + FromStr, + Pk: MiniscriptKey + FromStr, Pk::Sha256: FromStr, ::Err: ToString, ::Err: ToString, { - type Err = miniscript::Error; + type Err = MSError; fn from_str(s: &str) -> Result { - let policy = Policy::from_str(s)?; - Ok(Self::single_leaf(policy)) + let (internal_key, policy) = s + .strip_prefix("sim(") + .and_then(|s| s.strip_suffix(')')) + .and_then(|s| { + let mut split = s.splitn(2, ','); + let x = split.next()?; + let y = split.next()?; + Some((x, y)) + }) + .ok_or(MSError::BadDescriptor("bad taproot descriptor".to_string()))?; + let internal_key = ::from_str(internal_key) + .map_err(|e| MSError::BadDescriptor(e.to_string()))?; + let policy = Policy::from_str(policy)?; + + Ok(Self::new(internal_key, policy)) } } @@ -299,6 +313,14 @@ where mod tests { use super::*; + #[test] + fn from_string_to_string() { + let original = "sim(d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d,pk(e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f))"; + let descriptor = Descriptor::::from_str(original).expect("from_str"); + let display = descriptor.to_string(); + assert_eq!(original, display); + } + #[test] fn derived_descriptor() { let key = "[78412e3a/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*"; From 6e549f7dd1acab91be816263825dc2661d12618f Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Wed, 12 Jul 2023 18:03:12 +0200 Subject: [PATCH 07/11] policy: Use generic Satisfier There is no reason why PolicySatisfier is required for our satisfaction methods. We could even consider removing it entirely / moving it to the test cases. --- src/policy/descriptor.rs | 7 +++---- src/policy/satisfy.rs | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/policy/descriptor.rs b/src/policy/descriptor.rs index b95aeea8..7f36e92b 100644 --- a/src/policy/descriptor.rs +++ b/src/policy/descriptor.rs @@ -1,4 +1,3 @@ -use crate::policy::satisfy::PolicySatisfier; use crate::{miniscript, Error, Policy}; use bitcoin_hashes::Hash; use elements::schnorr::{TapTweak, XOnlyPublicKey}; @@ -11,7 +10,7 @@ use miniscript::descriptor::ConversionError; use miniscript::Error as MSError; use miniscript::{ translate_hash_clone, DefiniteDescriptorKey, DescriptorPublicKey, ForEachKey, MiniscriptKey, - ToPublicKey, Translator, + Satisfier, ToPublicKey, Translator, }; use std::fmt; use std::str::FromStr; @@ -189,9 +188,9 @@ impl Descriptor { /// Return a satisfying non-malleable witness and script sig with minimum weight /// to spend an output controlled by the given descriptor if it is possible to satisfy /// given the `satisfier` - pub fn get_satisfaction( + pub fn get_satisfaction>( &self, - satisfier: &PolicySatisfier, + satisfier: S, ) -> Result<(Vec>, elements::Script), Error> { let program = self.policy.satisfy(satisfier)?; diff --git a/src/policy/satisfy.rs b/src/policy/satisfy.rs index a46b2099..8488a937 100644 --- a/src/policy/satisfy.rs +++ b/src/policy/satisfy.rs @@ -160,9 +160,9 @@ impl Policy { pub fn satisfy>( &self, - satisfier: &S, + satisfier: S, ) -> Result>, Error> { - let witnode = self.satisfy_internal(satisfier)?; + let witnode = self.satisfy_internal(&satisfier)?; if witnode.must_prune() { Err(Error::IncompleteFinalization) } else { From aa00a5e638529963c31aeb0e9e3157beda804e4b Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Thu, 13 Jul 2023 00:10:48 +0200 Subject: [PATCH 08/11] satisfier: Remove PolicySatisfier This is a messy commit but the changes are quite limited: 1) Remove PolicySatisfier because we use generic Satisfier 2) Adapt unit tests and define HashSatisfier and SigSatisfier to that end 3) Check whether thresh satisfaction runs on the Bit Machine. This was uncovered because `env` was suddently unused. --- src/policy/satisfy.rs | 139 ++++++++++++++++++------------------------ 1 file changed, 61 insertions(+), 78 deletions(-) diff --git a/src/policy/satisfy.rs b/src/policy/satisfy.rs index 8488a937..1c3e7331 100644 --- a/src/policy/satisfy.rs +++ b/src/policy/satisfy.rs @@ -1,45 +1,17 @@ +use crate::analysis::Cost; use crate::jet::Elements; use crate::node::{RedeemNode, WitnessNode}; -use crate::{Error, Policy, Value}; +use crate::{miniscript, Error, Policy, Value}; use bitcoin_hashes::Hash; use elements::locktime::Height; use elements::taproot::TapLeafHash; use elements::{LockTime, Sequence}; -use elements_miniscript::{MiniscriptKey, Preimage32, Satisfier, ToPublicKey}; +use miniscript::{Satisfier, ToPublicKey}; -use crate::analysis::Cost; -use std::collections::HashMap; use std::convert::TryFrom; use std::sync::Arc; -pub struct PolicySatisfier<'a, Pk: MiniscriptKey> { - pub preimages: HashMap, - pub signatures: HashMap, - pub tx: &'a elements::Transaction, - pub index: usize, -} - -impl<'a, Pk: ToPublicKey> Satisfier for PolicySatisfier<'a, Pk> { - fn lookup_tap_leaf_script_sig(&self, pk: &Pk, _: &TapLeafHash) -> Option { - self.signatures.get(pk).copied() - } - - fn lookup_sha256(&self, hash: &Pk::Sha256) -> Option { - self.preimages.get(hash).copied() - } - - fn check_older(&self, sequence: Sequence) -> bool { - let self_sequence = self.tx.input[self.index].sequence; - >::check_older(&self_sequence, sequence) - } - - fn check_after(&self, locktime: LockTime) -> bool { - let self_locktime = LockTime::from(self.tx.lock_time); - >::check_after(&self_locktime, locktime) - } -} - impl Policy { pub fn satisfy_internal>( &self, @@ -178,19 +150,42 @@ mod tests { use crate::jet::elements::ElementsEnv; use crate::node::SimpleFinalizer; use crate::{BitMachine, FailEntropy}; - use bitcoin_hashes::{sha256, Hash}; - use elements::{bitcoin, secp256k1_zkp, PackedLockTime, SchnorrSigHashType}; + use bitcoin_hashes::sha256; + use elements::{bitcoin, secp256k1_zkp, PackedLockTime, SchnorrSig, SchnorrSigHashType}; + use miniscript::{MiniscriptKey, Preimage32}; + use std::collections::HashMap; use std::sync::Arc; - fn get_satisfier(env: &ElementsEnv) -> PolicySatisfier { + struct HashSatisfier(HashMap); + + impl Satisfier for HashSatisfier { + fn lookup_sha256(&self, image: &Pk::Sha256) -> Option { + self.0.get(image).copied() + } + } + + fn get_hash_satisfier() -> HashSatisfier { let mut preimages = HashMap::new(); - let mut signatures = HashMap::new(); for i in 0..3 { let preimage = [i; 32]; preimages.insert(sha256::Hash::hash(&preimage), preimage); } + HashSatisfier(preimages) + } + + struct SigSatisfier(HashMap); + + impl Satisfier for SigSatisfier { + fn lookup_tap_leaf_script_sig(&self, key: &Pk, _: &TapLeafHash) -> Option { + self.0.get(key).copied() + } + } + + fn get_sig_satisfier(env: &ElementsEnv) -> SigSatisfier { + let mut signatures = HashMap::new(); + let secp = secp256k1_zkp::Secp256k1::new(); let mut rng = secp256k1_zkp::rand::rngs::ThreadRng::default(); @@ -208,12 +203,7 @@ mod tests { signatures.insert(xonly, sig); } - PolicySatisfier { - preimages, - signatures, - tx: env.tx(), - index: 0, - } + SigSatisfier(signatures) } fn execute_successful(program: Arc>, env: &ElementsEnv) { @@ -231,7 +221,7 @@ mod tests { #[test] fn satisfy_unsatisfiable() { let env = ElementsEnv::dummy(); - let satisfier = get_satisfier(&env); + let satisfier = get_sig_satisfier(&env); let policy = Policy::Unsatisfiable(FailEntropy::ZERO); assert!(policy.satisfy(&satisfier).is_err()); @@ -250,7 +240,7 @@ mod tests { #[test] fn satisfy_trivial() { let env = ElementsEnv::dummy(); - let satisfier = get_satisfier(&env); + let satisfier = get_sig_satisfier(&env); let policy = Policy::Trivial; let program = policy.satisfy(&satisfier).expect("satisfiable"); @@ -263,9 +253,8 @@ mod tests { #[test] fn satisfy_pk() { let env = ElementsEnv::dummy(); - let satisfier = get_satisfier(&env); - let mut it = satisfier.signatures.keys(); - let xonly = it.next().unwrap(); + let satisfier = get_sig_satisfier(&env); + let xonly = satisfier.0.keys().next().expect("satisfier has keys"); let policy = Policy::Key(*xonly); let program = policy.satisfy(&satisfier).expect("satisfiable"); @@ -285,9 +274,8 @@ mod tests { #[test] fn satisfy_sha256() { let env = ElementsEnv::dummy(); - let satisfier = get_satisfier(&env); - let mut it = satisfier.preimages.keys(); - let image = *it.next().unwrap(); + let satisfier = get_hash_satisfier(); + let image = *satisfier.0.keys().next().expect("satisfier has image"); let policy = Policy::Sha256(image); let program = policy.satisfy(&satisfier).expect("satisfiable"); @@ -296,7 +284,7 @@ mod tests { let witness_bytes = witness[0].try_to_bytes().expect("to bytes"); let witness_preimage = Preimage32::try_from(witness_bytes.as_slice()).expect("to array"); - let preimage = *satisfier.preimages.get(&image).unwrap(); + let preimage = *satisfier.0.get(&image).unwrap(); assert_eq!(preimage, witness_preimage); execute_successful(program, &env); @@ -304,55 +292,54 @@ mod tests { #[test] fn satisfy_after() { - let env = ElementsEnv::dummy_with(PackedLockTime(42), Sequence::ZERO); - let satisfier = get_satisfier(&env); + let locktime = 42; + let env = ElementsEnv::dummy_with(PackedLockTime(locktime), Sequence::ZERO); + let satisfier = LockTime::Blocks(Height::from_consensus(locktime).expect("valid height")); - let policy0 = Policy::After(41); + let policy0: Policy = Policy::After(41); let program = policy0.satisfy(&satisfier).expect("satisfiable"); let witness = to_witness(&program); assert!(witness.is_empty()); execute_successful(program, &env); - let policy1 = Policy::After(42); + let policy1: Policy = Policy::After(42); let program = policy1.satisfy(&satisfier).expect("satisfiable"); let witness = to_witness(&program); assert!(witness.is_empty()); execute_successful(program, &env); - let policy2 = Policy::After(43); + let policy2: Policy = Policy::After(43); assert!(policy2.satisfy(&satisfier).is_err(), "unsatisfiable"); } #[test] fn satisfy_older() { - let env = ElementsEnv::dummy_with(PackedLockTime::ZERO, Sequence::from_consensus(42)); - let satisfier = get_satisfier(&env); + let sequence = Sequence::from_consensus(42); + let env = ElementsEnv::dummy_with(PackedLockTime::ZERO, sequence); + let satisfier = sequence; - let policy0 = Policy::Older(41); + let policy0: Policy = Policy::Older(41); let program = policy0.satisfy(&satisfier).expect("satisfiable"); let witness = to_witness(&program); assert!(witness.is_empty()); execute_successful(program, &env); - let policy1 = Policy::Older(42); + let policy1: Policy = Policy::Older(42); let program = policy1.satisfy(&satisfier).expect("satisfiable"); let witness = to_witness(&program); assert!(witness.is_empty()); execute_successful(program, &env); - let policy2 = Policy::Older(43); + let policy2: Policy = Policy::Older(43); assert!(policy2.satisfy(&satisfier).is_err(), "unsatisfiable"); } #[test] fn satisfy_and() { let env = ElementsEnv::dummy(); - let satisfier = get_satisfier(&env); - let images: Vec<_> = satisfier.preimages.keys().copied().collect(); - let preimages: Vec<_> = images - .iter() - .map(|x| satisfier.preimages.get(x).unwrap()) - .collect(); + let satisfier = get_hash_satisfier(); + let images: Vec<_> = satisfier.0.keys().copied().collect(); + let preimages: Vec<_> = images.iter().map(|x| satisfier.0.get(x).unwrap()).collect(); // Policy 0 @@ -393,12 +380,9 @@ mod tests { #[test] fn satisfy_or() { let env = ElementsEnv::dummy(); - let satisfier = get_satisfier(&env); - let images: Vec<_> = satisfier.preimages.keys().copied().collect(); - let preimages: Vec<_> = images - .iter() - .map(|x| satisfier.preimages.get(x).unwrap()) - .collect(); + let satisfier = get_hash_satisfier(); + let images: Vec<_> = satisfier.0.keys().copied().collect(); + let preimages: Vec<_> = images.iter().map(|x| satisfier.0.get(x).unwrap()).collect(); let assert_branch = |policy: &Policy, bit: bool| { let program = policy.satisfy(&satisfier).expect("satisfiable"); @@ -450,12 +434,9 @@ mod tests { #[test] fn satisfy_thresh() { let env = ElementsEnv::dummy(); - let satisfier = get_satisfier(&env); - let images: Vec<_> = satisfier.preimages.keys().copied().collect(); - let preimages: Vec<_> = images - .iter() - .map(|x| satisfier.preimages.get(x).unwrap()) - .collect(); + let satisfier = get_hash_satisfier(); + let images: Vec<_> = satisfier.0.keys().copied().collect(); + let preimages: Vec<_> = images.iter().map(|x| satisfier.0.get(x).unwrap()).collect(); let assert_branches = |policy: &Policy, bits: &[bool]| { let program = policy.satisfy(&satisfier).expect("satisfiable"); @@ -477,6 +458,8 @@ mod tests { witidx += 1; } } + + execute_successful(program, &env); }; let image_from_bit = |bit: bool, j: u8| { From faad19281b7d6fabb62f06a594dc98769acd04ec Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Wed, 12 Jul 2023 22:59:23 +0200 Subject: [PATCH 09/11] descriptor: parse_descriptor and to_string_with_secret This enables us to handle xpriv descriptors. They don't exist in data, but we can parse to a keymap and an xpub descriptor. This gives us a guarantee that the keymap corresponds to the xpub, so it's safe to use during spending. Copied from rust-bitcoin. I don't like copy-pasting so many LOC but it seems inevitable. There is a lot of boilerplate and repetions that could probably be handled by LISP-style metaprogramming that is not available to us. --- src/policy/descriptor.rs | 129 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 4 deletions(-) diff --git a/src/policy/descriptor.rs b/src/policy/descriptor.rs index 7f36e92b..9b2404e5 100644 --- a/src/policy/descriptor.rs +++ b/src/policy/descriptor.rs @@ -1,17 +1,18 @@ use crate::{miniscript, Error, Policy}; -use bitcoin_hashes::Hash; +use bitcoin_hashes::{hash160, ripemd160, sha256, Hash}; use elements::schnorr::{TapTweak, XOnlyPublicKey}; use elements::secp256k1_zkp; use elements::taproot::{ ControlBlock, LeafVersion, TapBranchHash, TapLeafHash, TaprootBuilder, TaprootMerkleBranch, TaprootSpendInfo, }; -use miniscript::descriptor::ConversionError; +use miniscript::descriptor::{ConversionError, DescriptorSecretKey, KeyMap}; use miniscript::Error as MSError; use miniscript::{ - translate_hash_clone, DefiniteDescriptorKey, DescriptorPublicKey, ForEachKey, MiniscriptKey, - Satisfier, ToPublicKey, Translator, + hash256, translate_hash_clone, DefiniteDescriptorKey, DescriptorPublicKey, ForEachKey, + MiniscriptKey, Satisfier, ToPublicKey, Translator, }; +use std::collections::HashMap; use std::fmt; use std::str::FromStr; use std::sync::{Arc, Mutex}; @@ -272,6 +273,126 @@ impl Descriptor { self.translate_pk(&mut Derivator(index)) } + + /// Parse a descriptor that may contain secret keys + /// + /// Internally turns every secret key found into the corresponding public key and then returns a + /// a descriptor that only contains public keys and a map to lookup the secret key given a public key. + pub fn parse_descriptor( + secp: &secp256k1_zkp::Secp256k1, + s: &str, + ) -> Result<(Descriptor, KeyMap), MSError> { + fn parse_key( + s: &str, + key_map: &mut KeyMap, + secp: &secp256k1_zkp::Secp256k1, + ) -> Result { + let (public_key, secret_key) = match DescriptorSecretKey::from_str(s) { + Ok(sk) => ( + sk.to_public(secp) + .map_err(|e| MSError::Unexpected(e.to_string()))?, + Some(sk), + ), + Err(_) => ( + DescriptorPublicKey::from_str(s) + .map_err(|e| MSError::Unexpected(e.to_string()))?, + None, + ), + }; + + if let Some(secret_key) = secret_key { + key_map.insert(public_key.clone(), secret_key); + } + + Ok(public_key) + } + + let mut keymap_pk = KeyMapWrapper(HashMap::new(), secp); + + struct KeyMapWrapper<'a, C: secp256k1_zkp::Signing>( + KeyMap, + &'a secp256k1_zkp::Secp256k1, + ); + + impl<'a, C: secp256k1_zkp::Signing> Translator + for KeyMapWrapper<'a, C> + { + fn pk(&mut self, pk: &String) -> Result { + parse_key(pk, &mut self.0, self.1) + } + + fn sha256(&mut self, sha256: &String) -> Result { + let hash = sha256::Hash::from_str(sha256) + .map_err(|e| MSError::Unexpected(e.to_string()))?; + Ok(hash) + } + + fn hash256(&mut self, hash256: &String) -> Result { + let hash = hash256::Hash::from_str(hash256) + .map_err(|e| MSError::Unexpected(e.to_string()))?; + Ok(hash) + } + + fn ripemd160(&mut self, ripemd160: &String) -> Result { + let hash = ripemd160::Hash::from_str(ripemd160) + .map_err(|e| MSError::Unexpected(e.to_string()))?; + Ok(hash) + } + + fn hash160(&mut self, hash160: &String) -> Result { + let hash = hash160::Hash::from_str(hash160) + .map_err(|e| MSError::Unexpected(e.to_string()))?; + Ok(hash) + } + } + + let descriptor = Descriptor::::from_str(s)?; + let descriptor = descriptor + .translate_pk(&mut keymap_pk) + .map_err(|e| MSError::Unexpected(e.to_string()))?; + + Ok((descriptor, keymap_pk.0)) + } + + /// Serialize a descriptor to string with its secret keys + pub fn to_string_with_secret(&self, key_map: &KeyMap) -> String { + struct KeyMapLookUp<'a>(&'a KeyMap); + + impl<'a> Translator for KeyMapLookUp<'a> { + fn pk(&mut self, pk: &DescriptorPublicKey) -> Result { + key_to_string(pk, self.0) + } + + fn sha256(&mut self, sha256: &sha256::Hash) -> Result { + Ok(sha256.to_string()) + } + + fn hash256(&mut self, hash256: &hash256::Hash) -> Result { + Ok(hash256.to_string()) + } + + fn ripemd160(&mut self, ripemd160: &ripemd160::Hash) -> Result { + Ok(ripemd160.to_string()) + } + + fn hash160(&mut self, hash160: &hash160::Hash) -> Result { + Ok(hash160.to_string()) + } + } + + fn key_to_string(pk: &DescriptorPublicKey, key_map: &KeyMap) -> Result { + Ok(match key_map.get(pk) { + Some(secret) => secret.to_string(), + None => pk.to_string(), + }) + } + + let descriptor = self + .translate_pk(&mut KeyMapLookUp(key_map)) + .expect("Translation to string cannot fail"); + + descriptor.to_string() + } } impl fmt::Display for Descriptor { From bcd35f8f8bb50d16a312d84f16e2278afc0c644b Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Wed, 12 Jul 2023 23:09:03 +0200 Subject: [PATCH 10/11] descriptor: derived_descriptor This enables us to work with pubkey descriptors instead of inaccessible xpubs without wildcards. Again copied from rust-miniscript. --- src/policy/descriptor.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/policy/descriptor.rs b/src/policy/descriptor.rs index 9b2404e5..b5cd33a4 100644 --- a/src/policy/descriptor.rs +++ b/src/policy/descriptor.rs @@ -1,11 +1,11 @@ use crate::{miniscript, Error, Policy}; use bitcoin_hashes::{hash160, ripemd160, sha256, Hash}; use elements::schnorr::{TapTweak, XOnlyPublicKey}; -use elements::secp256k1_zkp; use elements::taproot::{ ControlBlock, LeafVersion, TapBranchHash, TapLeafHash, TaprootBuilder, TaprootMerkleBranch, TaprootSpendInfo, }; +use elements::{bitcoin, secp256k1_zkp}; use miniscript::descriptor::{ConversionError, DescriptorSecretKey, KeyMap}; use miniscript::Error as MSError; use miniscript::{ @@ -395,6 +395,39 @@ impl Descriptor { } } +impl Descriptor { + /// Convert all the public keys in the descriptor to [`bitcoin::PublicKey`] by deriving them or + /// otherwise converting them. All [`secp256k1_zkp::XOnlyPublicKey`]s are converted to by adding a + /// default(0x02) y-coordinate. + /// + /// # Errors + /// + /// This function will return an error if hardened derivation is attempted. + pub fn derived_descriptor( + &self, + secp: &secp256k1_zkp::Secp256k1, + ) -> Result, ConversionError> { + struct Derivator<'a, C: secp256k1_zkp::Verification>(&'a secp256k1_zkp::Secp256k1); + + impl<'a, C: secp256k1_zkp::Verification> + Translator + for Derivator<'a, C> + { + fn pk( + &mut self, + pk: &DefiniteDescriptorKey, + ) -> Result { + pk.derive_public_key(self.0) + } + + translate_hash_clone!(DefiniteDescriptorKey, bitcoin::PublicKey, ConversionError); + } + + let derived = self.translate_pk(&mut Derivator(secp))?; + Ok(derived) + } +} + impl fmt::Display for Descriptor { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "sim({},{})", self.internal_key, self.policy) From 1b8a825e8919c6205b7ae05a4fdafd6134baa0d8 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 13 Jul 2023 20:09:28 +0000 Subject: [PATCH 11/11] Cargo.toml: patch for https://github.com/rust-lang/rust/issues/110475 Newly-released rustc 1.71 no longer compiles our rust-miniscript dependency. Patch rust-miniscript, then elements-miniscript, then this, to hack around it. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ad6180db..a6f93ed4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ bitcoin = { version = "0.29.2", optional = true } bitcoin_hashes = "0.11" byteorder = "1.3" elements = { version = "0.21.1", optional = true } -elements-miniscript = { git = "https://github.com/ElementsProject/elements-miniscript", rev = "955f380" } +elements-miniscript = { git = "https://github.com/apoelstra/elements-miniscript", tag = "2023-07--rust-simplicity-patch" } simplicity-sys = { version = "0.1.0", path = "./simplicity-sys" } actual-serde = { package = "serde", version = "1.0.103", features = ["derive"], optional = true }