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/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/lib.rs b/src/lib.rs index 62668bc1..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, 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}; @@ -71,6 +73,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)] diff --git a/src/policy/ast.rs b/src/policy/ast.rs index cffa829e..b541a788 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), @@ -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) + } +} 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) } } diff --git a/src/policy/mod.rs b/src/policy/mod.rs index a9a3e53e..7417106b 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -28,9 +28,11 @@ mod ast; mod error; mod key; -pub mod satisfy; +mod satisfy; mod serialize; +pub mod sighash; pub use ast::Policy; pub use error::Error; pub use key::{SimplicityKey, ToXOnlyPubkey, Translator}; +pub use satisfy::{Preimage32, Satisfier}; 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() 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) + } +}