diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 318a9ede..2ee3d03b 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -19,7 +19,6 @@ jobs: c_rust_merkle, decode_natural, decode_program, -parse_compile, ] steps: - name: Install test dependencies diff --git a/Cargo.toml b/Cargo.toml index 43905b62..16b1199c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" [features] default = [ "bitcoin", "elements" ] test-utils = ["simplicity-sys/test-utils"] -serde = ["actual-serde", "bitcoin/serde", "elements-miniscript/serde"] +serde = ["actual-serde", "bitcoin/serde", "elements/serde"] [lib] name = "simplicity" @@ -19,7 +19,6 @@ path = "src/lib.rs" bitcoin = { version = "0.30.0", optional = true } byteorder = "1.3" elements = { version = "0.22.0", optional = true } -elements-miniscript = "0.2.0" hashes = { package = "bitcoin_hashes", version = "0.12" } hex = { package = "hex-conservative", version = "0.1.1" } simplicity-sys = { version = "0.1.0", path = "./simplicity-sys" } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 3c3f1bef..8c8e859d 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -27,7 +27,3 @@ path = "fuzz_targets/decode_natural.rs" [[bin]] name = "decode_program" path = "fuzz_targets/decode_program.rs" - -[[bin]] -name = "parse_compile" -path = "fuzz_targets/parse_compile.rs" diff --git a/fuzz/fuzz_targets/parse_compile.rs b/fuzz/fuzz_targets/parse_compile.rs deleted file mode 100644 index 75169e3a..00000000 --- a/fuzz/fuzz_targets/parse_compile.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Rust Simplicity Library -// Written in 2022 by -// Christian Lewe -// -// To the extent possible under law, the author(s) have dedicated all -// copyright and related and neighboring rights to this software to -// the public domain worldwide. This software is distributed without -// any warranty. -// -// You should have received a copy of the CC0 Public Domain Dedication -// along with this software. -// If not, see . -// - -use honggfuzz::fuzz; - -use simplicity::bitcoin::key::XOnlyPublicKey; -use simplicity::policy::Policy; -use std::str::{self, FromStr}; - -fn do_test(data: &[u8]) { - let s = match str::from_utf8(data) { - Ok(s) => s, - Err(_) => return, - }; - let pol: Policy = match FromStr::from_str(s) { - Ok(p) => p, - Err(_) => return, - }; - let _ = pol.serialize_no_witness(); -} - -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} - -#[cfg(test)] -mod tests { - fn extend_vec_from_hex(hex: &str, out: &mut Vec) { - let mut b = 0; - for (idx, c) in hex.as_bytes().iter().enumerate() { - b <<= 4; - match *c { - b'A'..=b'F' => b |= c - b'A' + 10, - b'a'..=b'f' => b |= c - b'a' + 10, - b'0'..=b'9' => b |= c - b'0', - _ => panic!("Bad hex"), - } - if (idx & 1) == 1 { - out.push(b); - b = 0; - } - } - } - - #[test] - fn duplicate_crash() { - let mut a = Vec::new(); - extend_vec_from_hex("00000", &mut a); - super::do_test(&a); - } -} diff --git a/fuzz/generate-files.sh b/fuzz/generate-files.sh index 9dae1740..64caa0b3 100755 --- a/fuzz/generate-files.sh +++ b/fuzz/generate-files.sh @@ -24,8 +24,8 @@ honggfuzz = { version = "0.5.55", default-features = false } simplicity = { path = ".." } [patch.crates-io.bitcoin_hashes] -git = "https://github.com/apoelstra/bitcoin_hashes" -branch = "2023-04--fuzzcfg" +git = "https://github.com/apoelstra/rust-bitcoin" +tag = "2023-07--0.30.1-with-fuzzcfg" EOF for targetFile in $(listTargetFiles); do diff --git a/src/lib.rs b/src/lib.rs index db6e67ae..62668bc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,15 +28,11 @@ pub extern crate elements; /// Re-export of byteorder crate pub extern crate byteorder; -/// Re-export of elements_miniscript crate -pub extern crate elements_miniscript; /// Re-export of hashes crate pub extern crate hashes; /// Re-export of hex crate pub extern crate hex; -use elements_miniscript as miniscript; - #[macro_use] mod macros; @@ -59,7 +55,7 @@ pub use bit_encoding::BitWriter; pub use bit_encoding::{BitIter, EarlyEndOfStreamError}; #[cfg(feature = "elements")] -pub use crate::policy::{Descriptor, Policy}; +pub use crate::policy::{Policy, SimplicityKey, ToXOnlyPubkey, Translator}; pub use crate::bit_machine::BitMachine; pub use crate::encode::{encode_natural, encode_value, encode_witness}; @@ -95,8 +91,6 @@ pub enum Error { InconsistentWitnessLength, /// Tried to parse a jet but the name wasn't recognized InvalidJetName(String), - /// Miniscript error - MiniscriptError(miniscript::Error), /// Policy error #[cfg(feature = "elements")] Policy(policy::Error), @@ -119,7 +113,6 @@ impl fmt::Display for Error { } Error::InvalidJetName(s) => write!(f, "unknown jet `{}`", s), Error::NoMoreWitnesses => f.write_str("no more witness data available"), - Error::MiniscriptError(ref e) => fmt::Display::fmt(e, f), #[cfg(feature = "elements")] Error::Policy(ref e) => fmt::Display::fmt(e, f), } @@ -137,7 +130,6 @@ impl std::error::Error for Error { Error::IncompleteFinalization => None, Error::InconsistentWitnessLength => None, Error::InvalidJetName(..) => None, - Error::MiniscriptError(ref e) => Some(e), #[cfg(feature = "elements")] Error::Policy(ref e) => Some(e), } @@ -162,12 +154,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: miniscript::Error) -> Error { - Error::MiniscriptError(e) - } -} - #[cfg(feature = "elements")] impl From for Error { fn from(e: policy::Error) -> Error { diff --git a/src/macros.rs b/src/macros.rs index eac5a027..cb4adb40 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -14,105 +14,3 @@ macro_rules! decode_bits { } }; } - -/// Copied from rust-miniscript -/// -/// A macro that implements serde serialization and deserialization using the -/// `fmt::Display` and `str::FromStr` traits. -#[cfg(feature = "elements")] -macro_rules! serde_string_impl_pk { - ($name:ident, $expecting:expr $(, $gen:ident; $gen_con:ident)*) => { - #[cfg(feature = "serde")] - impl<'de, Pk $(, $gen)*> $crate::serde::Deserialize<'de> for $name - where - Pk: crate::miniscript::ToPublicKey + core::str::FromStr, - Pk::Sha256: core::str::FromStr, - Pk::Hash256: core::str::FromStr, - Pk::Ripemd160: core::str::FromStr, - Pk::Hash160: core::str::FromStr, - ::Err: core::fmt::Display, - <::Sha256 as core::str::FromStr>::Err: - core::fmt::Display, - <::Hash256 as core::str::FromStr>::Err: - core::fmt::Display, - <::Ripemd160 as core::str::FromStr>::Err: - core::fmt::Display, - <::Hash160 as core::str::FromStr>::Err: - core::fmt::Display, - $($gen : $gen_con,)* - { - fn deserialize(deserializer: D) -> Result<$name, D::Error> - where - D: $crate::serde::de::Deserializer<'de>, - { - use core::fmt::{self, Formatter}; - use core::marker::PhantomData; - use core::str::FromStr; - - #[allow(unused_parens)] - struct Visitor(PhantomData<(Pk $(, $gen)*)>); - impl<'de, Pk $(, $gen)*> $crate::serde::de::Visitor<'de> for Visitor - where - Pk: crate::miniscript::ToPublicKey + core::str::FromStr, - Pk::Sha256: core::str::FromStr, - Pk::Hash256: core::str::FromStr, - Pk::Ripemd160: core::str::FromStr, - Pk::Hash160: core::str::FromStr, - ::Err: core::fmt::Display, - <::Sha256 as core::str::FromStr>::Err: - core::fmt::Display, - <::Hash256 as core::str::FromStr>::Err: - core::fmt::Display, - <::Ripemd160 as core::str::FromStr>::Err: - core::fmt::Display, - <::Hash160 as core::str::FromStr>::Err: - core::fmt::Display, - $($gen: $gen_con,)* - { - type Value = $name; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str($expecting) - } - - fn visit_str(self, v: &str) -> Result - where - E: $crate::serde::de::Error, - { - $name::from_str(v).map_err(E::custom) - } - - fn visit_borrowed_str(self, v: &'de str) -> Result - where - E: $crate::serde::de::Error, - { - self.visit_str(v) - } - - fn visit_string(self, v: String) -> Result - where - E: $crate::serde::de::Error, - { - self.visit_str(&v) - } - } - - deserializer.deserialize_str(Visitor(PhantomData)) - } - } - - #[cfg(feature = "serde")] - impl<'de, Pk $(, $gen)*> $crate::serde::Serialize for $name - where - Pk: crate::miniscript::ToPublicKey, - $($gen: $gen_con,)* - { - fn serialize(&self, serializer: S) -> Result - where - S: $crate::serde::Serializer, - { - serializer.collect_str(&self) - } - } - }; -} diff --git a/src/policy/ast.rs b/src/policy/ast.rs index 2d754012..cffa829e 100644 --- a/src/policy/ast.rs +++ b/src/policy/ast.rs @@ -25,9 +25,9 @@ use std::sync::Arc; use std::{fmt, iter, mem}; use crate::jet::Elements; -use crate::miniscript::{MiniscriptKey, ToPublicKey, Translator}; use crate::node::{ConstructNode, NoWitness}; use crate::FailEntropy; +use crate::{SimplicityKey, ToXOnlyPubkey, Translator}; use super::serialize; @@ -38,7 +38,7 @@ use super::serialize; /// /// Furthermore, the policy can be normalized and is amenable to semantic analysis. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Policy { +pub enum Policy { /// Unsatisfiable Unsatisfiable(FailEntropy), /// Trivially satisfiable @@ -65,11 +65,11 @@ pub enum Policy { Threshold(usize, Vec>), } -impl Policy { +impl Policy { /// Serializes the policy as a Simplicity fragment, with all witness nodes unpopulated. pub fn serialize_no_witness(&self) -> Arc> where - Pk: ToPublicKey, + Pk: ToXOnlyPubkey, { match *self { Policy::Unsatisfiable(entropy) => serialize::unsatisfiable(entropy), @@ -112,7 +112,7 @@ impl Policy { pub fn translate(&self, translator: &mut T) -> Result, E> where T: Translator, - Q: MiniscriptKey, + Q: SimplicityKey, { match *self { Policy::Unsatisfiable(entropy) => Ok(Policy::Unsatisfiable(entropy)), @@ -217,7 +217,7 @@ impl Policy { } } -impl fmt::Debug for Policy { +impl fmt::Debug for Policy { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Policy::Unsatisfiable(..) => f.write_str("UNSATISFIABLE"), @@ -239,7 +239,7 @@ impl fmt::Debug for Policy { } } -impl fmt::Display for Policy { +impl fmt::Display for Policy { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self, f) } diff --git a/src/policy/descriptor.rs b/src/policy/descriptor.rs deleted file mode 100644 index adf517f4..00000000 --- a/src/policy/descriptor.rs +++ /dev/null @@ -1,201 +0,0 @@ -use crate::policy::satisfy::PolicySatisfier; -use crate::{Cmr, Error, Policy}; -use elements::schnorr::{TapTweak, XOnlyPublicKey}; -use elements::secp256k1_zkp; -use elements::taproot::{ - ControlBlock, LeafVersion, TapBranchHash, TapLeafHash, TaprootBuilder, TaprootMerkleBranch, - TaprootSpendInfo, -}; -use elements_miniscript::{MiniscriptKey, ToPublicKey}; -use hashes::Hash; -use std::fmt; -use std::str::FromStr; - -pub trait UnspendableKey: MiniscriptKey { - fn unspendable() -> Self; -} - -impl UnspendableKey for XOnlyPublicKey { - fn unspendable() -> Self { - XOnlyPublicKey::from_slice(&UNSPENDABLE_PUBLIC_KEY).expect("unspendable pubkey is valid") - } -} - -/// Bytes of x-only public key whose discrete logarithm (secret key) is unknown -/// -/// Taken from BIP 341 -const UNSPENDABLE_PUBLIC_KEY: [u8; 32] = [ - 0x50, 0x92, 0x9b, 0x74, 0xc1, 0xa0, 0x49, 0x54, 0xb7, 0x8b, 0x4b, 0x60, 0x35, 0xe9, 0x7a, 0x5e, - 0x07, 0x8a, 0x5a, 0x0f, 0x28, 0xec, 0x96, 0xd5, 0x47, 0xbf, 0xee, 0x9a, 0xce, 0x80, 0x3a, 0xc0, -]; -/// Version of Simplicity tap leaves -pub const TAPROOT_LEAF_SIMPLICITY: u8 = 0xbe; - -/// Return the version of Simplicity tap leaves -pub fn leaf_version() -> LeafVersion { - LeafVersion::from_u8(TAPROOT_LEAF_SIMPLICITY).expect("constant leaf version") -} - -// TODO: Support multiple tap leaves -/// Descriptor of Simplicity outputs. -/// -/// Simplicity is embedded in Taproot outputs as tap leaves with version 0xbe. -/// -/// The tap tree can include many leaves, be it version 0xc0 (standard Taproot) or 0xbe (Simplicity). -/// Currently only trees with a single Simplicity leaf are supported. -/// -/// On "top" of the tap tree sits the internal key which can be used as an alternative spending path -/// if all parties that own the output agree to sign. -/// -/// 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)] -pub struct Descriptor { - internal_key: Pk, - spend_info: TaprootSpendInfo, - policy: Policy, - cmr: Cmr, -} - -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(); - - 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"); - - Ok(Self { - internal_key, - spend_info, - policy, - cmr, - }) - } - - /// 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 - where - Pk: UnspendableKey, - { - let internal_key = Pk::unspendable(); - Self::new(internal_key, policy) - } - - /// Return the internal key - pub fn internal_key(&self) -> &Pk { - &self.internal_key - } - - /// Return the spend data - pub fn spend_info(&self) -> &TaprootSpendInfo { - &self.spend_info - } - - /// Return the script pubkey - pub fn script_pubkey(&self) -> elements::Script { - let output_key = self.spend_info().output_key(); - let builder = elements::script::Builder::new(); - builder - .push_opcode(elements::opcodes::all::OP_PUSHNUM_1) - .push_slice(&output_key.as_inner().serialize()) - .into_script() - } - - /// Return the Elements address on the given network - pub fn address(&self, params: &'static elements::AddressParams) -> elements::Address { - let output_key = self.spend_info().output_key(); - 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 version = leaf_version(); - (script, version) - } - - /// 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( - &self, - satisfier: &PolicySatisfier, - ) -> Result<(Vec>, elements::Script), Error> { - let program = self.policy.satisfy(satisfier)?; - - // Uncomment code below for sanity check - // that program successfully runs on Bit Machine - // let mut mac = crate::exec::BitMachine::for_program(&program); - // let _output = mac - // .exec(&program, &satisfier.env) - // .expect("sanity check"); - - let program_and_witness_bytes = program.encode_to_vec(); - let cmr_bytes = Vec::from(program.cmr().as_ref()); - - // Single leaf: leaf hash = merkle root - let (script, leaf_version) = self.leaf(); - let leaf_hash = TapLeafHash::from_script(&script, leaf_version); - let merkle_root = TapBranchHash::from_byte_array(leaf_hash.to_byte_array()); - // Single leaf: empty merkle inclusion proof - let merkle_branch = TaprootMerkleBranch::from_inner(Vec::new()).expect("constant branch"); - - let internal_key = self.internal_key().to_x_only_pubkey(); - let output_key_parity = internal_key - .tap_tweak(secp256k1_zkp::SECP256K1, Some(merkle_root)) - .1; - - let control_block = ControlBlock { - leaf_version, - output_key_parity, - internal_key, - merkle_branch, - }; - let witness = vec![ - program_and_witness_bytes, - cmr_bytes, - control_block.serialize(), - ]; - let script_sig = elements::Script::new(); - - Ok((witness, script_sig)) - } -} - -impl fmt::Display for Descriptor { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.policy, f) - } -} - -impl FromStr for Descriptor -where - Pk: UnspendableKey + ToPublicKey + FromStr, - Pk::Sha256: FromStr, - ::Err: ToString, - ::Err: ToString, -{ - type Err = Error; - - fn from_str(s: &str) -> Result { - let policy = Policy::from_str(s)?; - Self::single_leaf(policy) - } -} diff --git a/src/policy/embed.rs b/src/policy/embed.rs deleted file mode 100644 index 0a9284fa..00000000 --- a/src/policy/embed.rs +++ /dev/null @@ -1,219 +0,0 @@ -use std::convert::{TryFrom, TryInto}; -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, policy}; - -use crate::policy::Policy; -use crate::{Error, FailEntropy}; - -serde_string_impl_pk!(Policy, "a Simplicity policy"); - -impl FromStr for Policy -where - Pk: ToPublicKey + FromStr, - Pk::Sha256: FromStr, - ::Err: ToString, - ::Err: ToString, -{ - type Err = miniscript::Error; - - fn from_str(s: &str) -> Result, Self::Err> { - for ch in s.as_bytes() { - if *ch < 20 || *ch > 127 { - return Err(miniscript::Error::Unprintable(*ch)); - } - } - - let tree = expression::Tree::from_str(s)?; - expression::FromTree::from_tree(&tree) - } -} - -impl expression::FromTree for Policy -where - Pk: ToPublicKey + FromStr, - Pk::Sha256: FromStr, - ::Err: ToString, - ::Err: ToString, -{ - fn from_tree(top: &expression::Tree) -> Result, msError> { - use miniscript::policy::concrete::PolicyError as MsPolicyError; - match (top.name, top.args.len() as u32) { - ("UNSATISFIABLE", 0) => Ok(Policy::Unsatisfiable(FailEntropy::ZERO)), - ("TRIVIAL", 0) => Ok(Policy::Trivial), - ("pk", 1) => expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Policy::Key)), - ("after", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x).map(Policy::After) - }), - ("older", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x).map(Policy::Older) - }), - ("sha256", 1) => expression::terminal(&top.args[0], |x| { - Pk::Sha256::from_str(x).map(Policy::Sha256) - }), - ("and", _) => { - if top.args.len() != 2 { - return Err(msError::PolicyError(MsPolicyError::NonBinaryArgAnd)); - } - let left = Arc::new(Policy::from_tree(&top.args[0])?); - let right = Arc::new(Policy::from_tree(&top.args[0])?); - Ok(Policy::And { left, right }) - } - ("or", _) => { - if top.args.len() != 2 { - return Err(msError::PolicyError(MsPolicyError::NonBinaryArgOr)); - } - let left = Arc::new(Policy::from_tree(&top.args[0])?); - let right = Arc::new(Policy::from_tree(&top.args[0])?); - Ok(Policy::Or { left, right }) - } - ("thresh", nsubs) => { - if nsubs == 0 { - return Err(msError::Unexpected("thresh without args".to_owned())); - } - if nsubs < 3 { - return Err(msError::Unexpected( - "thresh must have a threshold value and at least 2 children".to_owned(), - )); - } - if !top.args[0].args.is_empty() { - return Err(msError::Unexpected(top.args[0].args[0].name.to_owned())); - } - - let thresh: u32 = expression::parse_num(top.args[0].name)?; - if thresh >= nsubs { - return Err(msError::Unexpected(top.args[0].name.to_owned())); - } - - let mut subs = Vec::with_capacity(top.args.len() - 1); - for arg in &top.args[1..] { - subs.push(Policy::from_tree(arg)?); - } - Ok(Policy::Threshold(thresh as usize, subs)) - } - _ => Err(msError::Unexpected(top.name.to_owned())), - } - } -} - -impl<'a, Pk: ToPublicKey, Ctx: ScriptContext> TryFrom<&'a Miniscript> for Policy { - type Error = Error; - - fn try_from(top: &Miniscript) -> Result { - match &top.node { - Terminal::True => Ok(Policy::Trivial), - Terminal::False => Ok(Policy::Unsatisfiable(FailEntropy::ZERO)), - Terminal::PkK(key) => Ok(Policy::Key(key.clone())), - Terminal::PkH(_) | Terminal::RawPkH(_) => { - Err(Error::Policy(policy::Error::PublicKeyHash)) - } - Terminal::After(n) => Ok(Policy::After(n.to_consensus_u32())), - Terminal::Older(n) => { - if !n.is_height_locked() { - return Err(Error::Policy(policy::Error::InvalidSequence)); - } - - let low_16 = n.0 as u16; - Ok(Policy::Older(low_16)) - } - Terminal::Sha256(h) => Ok(Policy::Sha256(h.clone())), - Terminal::Hash256(_h) => Err(Error::Policy(policy::Error::Sha256d)), - Terminal::Ripemd160(_) | Terminal::Hash160(_) => { - Err(Error::Policy(policy::Error::Ripemd160)) - } - Terminal::AndV(x, y) | Terminal::AndB(x, y) => { - let left = Arc::new(x.as_ref().try_into()?); - let right = Arc::new(y.as_ref().try_into()?); - Ok(Policy::And { left, right }) - } - Terminal::AndOr(x, y, z) => { - let and_left = Arc::new(x.as_ref().try_into()?); - let and_right = Arc::new(y.as_ref().try_into()?); - let and = Arc::new(Policy::And { - left: and_left, - right: and_right, - }); - let or_right = Arc::new(z.as_ref().try_into()?); - Ok(Policy::Or { - left: and, - right: or_right, - }) - } - Terminal::OrB(x, y) - | Terminal::OrD(x, y) - | Terminal::OrC(x, y) - | Terminal::OrI(x, y) => { - let left = Arc::new(x.as_ref().try_into()?); - let right = Arc::new(y.as_ref().try_into()?); - Ok(Policy::Or { left, right }) - } - Terminal::Thresh(k, sub_policies) => { - let mut translated_sub_policies = Vec::with_capacity(sub_policies.len()); - - for sub in sub_policies { - translated_sub_policies.push(sub.as_ref().try_into()?); - } - - Ok(Policy::Threshold(*k, translated_sub_policies)) - } - Terminal::Alt(x) - | Terminal::Swap(x) - | Terminal::Check(x) - | Terminal::DupIf(x) - | Terminal::Verify(x) - | Terminal::NonZero(x) - | Terminal::ZeroNotEqual(x) => x.as_ref().try_into(), - Terminal::Multi(_, _) | Terminal::MultiA(_, _) => { - Err(Error::Policy(policy::Error::Multisig)) - } - Terminal::Ext(_) => Err(Error::Policy(policy::Error::Extensions)), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::miniscript::bitcoin::key::XOnlyPublicKey; - - #[test] - fn parse_bad_thresh() { - assert_eq!( - Policy::::from_str("thresh()"), - Err(msError::Unexpected( - "thresh must have a threshold value and at least 2 children".to_string() - )), - ); - - assert_eq!( - Policy::::from_str("thresh"), - Err(msError::Unexpected("thresh without args".to_string())), - ); - - assert_eq!( - Policy::::from_str("thresh(0)"), - Err(msError::Unexpected( - "thresh must have a threshold value and at least 2 children".to_string() - )), - ); - - assert_eq!( - Policy::::from_str("thresh(0,TRIVIAL)"), - Err(msError::Unexpected( - "thresh must have a threshold value and at least 2 children".to_string() - )), - ); - - assert!(Policy::::from_str("thresh(0,TRIVIAL,TRIVIAL)").is_ok()); - assert!(Policy::::from_str("thresh(2,TRIVIAL,TRIVIAL)").is_ok()); - - assert_eq!( - Policy::::from_str("thresh(3,TRIVIAL,TRIVIAL)"), - Err(msError::Unexpected("3".to_string())), - ); - } -} diff --git a/src/policy/key.rs b/src/policy/key.rs new file mode 100644 index 00000000..682320a5 --- /dev/null +++ b/src/policy/key.rs @@ -0,0 +1,45 @@ +use elements::bitcoin::key::XOnlyPublicKey; +use hashes::sha256; +use std::fmt::{Debug, Display}; + +/// Public key which can be converted to a hash type. +pub trait SimplicityKey: Clone + Eq + Ord + Debug + Display + std::hash::Hash { + /// SHA 256 hash associated with this key, used in the sha256 fragment. + type Sha256: Clone + Eq + Ord + Display + Debug + std::hash::Hash; +} + +impl SimplicityKey for XOnlyPublicKey { + type Sha256 = sha256::Hash; +} + +/// Public key which can be converted to a (x-only) public key which can be used in Simplicity. +pub trait ToXOnlyPubkey: SimplicityKey { + /// Convert the key to an x-only public key. + fn to_x_only_pubkey(&self) -> XOnlyPublicKey; + + /// Convert the generic associated [`SimplicityKey::Sha256`] to [`sha256::Hash`]. + fn to_sha256(hash: &Self::Sha256) -> sha256::Hash; +} + +impl ToXOnlyPubkey for XOnlyPublicKey { + fn to_x_only_pubkey(&self) -> XOnlyPublicKey { + *self + } + + fn to_sha256(hash: &Self::Sha256) -> sha256::Hash { + *hash + } +} + +/// Object which can translate one key type to another, including all associated hashes. +pub trait Translator +where + P: SimplicityKey, + Q: SimplicityKey, +{ + /// Translates public keys [`P`] → [`Q`]. + fn pk(&mut self, pk: &P) -> Result; + + /// Translates SHA 256 hashes [`P::Sha256`] → [`Q::Sha256`]. + fn sha256(&mut self, sha256: &P::Sha256) -> Result; +} diff --git a/src/policy/mod.rs b/src/policy/mod.rs index f21315d0..a9a3e53e 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -26,12 +26,11 @@ //! mod ast; -pub mod descriptor; -mod embed; mod error; +mod key; pub mod satisfy; mod serialize; pub use ast::Policy; -pub use descriptor::Descriptor; pub use error::Error; +pub use key::{SimplicityKey, ToXOnlyPubkey, Translator}; diff --git a/src/policy/satisfy.rs b/src/policy/satisfy.rs index b98c6e08..05429d5a 100644 --- a/src/policy/satisfy.rs +++ b/src/policy/satisfy.rs @@ -1,44 +1,81 @@ +use crate::analysis::Cost; use crate::jet::Elements; use crate::node::{RedeemNode, WitnessNode}; +use crate::policy::ToXOnlyPubkey; use crate::{Error, Policy, Value}; +use elements::bitcoin; use elements::locktime::Height; use elements::taproot::TapLeafHash; -use elements_miniscript::{MiniscriptKey, Preimage32, Satisfier, ToPublicKey}; use hashes::Hash; -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, -} +/// Type alias for 32-byte preimage. +pub type Preimage32 = [u8; 32]; + +/// Lookup table for signatures, hash preimages, etc. +/// +/// Every method has a default implementation that simply returns `None` +/// on every query. Users are expected to override the methods that they +/// have data for. +pub trait Satisfier { + /// Given a public key, look up a Schnorr signature with that key. + fn lookup_tap_leaf_script_sig(&self, _: &Pk, _: &TapLeafHash) -> Option { + None + } -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() + /// Given a SHA256 hash, look up its preimage. + fn lookup_sha256(&self, _: &Pk::Sha256) -> Option { + None } - fn lookup_sha256(&self, hash: &Pk::Sha256) -> Option { - self.preimages.get(hash).copied() + /// Assert that a relative locktime is satisfied. + fn check_older(&self, _: elements::Sequence) -> bool { + false } - fn check_older(&self, sequence: elements::Sequence) -> bool { - let self_sequence = self.tx.input[self.index].sequence; - >::check_older(&self_sequence, sequence) + /// Assert that an absolute locktime is satisfied. + fn check_after(&self, _: elements::LockTime) -> bool { + false } +} + +impl Satisfier for elements::Sequence { + fn check_older(&self, n: elements::Sequence) -> bool { + use elements::bitcoin::locktime::relative::LockTime::*; - fn check_after(&self, locktime: elements::LockTime) -> bool { - >::check_after(&self.tx.lock_time, locktime) + let this = match bitcoin::Sequence(self.0).to_relative_lock_time() { + Some(x) => x, + None => return false, + }; + let n = match bitcoin::Sequence(n.0).to_relative_lock_time() { + Some(x) => x, + None => return false, + }; + + match (n, this) { + (Blocks(n), Blocks(lock_time)) => n <= lock_time, + (Time(n), Time(lock_time)) => n <= lock_time, + _ => false, // Not the same units + } + } +} + +impl Satisfier for elements::LockTime { + fn check_after(&self, n: elements::LockTime) -> bool { + use elements::LockTime::*; + + match (n, *self) { + (Blocks(n), Blocks(lock_time)) => n <= lock_time, + (Seconds(n), Seconds(lock_time)) => n <= lock_time, + _ => false, // Not the same units. + } } } -impl Policy { +impl Policy { pub fn satisfy_internal>( &self, satisfier: &S, @@ -175,15 +212,45 @@ mod tests { use crate::dag::{DagLike, NoSharing}; use crate::jet::elements::ElementsEnv; use crate::node::SimpleFinalizer; - use crate::{BitMachine, FailEntropy}; + use crate::{BitMachine, FailEntropy, SimplicityKey}; use elements::bitcoin::key::{KeyPair, XOnlyPublicKey}; - use elements::{secp256k1_zkp, LockTime, SchnorrSigHashType}; + use elements::secp256k1_zkp; use hashes::{sha256, Hash}; + use std::collections::HashMap; use std::sync::Arc; + pub struct PolicySatisfier<'a, Pk: SimplicityKey> { + pub preimages: HashMap, + pub signatures: HashMap, + pub tx: &'a elements::Transaction, + pub index: usize, + } + + impl<'a, Pk: ToXOnlyPubkey> 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: elements::Sequence) -> bool { + let self_sequence = self.tx.input[self.index].sequence; + >::check_older(&self_sequence, sequence) + } + + fn check_after(&self, locktime: elements::LockTime) -> bool { + >::check_after(&self.tx.lock_time, locktime) + } + } + fn get_satisfier(env: &ElementsEnv) -> PolicySatisfier { let mut preimages = HashMap::new(); - let mut signatures = HashMap::new(); for i in 0..3 { let preimage = [i; 32]; @@ -192,6 +259,7 @@ mod tests { let secp = secp256k1_zkp::Secp256k1::new(); let mut rng = secp256k1_zkp::rand::rngs::ThreadRng::default(); + let mut signatures = HashMap::new(); for _ in 0..3 { let keypair = KeyPair::new(&secp, &mut rng); @@ -201,7 +269,7 @@ mod tests { let msg = secp256k1_zkp::Message::from(sighash); let sig = elements::SchnorrSig { sig: keypair.sign_schnorr(msg), - hash_ty: SchnorrSigHashType::All, + hash_ty: elements::SchnorrSigHashType::All, }; signatures.insert(xonly, sig); @@ -211,7 +279,7 @@ mod tests { preimages, signatures, tx: env.tx(), - index: 0, + index: env.ix() as usize, } } @@ -304,7 +372,8 @@ mod tests { #[test] fn satisfy_after() { let height = Height::from_consensus(42).unwrap(); - let env = ElementsEnv::dummy_with(LockTime::Blocks(height), elements::Sequence::ZERO); + let env = + ElementsEnv::dummy_with(elements::LockTime::Blocks(height), elements::Sequence::ZERO); let satisfier = get_satisfier(&env); let policy0 = Policy::After(41); @@ -325,7 +394,10 @@ mod tests { #[test] fn satisfy_older() { - let env = ElementsEnv::dummy_with(LockTime::ZERO, elements::Sequence::from_consensus(42)); + let env = ElementsEnv::dummy_with( + elements::LockTime::ZERO, + elements::Sequence::from_consensus(42), + ); let satisfier = get_satisfier(&env); let policy0 = Policy::Older(41); @@ -477,6 +549,8 @@ mod tests { witidx += 1; } } + + execute_successful(program, &env); }; let image_from_bit = |bit: bool, j: u8| { diff --git a/src/policy/serialize.rs b/src/policy/serialize.rs index a9d7c58d..8dc0994a 100644 --- a/src/policy/serialize.rs +++ b/src/policy/serialize.rs @@ -15,9 +15,9 @@ //! Serialization of Policy as Simplicity use crate::jet::Elements; -use crate::miniscript::ToPublicKey; use crate::node::{self, Node}; use crate::node::{CoreConstructible, JetConstructible, WitnessConstructible}; +use crate::ToXOnlyPubkey; use crate::{FailEntropy, Value}; use std::convert::TryFrom; @@ -43,7 +43,7 @@ where pub fn key(key: &Pk, witness: N::Witness) -> ArcNode where - Pk: ToPublicKey, + Pk: ToXOnlyPubkey, N: node::Marker, ArcNode: CoreConstructible + JetConstructible + WitnessConstructible, { @@ -107,7 +107,7 @@ where pub fn sha256(hash: &Pk::Sha256, witness: N::Witness) -> ArcNode where - Pk: ToPublicKey, + Pk: ToXOnlyPubkey, N: node::Marker, ArcNode: CoreConstructible + JetConstructible + WitnessConstructible, {