From 3fcf2f7eb6ad635e770ef897466281f6becd9b99 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Wed, 2 Aug 2023 15:24:33 +0200 Subject: [PATCH 1/7] policy: Implement Simplicity key traits for Miniscript equivalents This is the easiest solution to use Miniscript keys in Simplicity contexts. We focused on subtraits before, which would require changes to (Elements) Miniscript, but we can also use generic impl blocks, as long as they are inside rust-simplicity (orphan rule). We removed elements-miniscript from rust-simplicity to avoid a cyclic dependency. MiniscriptKey is defined in rust-miniscript, so importing it here does not create a cycle. We get a diamond pattern where both elements-miniscript and rust-simplicity depend on the same library, which should be okay. --- Cargo.toml | 1 + src/policy/key.rs | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a78208e0..644be4b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ path = "src/lib.rs" [dependencies] bitcoin = { version = "0.30.0", optional = true } +bitcoin-miniscript = { package = "miniscript", version = "10.0" } byteorder = "1.3" elements = { version = "0.22.0", optional = true } hashes = { package = "bitcoin_hashes", version = "0.12" } diff --git a/src/policy/key.rs b/src/policy/key.rs index 682320a5..f91d6b75 100644 --- a/src/policy/key.rs +++ b/src/policy/key.rs @@ -1,3 +1,4 @@ +use bitcoin_miniscript::{MiniscriptKey, ToPublicKey}; use elements::bitcoin::key::XOnlyPublicKey; use hashes::sha256; use std::fmt::{Debug, Display}; @@ -8,8 +9,8 @@ pub trait SimplicityKey: Clone + Eq + Ord + Debug + Display + std::hash::Hash { type Sha256: Clone + Eq + Ord + Display + Debug + std::hash::Hash; } -impl SimplicityKey for XOnlyPublicKey { - type Sha256 = sha256::Hash; +impl SimplicityKey for Pk { + type Sha256 = ::Sha256; } /// Public key which can be converted to a (x-only) public key which can be used in Simplicity. @@ -21,13 +22,13 @@ pub trait ToXOnlyPubkey: SimplicityKey { fn to_sha256(hash: &Self::Sha256) -> sha256::Hash; } -impl ToXOnlyPubkey for XOnlyPublicKey { +impl ToXOnlyPubkey for Pk { fn to_x_only_pubkey(&self) -> XOnlyPublicKey { - *self + ::to_x_only_pubkey(self) } fn to_sha256(hash: &Self::Sha256) -> sha256::Hash { - *hash + ::to_sha256(hash) } } From dfa267e47c6a8894faacc474ee74a6732ca929fc Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Fri, 21 Jul 2023 16:32:52 +0200 Subject: [PATCH 2/7] policy: Implement Hash for Policy We get this for free. --- src/policy/ast.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/policy/ast.rs b/src/policy/ast.rs index cffa829e..4904c2c3 100644 --- a/src/policy/ast.rs +++ b/src/policy/ast.rs @@ -37,7 +37,7 @@ use super::serialize; /// given a witness. /// /// Furthermore, the policy can be normalized and is amenable to semantic analysis. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Policy { /// Unsatisfiable Unsatisfiable(FailEntropy), From 36b4ad9fe398790dc7d3594600c892c1ed3bd971 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Thu, 3 Aug 2023 11:45:38 +0200 Subject: [PATCH 3/7] Add accessor to Simplicity's leaf version --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 62668bc1..74aade4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,6 +71,12 @@ pub use crate::value::Value; pub use simplicity_sys as ffi; use std::fmt; +/// Return the version of Simplicity leaves inside a tap tree. +#[cfg(feature = "elements")] +pub fn leaf_version() -> elements::taproot::LeafVersion { + elements::taproot::LeafVersion::from_u8(0xbe).expect("constant leaf version") +} + /// Error type for simplicity #[non_exhaustive] #[derive(Debug)] From 918289794c2d6bf08b89c2065124bcf830eb99a4 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Thu, 3 Aug 2023 15:05:02 +0200 Subject: [PATCH 4/7] policy: Export satisfy structs --- src/lib.rs | 2 +- src/policy/mod.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 74aade4d..6a6d07e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,7 @@ pub use bit_encoding::BitWriter; pub use bit_encoding::{BitIter, EarlyEndOfStreamError}; #[cfg(feature = "elements")] -pub use crate::policy::{Policy, SimplicityKey, ToXOnlyPubkey, Translator}; +pub use crate::policy::{Policy, Preimage32, Satisfier, SimplicityKey, ToXOnlyPubkey, Translator}; pub use crate::bit_machine::BitMachine; pub use crate::encode::{encode_natural, encode_value, encode_witness}; diff --git a/src/policy/mod.rs b/src/policy/mod.rs index a9a3e53e..f779cd44 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -28,9 +28,10 @@ mod ast; mod error; mod key; -pub mod satisfy; +mod satisfy; mod serialize; pub use ast::Policy; pub use error::Error; pub use key::{SimplicityKey, ToXOnlyPubkey, Translator}; +pub use satisfy::{Preimage32, Satisfier}; From 67dfb8305b5eb41274620323bfde170296773222 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Fri, 18 Aug 2023 16:20:57 +0200 Subject: [PATCH 5/7] elements: Environment with generic transaction ref A generic transaction reference is more flexible and will prevent us from cloning transactions in the upcoming sighash. Because we changed jet/init/elements.rs, we need to update the Haskell code that generated the original file. --- jets-bench/benches/elements/env.rs | 8 ++++---- jets-bench/benches/elements/main.rs | 2 +- src/jet/elements/environment.rs | 17 ++++++++++------- src/jet/init/elements.rs | 3 ++- src/policy/satisfy.rs | 9 +++++++-- src/policy/serialize.rs | 9 +++++++-- 6 files changed, 31 insertions(+), 17 deletions(-) diff --git a/jets-bench/benches/elements/env.rs b/jets-bench/benches/elements/env.rs index 302d0ebb..bd1afbda 100644 --- a/jets-bench/benches/elements/env.rs +++ b/jets-bench/benches/elements/env.rs @@ -28,12 +28,12 @@ pub enum EnvSampling { impl EnvSampling { /// Obtain a random environment without any annex - pub fn env(&self) -> ElementsEnv { + pub fn env(&self) -> ElementsEnv> { self.env_with_annex(None) } /// Obtains a env with annex - pub fn env_with_annex(&self, annex: Option>) -> ElementsEnv { + pub fn env_with_annex(&self, annex: Option>) -> ElementsEnv> { let ((txin, spent_utxo), n_in, n_out) = match self { EnvSampling::Null => return null_env(), EnvSampling::Issuance(n_in, n_out) => (txin_utils::issuance(), n_in, n_out), @@ -81,7 +81,7 @@ fn env_with_spent_utxos( tx: Transaction, utxos: Vec, annex: Option>, -) -> ElementsEnv { +) -> ElementsEnv> { let ctrl_blk: [u8; 33] = [ 0xc0, 0xeb, 0x04, 0xb6, 0x8e, 0x9a, 0x26, 0xd1, 0x16, 0x04, 0x6c, 0x76, 0xe8, 0xff, 0x47, 0x33, 0x2f, 0xb7, 0x1d, 0xda, 0x90, 0xff, 0x4b, 0xef, 0x53, 0x70, 0xf2, 0x52, 0x26, 0xd3, @@ -98,7 +98,7 @@ fn env_with_spent_utxos( ) } -fn null_env() -> ElementsEnv { +fn null_env() -> ElementsEnv> { let tx = Transaction { version: u32::default(), lock_time: LockTime::ZERO, diff --git a/jets-bench/benches/elements/main.rs b/jets-bench/benches/elements/main.rs index b70ea61f..8d80895f 100644 --- a/jets-bench/benches/elements/main.rs +++ b/jets-bench/benches/elements/main.rs @@ -59,7 +59,7 @@ enum ElementsBenchEnvType { } impl ElementsBenchEnvType { - fn env(&self) -> ElementsEnv { + fn env(&self) -> ElementsEnv> { let n_in = NUM_TX_INPUTS; let n_out = NUM_TX_OUTPUTS; let env_sampler = match self { diff --git a/src/jet/elements/environment.rs b/src/jet/elements/environment.rs index bc54272c..a0c79ee3 100644 --- a/src/jet/elements/environment.rs +++ b/src/jet/elements/environment.rs @@ -16,7 +16,7 @@ use crate::merkle::cmr::Cmr; use elements::confidential; use elements::taproot::ControlBlock; use simplicity_sys::c_jets::c_env::CElementsTxEnv; -use std::sync::Arc; +use std::ops::Deref; use super::c_env; @@ -58,11 +58,11 @@ impl From for ElementsUtxo { // Similar story if we tried to use a &'a elements::Transaction rather than // an Arc: we'd have a lifetime parameter <'a> that would cause us trouble. #[allow(dead_code)] -pub struct ElementsEnv { +pub struct ElementsEnv> { /// The CTxEnv struct c_tx_env: CElementsTxEnv, /// The elements transaction - tx: Arc, + tx: T, /// the current index of the input ix: u32, /// Control block used to spend this leaf script @@ -73,9 +73,12 @@ pub struct ElementsEnv { genesis_hash: elements::BlockHash, } -impl ElementsEnv { +impl ElementsEnv +where + T: Deref, +{ pub fn new( - tx: Arc, + tx: T, utxos: Vec, ix: u32, script_cmr: Cmr, @@ -128,7 +131,7 @@ impl ElementsEnv { } #[cfg(test)] -impl ElementsEnv { +impl ElementsEnv> { /// Return a dummy Elements environment pub fn dummy() -> Self { Self::dummy_with(elements::LockTime::ZERO, elements::Sequence::MAX) @@ -146,7 +149,7 @@ impl ElementsEnv { ]; ElementsEnv::new( - Arc::new(elements::Transaction { + std::sync::Arc::new(elements::Transaction { version: 2, lock_time, // Enable locktime in dummy txin diff --git a/src/jet/init/elements.rs b/src/jet/init/elements.rs index d5592021..4e7912af 100644 --- a/src/jet/init/elements.rs +++ b/src/jet/init/elements.rs @@ -12,6 +12,7 @@ use std::io::Write; use std::{fmt, str}; use crate::jet::elements::ElementsEnv; use simplicity_sys::CElementsTxEnv; +use std::sync::Arc; /// Elements jet family #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] @@ -313,7 +314,7 @@ pub enum Elements { impl Jet for Elements { - type Environment = ElementsEnv; + type Environment = ElementsEnv>; type CJetEnvironment = CElementsTxEnv; fn c_jet_env<'env>(&self, env: &'env Self::Environment) -> &'env Self::CJetEnvironment { diff --git a/src/policy/satisfy.rs b/src/policy/satisfy.rs index 05429d5a..2915e302 100644 --- a/src/policy/satisfy.rs +++ b/src/policy/satisfy.rs @@ -249,7 +249,9 @@ mod tests { } } - fn get_satisfier(env: &ElementsEnv) -> PolicySatisfier { + fn get_satisfier( + env: &ElementsEnv>, + ) -> PolicySatisfier { let mut preimages = HashMap::new(); for i in 0..3 { @@ -283,7 +285,10 @@ mod tests { } } - fn execute_successful(program: Arc>, env: &ElementsEnv) { + fn execute_successful( + program: Arc>, + env: &ElementsEnv>, + ) { let mut mac = BitMachine::for_program(&program); assert!(mac.exec(&program, env).is_ok()); } diff --git a/src/policy/serialize.rs b/src/policy/serialize.rs index 8dc0994a..a9ac9e27 100644 --- a/src/policy/serialize.rs +++ b/src/policy/serialize.rs @@ -255,7 +255,12 @@ mod tests { use hashes::{sha256, Hash}; use std::sync::Arc; - fn compile(policy: Policy) -> (Arc>, ElementsEnv) { + fn compile( + policy: Policy, + ) -> ( + Arc>, + ElementsEnv>, + ) { let commit = policy.serialize_no_witness(); let env = ElementsEnv::dummy(); @@ -265,7 +270,7 @@ mod tests { fn execute_successful( commit: &ConstructNode, witness: Vec, - env: &ElementsEnv, + env: &ElementsEnv>, ) -> bool { let finalized = commit .finalize_types() From 1bdedd14415e576b0f91931198f7211b24ed82ba Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Fri, 11 Aug 2023 21:46:08 +0200 Subject: [PATCH 6/7] policy: Add sighash cache The signature hashes are different for Simplicity than for Taproot. Among many things, the Simplicity sighash commits to the annexes of all inputs, while Taproot only commits to the annex of the local input. There is no clean way to compute the sighash at the moment; the code is hidden somewhere in the Elements environment in the C files. We can naively construct a Rust Elements environment in order to extract the sighash from its FFI bindings. This requires information (control block) that we arguably don't need, while other information is left unused (leaf hash). We should write more direct bindings or implement the sighash in pure Rust in the future. This will take time, so we make do with what we have. --- src/lib.rs | 4 +- src/policy/mod.rs | 1 + src/policy/sighash.rs | 94 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/policy/sighash.rs diff --git a/src/lib.rs b/src/lib.rs index 6a6d07e7..950aea38 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,9 @@ pub use bit_encoding::BitWriter; pub use bit_encoding::{BitIter, EarlyEndOfStreamError}; #[cfg(feature = "elements")] -pub use crate::policy::{Policy, Preimage32, Satisfier, SimplicityKey, ToXOnlyPubkey, Translator}; +pub use crate::policy::{ + sighash, Policy, Preimage32, Satisfier, SimplicityKey, ToXOnlyPubkey, Translator, +}; pub use crate::bit_machine::BitMachine; pub use crate::encode::{encode_natural, encode_value, encode_witness}; diff --git a/src/policy/mod.rs b/src/policy/mod.rs index f779cd44..7417106b 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -30,6 +30,7 @@ mod error; mod key; mod satisfy; mod serialize; +pub mod sighash; pub use ast::Policy; pub use error::Error; diff --git a/src/policy/sighash.rs b/src/policy/sighash.rs new file mode 100644 index 00000000..ab7ee0cb --- /dev/null +++ b/src/policy/sighash.rs @@ -0,0 +1,94 @@ +use std::borrow::Borrow; +use std::ops::Deref; + +use hashes::sha256; + +use crate::jet::elements::ElementsUtxo; +use crate::Cmr; + +pub struct SighashCache> { + tx: T, + fallback: elements::sighash::SigHashCache, +} + +impl + Clone> SighashCache { + pub fn new(tx: T) -> Self { + Self { + tx: tx.clone(), + fallback: elements::sighash::SigHashCache::new(tx), + } + } + + pub fn taproot_key_spend_signature_hash( + &mut self, + input_index: usize, + prevouts: &elements::sighash::Prevouts, + sighash_type: elements::sighash::SchnorrSigHashType, + genesis_hash: elements::BlockHash, + ) -> Result + where + O: Borrow, + { + self.fallback.taproot_key_spend_signature_hash( + input_index, + prevouts, + sighash_type, + genesis_hash, + ) + } + + pub fn taproot_script_spend_signature_hash( + &mut self, + input_index: usize, + prevouts: &elements::sighash::Prevouts, + leaf_hash: S, + sighash_type: elements::sighash::SchnorrSigHashType, + genesis_hash: elements::BlockHash, + ) -> Result + where + S: Into, + O: Borrow, + { + self.fallback.taproot_script_spend_signature_hash( + input_index, + prevouts, + leaf_hash, + sighash_type, + genesis_hash, + ) + } + + pub fn simplicity_spend_signature_hash( + &mut self, + input_index: usize, + prevouts: &elements::sighash::Prevouts, + script_cmr: Cmr, + control_block: elements::taproot::ControlBlock, + genesis_hash: elements::BlockHash, + ) -> Result + where + O: Borrow, + { + let all = match prevouts { + elements::sighash::Prevouts::All(prevouts) => *prevouts, + _ => return Err(elements::sighash::Error::PrevoutKind), + }; + let utxos: Vec<_> = all + .iter() + .map(|o| ElementsUtxo::from(O::borrow(o).clone())) + .collect(); + + let simplicity_env = crate::jet::elements::ElementsEnv::new( + self.tx.clone(), + utxos, + input_index as u32, + script_cmr, + control_block, + None, + genesis_hash, + ); + let simplicity_sighash = simplicity_env.c_tx_env().sighash_all(); + + Ok(simplicity_sighash) + } +} From d64e3b220fa3cd04e3dea1ac570ca6775b04fa25 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Wed, 16 Aug 2023 16:04:45 +0200 Subject: [PATCH 7/7] policy: Iterator over fragments and keys The order of fragments doesn't seem to matter, so I chose preorder which is easy to implement. I would have used the iterators from dag.rs, but Dag doesn't cover nodes with arbitrary but fixed outdegree, like Policy::Threshold. It might be worth to extend Dag in the future, although that could make the generic code in dag.rs more complicated than it already is. --- src/policy/ast.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/policy/ast.rs b/src/policy/ast.rs index 4904c2c3..b541a788 100644 --- a/src/policy/ast.rs +++ b/src/policy/ast.rs @@ -215,6 +215,19 @@ impl Policy { _ => {} } } + + /// Return an iterator over the fragments of the policy. + pub fn iter(&self) -> PolicyIter<'_, Pk> { + PolicyIter::new(self) + } + + /// Return an iterator over the public keys of the policy. + pub fn iter_pk(&self) -> impl Iterator + '_ { + self.iter().filter_map(|fragment| match fragment { + Policy::Key(key) => Some(key.clone()), + _ => None, + }) + } } impl fmt::Debug for Policy { @@ -244,3 +257,39 @@ impl fmt::Display for Policy { fmt::Debug::fmt(self, f) } } + +/// Iterator over the fragments of a Simplicity policy. +/// +/// The fragments are visited in preorder: +/// We first visit the parent, then the left subtree, then the right subtree. +pub struct PolicyIter<'a, Pk: SimplicityKey> { + stack: Vec<&'a Policy>, +} + +impl<'a, Pk: SimplicityKey> PolicyIter<'a, Pk> { + /// Create an iterator for the given policy. + pub fn new(policy: &'a Policy) -> Self { + Self { + stack: vec![policy], + } + } +} + +impl<'a, Pk: SimplicityKey> Iterator for PolicyIter<'a, Pk> { + type Item = &'a Policy; + + fn next(&mut self) -> Option { + let top = self.stack.pop()?; + match top { + Policy::And { left, right } | Policy::Or { left, right } => { + self.stack.push(right); + self.stack.push(left); + } + Policy::Threshold(_, children) => { + self.stack.extend(children.iter().rev()); + } + _ => {} + } + Some(top) + } +}