From 0071010929b7837a2599eb6bc358b31cbec1bfb4 Mon Sep 17 00:00:00 2001 From: Kyryl R Date: Sun, 23 Aug 2026 12:22:43 +0300 Subject: [PATCH 1/5] general: bump MSRV to align it with rust-simplicity + formatting run --- Cargo.toml | 2 +- README.md | 4 +- clippy.toml | 2 +- src/confidential/elip151.rs | 43 ++++++++++---- src/confidential/mod.rs | 90 ++++++++++++++++++++++------- src/descriptor/key.rs | 8 +-- src/descriptor/tr.rs | 68 ++++++++++++---------- src/interpreter/mod.rs | 5 +- src/miniscript/context.rs | 9 +-- src/miniscript/types/extra_props.rs | 21 ++++--- src/policy/compiler.rs | 12 ++-- src/simplicity.rs | 22 ++++--- 12 files changed, 183 insertions(+), 103 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fcce1038..7397d880 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ keywords = [ "crypto", "bitcoin", "miniscript", "script" ] readme = "README.md" homepage = "https://github.com/rust-bitcoin/rust-miniscript/" edition = "2018" -rust-version = "1.63.0" +rust-version = "1.74.0" [features] compiler = [] diff --git a/README.md b/README.md index d8962f24..79e0d720 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Build](https://github.com/ElementsProject/elements-miniscript/workflows/Continuous%20integration/badge.svg) -**Minimum Supported Rust Version:** 1.63.0 +**Minimum Supported Rust Version:** 1.74.0 *This crate uses "2018" edition @@ -40,7 +40,7 @@ Enabling the `no-std` feature does not disable `std`. To disable the `std` featu To run the benchmarks run `RUSTFLAGS=--cfg=miniscript_bench cargo +nightly bench --all-features`. ## Minimum Supported Rust Version (MSRV) -This library should always compile with any combination of features on **Rust 1.63.0**. +This library should always compile with any combination of features on **Rust 1.74.0**. Some dependencies do not play nicely with our MSRV, if you are running the tests diff --git a/clippy.toml b/clippy.toml index dfe5dd9b..9c7dde4a 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,3 +1,3 @@ -msrv = "1.63.0" +msrv = "1.74.0" # PSBT API returns Self as an error type for an large-ish enum large-error-threshold = 289 diff --git a/src/confidential/elip151.rs b/src/confidential/elip151.rs index 14622781..2ae427d7 100644 --- a/src/confidential/elip151.rs +++ b/src/confidential/elip151.rs @@ -103,7 +103,7 @@ impl ConfidentialDescriptor mod test { use super::*; use crate::descriptor::checksum::desc_checksum; - use bitcoin::hashes::{sha256, HashEngine, sha256t::Tag}; + use bitcoin::hashes::{sha256, sha256t::Tag, HashEngine}; use std::str::FromStr; /// The SHA-256 initial midstate value for the [`Elip151Hash`]. @@ -124,7 +124,10 @@ mod test { // Test empty hash let expected = "dcd8403dcf5af960f69fa41d114931a840877dfb5378046018f78ea894a36ebd"; - assert_eq!(Elip151Hash::from_engine(Elip151Tag::engine()).to_string(), expected); + assert_eq!( + Elip151Hash::from_engine(Elip151Tag::engine()).to_string(), + expected + ); assert_eq!(Elip151Hash::hash(&[]).to_string(), expected); } @@ -174,10 +177,22 @@ mod test { let mut _i = 0; for (desc, key) in [ - (&format!("elwpkh({xpub}/<0;1>/*)"), "b3baf94d60cf8423cd257283575997a2c00664ced3e8de00f8726703142b1989"), - (&format!("elwpkh({xpub}/0/*)"), "de9c5fb624154624146a8aea0489b30f05c720eed6b493b1f3ab63405a11bf37"), - (&format!("elwsh(multi(2,{xpub}/<0;1>/*,{xpub}/0/<0;1>/*))"), "7fcc1b9a20bbf611d157016192a7d28e353033cfa6a4885b3c48fa5ff9ce1881"), - (&format!("elwsh(multi(2,{xpub}/<0;1>/*,{xpub}/0/<1;2>/*))"), "ff0a08050417f0ca95fb6ef7df979ae464739cb79b8c8f4b05408e0ac681a527"), + ( + &format!("elwpkh({xpub}/<0;1>/*)"), + "b3baf94d60cf8423cd257283575997a2c00664ced3e8de00f8726703142b1989", + ), + ( + &format!("elwpkh({xpub}/0/*)"), + "de9c5fb624154624146a8aea0489b30f05c720eed6b493b1f3ab63405a11bf37", + ), + ( + &format!("elwsh(multi(2,{xpub}/<0;1>/*,{xpub}/0/<0;1>/*))"), + "7fcc1b9a20bbf611d157016192a7d28e353033cfa6a4885b3c48fa5ff9ce1881", + ), + ( + &format!("elwsh(multi(2,{xpub}/<0;1>/*,{xpub}/0/<1;2>/*))"), + "ff0a08050417f0ca95fb6ef7df979ae464739cb79b8c8f4b05408e0ac681a527", + ), ] { let conf_desc = confidential_descriptor(desc).unwrap(); let elip151_desc = add_checksum(&format!("ct(elip151,{})", desc)); @@ -202,9 +217,18 @@ mod test { _i = 0; let text = "Descriptors without wildcards are not supported in elip151".to_string(); for (invalid_desc, expected_err) in [ - (&format!("elwpkh({xpub})"), Error::Unexpected(text.to_string())), - (&format!("elwpkh({pubkey})"), Error::Unexpected(text.to_string())), - (&format!("elwsh(multi(2,{xpub}/<0;1>/*,{xpub}/0/<0;1;2>/*))"), Error::MultipathDescLenMismatch), + ( + &format!("elwpkh({xpub})"), + Error::Unexpected(text.to_string()), + ), + ( + &format!("elwpkh({pubkey})"), + Error::Unexpected(text.to_string()), + ), + ( + &format!("elwsh(multi(2,{xpub}/<0;1>/*,{xpub}/0/<0;1;2>/*))"), + Error::MultipathDescLenMismatch, + ), ] { let err = confidential_descriptor(invalid_desc).unwrap_err(); assert_eq!(err, expected_err); @@ -215,6 +239,5 @@ mod test { println!("** Invalid confidential descriptor: {}", add_checksum(&format!("ct(elip151,{})", invalid_desc))); */ } - } } diff --git a/src/confidential/mod.rs b/src/confidential/mod.rs index 828600a3..b9ab10bd 100644 --- a/src/confidential/mod.rs +++ b/src/confidential/mod.rs @@ -28,8 +28,8 @@ use elements::secp256k1_zkp; use crate::descriptor::checksum::{self, verify_checksum}; use crate::descriptor::{ - ConversionError, DefiniteDescriptorKey, DescriptorSecretKey, DescriptorPublicKey, - DescriptorXKey, Wildcard + ConversionError, DefiniteDescriptorKey, DescriptorPublicKey, DescriptorSecretKey, + DescriptorXKey, Wildcard, }; use crate::expression::FromTree; use crate::extensions::{CovExtArgs, CovenantExt, Extension, ParseableExt}; @@ -83,13 +83,16 @@ impl Key { // Convert into a DefiniteDescriptorKey, note that we are deriving the xpub // since there is not wildcard. // Consider adding DescriptorPublicKey::to_definite_descriptor - let pk = pk.clone().at_derivation_index(0).expect("single or xpub without wildcards"); + let pk = pk + .clone() + .at_derivation_index(0) + .expect("single or xpub without wildcards"); // Derive explicitly rather than relying on ToPublicKey::to_public_key, // which panics on keys with hardened derivation steps. let pk = pk.derive_public_key(secp)?; Ok(bare::tweak_key(secp, spk, &pk)) } - }, + } Key::View(ref sk) => { if sk.is_multipath() { Err(Error::Unexpected("multipath blinding key".into())) @@ -98,11 +101,13 @@ impl Key { if pk.has_wildcard() { Err(Error::Unexpected("wildcard blinding key".into())) } else { - let pk = pk.at_derivation_index(0).expect("single or xprv without wildcards"); + let pk = pk + .at_derivation_index(0) + .expect("single or xprv without wildcards"); Ok(bare::tweak_key(secp, spk, &pk)) } } - }, + } } } } @@ -130,7 +135,10 @@ impl Descriptor { /// /// # Errors /// - If index ≥ 2^31 - pub fn at_derivation_index(&self, index: u32) -> Result, ConversionError> { + pub fn at_derivation_index( + &self, + index: u32, + ) -> Result, ConversionError> { let definite_key = match self.key.clone() { Key::Slip77(k) => Key::Slip77(k), Key::Bare(k) => Key::Bare(k.at_derivation_index(index)?.into_descriptor_public_key()), @@ -157,12 +165,12 @@ impl Descriptor { derivation_path, wildcard: Wildcard::None, }) - }, + } DescriptorSecretKey::MultiXPrv(_) => return Err(ConversionError::MultiKey), }), }; let definite_descriptor = self.descriptor.at_derivation_index(index)?; - Ok(Descriptor{ + Ok(Descriptor { key: definite_key, descriptor: definite_descriptor, }) @@ -550,8 +558,20 @@ mod tests { ] { let desc = Descriptor::::from_str(&desc_str).unwrap(); assert_eq!(desc.to_string(), desc_str); - assert_eq!(addr_conf, &desc.address(&secp, &elements::AddressParams::LIQUID).unwrap().to_string()); - assert_eq!(addr_unconf, &desc.unconfidential_address(&elements::AddressParams::LIQUID).unwrap().to_string()); + assert_eq!( + addr_conf, + &desc + .address(&secp, &elements::AddressParams::LIQUID) + .unwrap() + .to_string() + ); + assert_eq!( + addr_unconf, + &desc + .unconfidential_address(&elements::AddressParams::LIQUID) + .unwrap() + .to_string() + ); } } @@ -580,10 +600,8 @@ mod tests { let desc: Descriptor = Descriptor { key: Key::Bare(DescriptorPublicKey::from_str(&format!("{}/0h", xpub)).unwrap()), - descriptor: crate::Descriptor::new_wpkh( - DefiniteDescriptorKey::from_str(xpub).unwrap(), - ) - .unwrap(), + descriptor: crate::Descriptor::new_wpkh(DefiniteDescriptorKey::from_str(xpub).unwrap()) + .unwrap(), }; assert_eq!( @@ -628,20 +646,50 @@ mod tests { let desc_bare = Descriptor::::from_str(&desc_bare_str).unwrap(); let definite_desc_view = desc_view.at_derivation_index(index).unwrap(); let definite_desc_bare = desc_bare.at_derivation_index(index).unwrap(); - assert_eq!(definite_desc_view.address(&secp, params).unwrap().to_string(), conf_addr.to_string()); - assert_eq!(definite_desc_bare.address(&secp, params).unwrap().to_string(), conf_addr.to_string()); - assert_eq!(definite_desc_view.unconfidential_address(params).unwrap().to_string(), unconf_addr.to_string()); - assert_eq!(definite_desc_bare.unconfidential_address(params).unwrap().to_string(), unconf_addr.to_string()); + assert_eq!( + definite_desc_view + .address(&secp, params) + .unwrap() + .to_string(), + conf_addr.to_string() + ); + assert_eq!( + definite_desc_bare + .address(&secp, params) + .unwrap() + .to_string(), + conf_addr.to_string() + ); + assert_eq!( + definite_desc_view + .unconfidential_address(params) + .unwrap() + .to_string(), + unconf_addr.to_string() + ); + assert_eq!( + definite_desc_bare + .unconfidential_address(params) + .unwrap() + .to_string(), + unconf_addr.to_string() + ); // It's not possible to get an address if the blinding key has a wildcard, // because the descriptor blinding key is not *definite*, // but we can't enforce this with the Descriptor generic. let desc_view_str = format!("ct({}/*,elwpkh({}))#ls6mx2ac", xprv, xpub); let desc_view = Descriptor::::from_str(&desc_view_str).unwrap(); - assert_eq!(desc_view.address(&secp, params).unwrap_err(), Error::Unexpected("wildcard blinding key".into())); + assert_eq!( + desc_view.address(&secp, params).unwrap_err(), + Error::Unexpected("wildcard blinding key".into()) + ); let desc_bare_str = format!("ct({}/*,elwpkh({}))#czkz0hwn", xpub, xpub); let desc_bare = Descriptor::::from_str(&desc_bare_str).unwrap(); - assert_eq!(desc_bare.address(&secp, params).unwrap_err(), Error::Unexpected("wildcard blinding key".into())); + assert_eq!( + desc_bare.address(&secp, params).unwrap_err(), + Error::Unexpected("wildcard blinding key".into()) + ); } } diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index e2700033..ae81c995 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -1018,9 +1018,7 @@ impl DescriptorXKey { let (compare_fingerprint, compare_path) = match self.origin { Some((fingerprint, ref path)) => ( fingerprint, - path.into_iter() - .chain(&self.derivation_path) - .collect(), + path.into_iter().chain(&self.derivation_path).collect(), ), None => ( self.xkey.xkey_fingerprint(secp), @@ -1039,9 +1037,7 @@ impl DescriptorXKey { }; if &compare_fingerprint == fingerprint - && compare_path - .into_iter() - .eq(&path_excluding_wildcard) + && compare_path.into_iter().eq(&path_excluding_wildcard) { Some(path_excluding_wildcard) } else { diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 36b49889..64601760 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -165,7 +165,9 @@ impl TapTree { ), TapTree::Leaf(ms) => TapTree::Leaf(Arc::new(ms.translate_pk(t)?)), #[cfg(feature = "simplicity")] - TapTree::SimplicityLeaf(sim) => TapTree::SimplicityLeaf(Arc::new(sim.translate(&mut SimTranslator(t))?)) + TapTree::SimplicityLeaf(sim) => { + TapTree::SimplicityLeaf(Arc::new(sim.translate(&mut SimTranslator(t))?)) + } }; Ok(frag) } @@ -296,7 +298,7 @@ impl Tr { TapLeafScript::Miniscript(ms) => ms.sanity_check()?, // TODO: Add sanity check for Simplicity policies #[cfg(feature = "simplicity")] - TapLeafScript::Simplicity(..) => {}, + TapLeafScript::Simplicity(..) => {} } } Ok(()) @@ -446,7 +448,7 @@ pub enum TapLeafScript<'a, Pk: MiniscriptKey, Ext: Extension> { Miniscript(&'a Miniscript), /// Simplicity leaf #[cfg(feature = "simplicity")] - Simplicity(&'a simplicity::Policy) + Simplicity(&'a simplicity::Policy), } impl<'a, Pk: MiniscriptKey, Ext: Extension> TapLeafScript<'a, Pk, Ext> { @@ -509,12 +511,14 @@ impl<'a, Pk: MiniscriptKey, Ext: Extension> TapLeafScript<'a, Pk, Ext> { // We mark the witness size as malleable since it depends on the chosen spending path // TODO: Add method to simplicity::Policy and use it here #[cfg(feature = "simplicity")] - TapLeafScript::Simplicity(..) => Err(Error::AnalysisError(crate::AnalysisError::Malleable)) + TapLeafScript::Simplicity(..) => { + Err(Error::AnalysisError(crate::AnalysisError::Malleable)) + } } } /// Return an iterator over the plain public keys (and not key hash values) of the leaf script. - pub fn iter_pk(&self) -> Box + 'a> { + pub fn iter_pk(&self) -> Box + 'a> { match self { TapLeafScript::Miniscript(ms) => Box::new(ms.iter_pk()), #[cfg(feature = "simplicity")] @@ -529,9 +533,7 @@ impl<'a, Pk: ToPublicKey, Ext: ParseableExt> TapLeafScript<'a, Pk, Ext> { match self { TapLeafScript::Miniscript(ms) => ms.encode(), #[cfg(feature = "simplicity")] - TapLeafScript::Simplicity(sim) => { - Script::from(sim.cmr().as_ref().to_vec()) - } + TapLeafScript::Simplicity(sim) => Script::from(sim.cmr().as_ref().to_vec()), } } @@ -552,7 +554,9 @@ impl<'a, Pk: ToPublicKey, Ext: ParseableExt> TapLeafScript<'a, Pk, Ext> { #[cfg(feature = "simplicity")] TapLeafScript::Simplicity(sim) => { let satisfier = crate::simplicity::SatisfierWrapper::new(satisfier); - let program = sim.satisfy(&satisfier).map_err(|_| Error::CouldNotSatisfy)?; + let program = sim + .satisfy(&satisfier) + .map_err(|_| Error::CouldNotSatisfy)?; let (program_bytes, witness_bytes) = program.encode_to_vec(); Ok(vec![witness_bytes, program_bytes]) } @@ -594,9 +598,7 @@ where self.stack.push((depth + 1, r)); self.stack.push((depth + 1, l)); } - TapTree::Leaf(ref ms) => { - return Some((depth, TapLeafScript::Miniscript(ms))) - }, + TapTree::Leaf(ref ms) => return Some((depth, TapLeafScript::Miniscript(ms))), #[cfg(feature = "simplicity")] TapTree::SimplicityLeaf(ref sim) => { return Some((depth, TapLeafScript::Simplicity(sim))) @@ -790,7 +792,9 @@ impl Liftable for TapTree { } TapTree::Leaf(ref leaf) => leaf.lift(), #[cfg(feature = "simplicity")] - TapTree::SimplicityLeaf(..) => panic!("FIXME: Cannot lift Simplicity policy to Miniscript semantic policy"), + TapTree::SimplicityLeaf(..) => { + panic!("FIXME: Cannot lift Simplicity policy to Miniscript semantic policy") + } } } @@ -816,15 +820,11 @@ impl ForEachKey for Tr { where Pk: 'a, { - let script_keys_res = self - .iter_scripts() - .all(|(_d, script)| { - match script { - TapLeafScript::Miniscript(ms) => ms.for_each_key(&mut pred), - #[cfg(feature = "simplicity")] - TapLeafScript::Simplicity(sim) => crate::simplicity::for_each_key(sim, &mut pred), - } - }); + let script_keys_res = self.iter_scripts().all(|(_d, script)| match script { + TapLeafScript::Miniscript(ms) => ms.for_each_key(&mut pred), + #[cfg(feature = "simplicity")] + TapLeafScript::Simplicity(sim) => crate::simplicity::for_each_key(sim, &mut pred), + }); script_keys_res && pred(&self.internal_key) } } @@ -970,7 +970,11 @@ mod tests { assert!(!tr.for_each_key(|k| k.starts_with("acc"))); } - fn verify_from_str(desc_str: &str, internal_key: &str, scripts: &[TapLeafScript]) { + fn verify_from_str( + desc_str: &str, + internal_key: &str, + scripts: &[TapLeafScript], + ) { let desc = Tr::::from_str(desc_str).unwrap(); assert_eq!(desc_str, &desc.to_string()); assert_eq!(internal_key, &desc.internal_key); @@ -992,8 +996,9 @@ mod tests { // Miniscript key spend let ms = Miniscript::::from_str("pk(a)").unwrap(); verify_from_str( - "eltr(internal,pk(a))#vadmk9gd", "internal", - &[TapLeafScript::Miniscript(&ms)] + "eltr(internal,pk(a))#vadmk9gd", + "internal", + &[TapLeafScript::Miniscript(&ms)], ); #[cfg(feature = "simplicity")] @@ -1001,14 +1006,19 @@ mod tests { // Simplicity key spend let sim = simplicity::Policy::Key("a".to_string()); verify_from_str( - "eltr(internal,sim{pk(a)})#duhmnzmm", "internal", - &[TapLeafScript::Simplicity(&sim)] + "eltr(internal,sim{pk(a)})#duhmnzmm", + "internal", + &[TapLeafScript::Simplicity(&sim)], ); // Mixed Miniscript and Simplicity verify_from_str( - "eltr(internal,{pk(a),sim{pk(a)}})#7vmfhpaj", "internal", - &[TapLeafScript::Miniscript(&ms), TapLeafScript::Simplicity(&sim)] + "eltr(internal,{pk(a),sim{pk(a)}})#7vmfhpaj", + "internal", + &[ + TapLeafScript::Miniscript(&ms), + TapLeafScript::Simplicity(&sim), + ], ); } } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index c186f3a7..a1170a82 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -701,7 +701,10 @@ where Terminal::After(ref n) => { debug_assert_eq!(node_state.n_evaluated, 0); debug_assert_eq!(node_state.n_satisfied, 0); - return Some(self.stack.evaluate_after(LockTime::from(*n), self.lock_time)); + return Some( + self.stack + .evaluate_after(LockTime::from(*n), self.lock_time), + ); } Terminal::Older(ref n) => { debug_assert_eq!(node_state.n_evaluated, 0); diff --git a/src/miniscript/context.rs b/src/miniscript/context.rs index 705e10c7..a518d0df 100644 --- a/src/miniscript/context.rs +++ b/src/miniscript/context.rs @@ -756,12 +756,9 @@ impl ScriptContext for BareCtx { ))); } match ms.node { - Terminal::PkK(ref key) if key.is_x_only_key() => { - Err(ScriptContextError::XOnlyKeysNotAllowed( - key.to_string(), - Self::name_str(), - )) - } + Terminal::PkK(ref key) if key.is_x_only_key() => Err( + ScriptContextError::XOnlyKeysNotAllowed(key.to_string(), Self::name_str()), + ), Terminal::Multi(_k, ref pks) => { if pks.len() > MAX_PUBKEYS_PER_MULTISIG { return Err(ScriptContextError::CheckMultiSigLimitExceeded); diff --git a/src/miniscript/types/extra_props.rs b/src/miniscript/types/extra_props.rs index 9806b7f7..ed3cf767 100644 --- a/src/miniscript/types/extra_props.rs +++ b/src/miniscript/types/extra_props.rs @@ -857,17 +857,16 @@ impl Property for ExtData { }); ops_count_sat_vec.sort_by(sat_minus_dissat); - let op_count_sat = - ops_count_sat_vec - .iter() - .enumerate() - .try_fold(0, |acc, (i, &(x, y))| { - if i <= k { - x.map(|x| x + acc) - } else { - Some(y + acc) - } - }); + let op_count_sat = ops_count_sat_vec + .iter() + .enumerate() + .try_fold(0, |acc, (i, &(x, y))| { + if i <= k { + x.map(|x| x + acc) + } else { + Some(y + acc) + } + }); Ok(ExtData { pk_cost: pk_cost + n - 1, //all pk cost + (n-1)*ADD diff --git a/src/policy/compiler.rs b/src/policy/compiler.rs index 0704d823..58714b30 100644 --- a/src/policy/compiler.rs +++ b/src/policy/compiler.rs @@ -19,7 +19,7 @@ use elements::{LockTime, Sequence}; use crate::miniscript::context::SigType; use crate::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG; -use crate::miniscript::types::{self, ErrorKind, ExtData, Property, Type, Error}; +use crate::miniscript::types::{self, Error, ErrorKind, ExtData, Property, Type}; use crate::miniscript::ScriptContext; use crate::policy::Concrete; use crate::{policy, Extension, Miniscript, MiniscriptKey, Terminal}; @@ -850,12 +850,10 @@ fn insert_elem( // Check whether the new element is worse than any existing element. If there // is an element which is a subtype of the current element and has better // cost, don't consider this element. - let is_worse = map - .iter() - .any(|(existing_key, existing_elem)| { - let existing_elem_cost = existing_elem.cost_1d(sat_prob, dissat_prob); - existing_key.is_subtype(elem_key) && existing_elem_cost <= elem_cost - }); + let is_worse = map.iter().any(|(existing_key, existing_elem)| { + let existing_elem_cost = existing_elem.cost_1d(sat_prob, dissat_prob); + existing_key.is_subtype(elem_key) && existing_elem_cost <= elem_cost + }); if !is_worse { // If the element is not worse any element in the map, remove elements // whose subtype is the current element and have worse cost. diff --git a/src/simplicity.rs b/src/simplicity.rs index 8068f3e8..068fc385 100644 --- a/src/simplicity.rs +++ b/src/simplicity.rs @@ -5,9 +5,9 @@ use std::str::FromStr; use std::sync::Arc; use bitcoin_miniscript::ToPublicKey; -use elements::{LockTime, SchnorrSig, Sequence}; use elements::taproot::TapLeafHash; -use simplicity::{Policy, FailEntropy, Preimage32}; +use elements::{LockTime, SchnorrSig, Sequence}; +use simplicity::{FailEntropy, Policy, Preimage32}; use crate::policy::concrete::PolicyError; use crate::{expression, Error, MiniscriptKey}; @@ -98,8 +98,10 @@ impl_from_str!( // We cannot use our wrapper because we don't own the Policy (we have a reference) // Implementing a wrapper of Cow<'a, Policy> leads to lifetime issues // when implementing ForEachKey, because for_each_key() has its own lifetime 'a -pub fn for_each_key<'a, Pk: MiniscriptKey + 'a, F: FnMut(&'a Pk) -> bool>(policy: &'a Policy, mut pred: F) -> bool -{ +pub fn for_each_key<'a, Pk: MiniscriptKey + 'a, F: FnMut(&'a Pk) -> bool>( + policy: &'a Policy, + mut pred: F, +) -> bool { let mut stack = vec![policy]; while let Some(top) = stack.pop() { @@ -136,7 +138,9 @@ impl> SatisfierWrapper { } } -impl> simplicity::Satisfier for SatisfierWrapper { +impl> simplicity::Satisfier + for SatisfierWrapper +{ fn lookup_tap_leaf_script_sig(&self, pk: &Pk, hash: &TapLeafHash) -> Option { self.0.lookup_tap_leaf_script_sig(pk, hash) } @@ -156,9 +160,9 @@ impl> simplicity::Satisfier for Sat #[cfg(test)] mod tests { - use secp256k1::XOnlyPublicKey; - use crate::DescriptorPublicKey; use super::*; + use crate::DescriptorPublicKey; + use secp256k1::XOnlyPublicKey; #[test] fn parse_bad_thresh() { @@ -202,7 +206,9 @@ mod tests { 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 = PolicyWrapper::::from_str(&s).expect("decode policy").0; + let decoded_policy = PolicyWrapper::::from_str(&s) + .expect("decode policy") + .0; if let Policy::Key(key) = decoded_policy { assert_eq!(decoded_key, key); From 6c8d4fb2d455ac2151c9e6ef2f560a131b776508 Mon Sep 17 00:00:00 2001 From: Kyryl R Date: Sun, 23 Aug 2026 12:39:20 +0300 Subject: [PATCH 2/5] general: bump rust-simplicity, fail on simplicity satisfaction explicitly --- Cargo-latest.lock | 27 ++++++++++++--------------- Cargo.toml | 2 +- src/descriptor/tr.rs | 26 ++++++++++++++++++-------- src/simplicity.rs | 39 +-------------------------------------- 4 files changed, 32 insertions(+), 62 deletions(-) diff --git a/Cargo-latest.lock b/Cargo-latest.lock index e64fbe7a..30c7090b 100644 --- a/Cargo-latest.lock +++ b/Cargo-latest.lock @@ -316,6 +316,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghost-cell" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8449d342b1c67f49169e92e71deb7b9b27f30062301a16dbc27a4cc8d2351b7" + [[package]] name = "hex-conservative" version = "0.1.2" @@ -560,15 +566,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" -[[package]] -name = "santiago" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de36022292bc2086eb8f55bffa460fef3475e4459b478820711f4c421feb87ec" -dependencies = [ - "regex", -] - [[package]] name = "secp256k1" version = "0.28.2" @@ -689,26 +686,26 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simplicity-lang" -version = "0.3.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75c8fb4a18e63fbce4cf16026c36a6c38066e4f4a09ce5e81be817d0e36d8f8" +checksum = "13ed081e3046d66c146d7201bcbf3b655ca3436cb83f6efc26d7895bd2b79d06" dependencies = [ "bitcoin 0.32.7", "bitcoin_hashes 0.14.0", "byteorder", "elements", "getrandom", - "hex-conservative 0.1.2", + "ghost-cell", + "hex-conservative 0.2.1", "miniscript", - "santiago", "simplicity-sys", ] [[package]] name = "simplicity-sys" -version = "0.3.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd2cc5d458a8032d328ea85e824f54f61664ab84c3d42b3b7f8804fb9b81572" +checksum = "96d1ec5477c7650b8ef511aa56dccb28f2e8cdb6e87f260ecffdaf0ebfef2d3b" dependencies = [ "bitcoin_hashes 0.14.0", "cc", diff --git a/Cargo.toml b/Cargo.toml index 7397d880..34a47229 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ base64 = ["bitcoin/base64", "elements/base64"] bitcoin = "0.32.0" elements = "0.25.0" bitcoin-miniscript = { package = "miniscript", version = "12.0" } -simplicity = { package = "simplicity-lang", version = "0.3.0", optional = true } +simplicity = { package = "simplicity-lang", version = "=0.8.0", optional = true } # Do NOT use this as a feature! Use the `serde` feature instead. actual-serde = { package = "serde", version = "1.0", optional = true } diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 64601760..6e265e1c 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -552,14 +552,7 @@ impl<'a, Pk: ToPublicKey, Ext: ParseableExt> TapLeafScript<'a, Pk, Ext> { match self { TapLeafScript::Miniscript(ms) => ms.satisfy(satisfier), #[cfg(feature = "simplicity")] - TapLeafScript::Simplicity(sim) => { - let satisfier = crate::simplicity::SatisfierWrapper::new(satisfier); - let program = sim - .satisfy(&satisfier) - .map_err(|_| Error::CouldNotSatisfy)?; - let (program_bytes, witness_bytes) = program.encode_to_vec(); - Ok(vec![witness_bytes, program_bytes]) - } + TapLeafScript::Simplicity(..) => Err(Error::CouldNotSatisfy), } } } @@ -950,6 +943,9 @@ mod tests { use super::*; use crate::{ForEachKey, NoExt}; + #[cfg(feature = "simplicity")] + const INTERNAL_KEY: &str = "020000000000000000000000000000000000000000000000000000000000000001"; + #[test] fn test_for_each() { let desc = "eltr(acc0, { @@ -1022,4 +1018,18 @@ mod tests { ); } } + + #[test] + #[cfg(feature = "simplicity")] + fn simplicity_satisfaction_fails_closed_without_env() { + let descriptor = Tr::::from_str(&format!( + "eltr({},sim{{TRIVIAL}})", + INTERNAL_KEY + )) + .expect("valid concrete Taproot descriptor"); + let (_, leaf) = descriptor.iter_scripts().next().unwrap(); + + assert_eq!(leaf.satisfy(()), Err(Error::CouldNotSatisfy)); + assert_eq!(descriptor.get_satisfaction(()), Err(Error::CouldNotSatisfy)); + } } diff --git a/src/simplicity.rs b/src/simplicity.rs index 068fc385..f0a3ce08 100644 --- a/src/simplicity.rs +++ b/src/simplicity.rs @@ -1,13 +1,9 @@ // SPDX-License-Identifier: CC0-1.0 use std::fmt; -use std::marker::PhantomData; use std::str::FromStr; use std::sync::Arc; -use bitcoin_miniscript::ToPublicKey; -use elements::taproot::TapLeafHash; -use elements::{LockTime, SchnorrSig, Sequence}; -use simplicity::{FailEntropy, Policy, Preimage32}; +use simplicity::{FailEntropy, Policy}; use crate::policy::concrete::PolicyError; use crate::{expression, Error, MiniscriptKey}; @@ -125,39 +121,6 @@ pub fn for_each_key<'a, Pk: MiniscriptKey + 'a, F: FnMut(&'a Pk) -> bool>( true } -// We could make crate::Satisfier a subtrait of simplicity::Satisfier, -// but then we would have to implement simplicity::Satisfier for all the blanket implementations -// of crate::Satisfier, such as HashMap, which is annoying -// We might choose to do so in the future, but for now a crate-local wrapper is easier -// This wrapper is internally used by `Tr` and is never encountered by users -pub(crate) struct SatisfierWrapper>(S, PhantomData); - -impl> SatisfierWrapper { - pub fn new(satisfier: S) -> Self { - Self(satisfier, PhantomData) - } -} - -impl> simplicity::Satisfier - for SatisfierWrapper -{ - fn lookup_tap_leaf_script_sig(&self, pk: &Pk, hash: &TapLeafHash) -> Option { - self.0.lookup_tap_leaf_script_sig(pk, hash) - } - - fn lookup_sha256(&self, hash: &Pk::Sha256) -> Option { - self.0.lookup_sha256(hash) - } - - fn check_older(&self, sequence: Sequence) -> bool { - self.0.check_older(sequence) - } - - fn check_after(&self, locktime: LockTime) -> bool { - self.0.check_after(locktime) - } -} - #[cfg(test)] mod tests { use super::*; From ca011ab2be3a921213b6df8fd18a19573387f8ee Mon Sep 17 00:00:00 2001 From: Kyryl R Date: Sun, 23 Aug 2026 12:52:08 +0300 Subject: [PATCH 3/5] simplicity: support simplicity CMR descriptors --- src/descriptor/tr.rs | 36 ++++++++++++++++++++++++++++++++++++ src/simplicity.rs | 9 ++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 6e265e1c..a6813bca 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -940,6 +940,9 @@ where #[cfg(test)] mod tests { + #[cfg(feature = "simplicity")] + use elements::hex::ToHex; + use super::*; use crate::{ForEachKey, NoExt}; @@ -1032,4 +1035,37 @@ mod tests { assert_eq!(leaf.satisfy(()), Err(Error::CouldNotSatisfy)); assert_eq!(descriptor.get_satisfaction(()), Err(Error::CouldNotSatisfy)); } + + #[test] + #[cfg(feature = "simplicity")] + fn raw_simplicity_cmr_roundtrips() { + let cmr = "11".repeat(32); + let raw = format!("eltr({},sim{{asm({})}})", INTERNAL_KEY, cmr); + let descriptor = Tr::::from_str(&raw) + .expect("valid raw-CMR Taproot descriptor"); + let rendered = descriptor.to_string(); + + assert_eq!(checksum::desc_checksum(&raw).unwrap(), "g7a9hfgp"); + assert_eq!(rendered, format!("{}#g7a9hfgp", raw)); + assert_eq!( + Tr::::from_str(&rendered).unwrap(), + descriptor + ); + + let leaves: Vec<_> = descriptor.iter_scripts().collect(); + assert_eq!(leaves.len(), 1); + assert_eq!(leaves[0].0, 0); + assert_eq!(leaves[0].1.version().as_u8(), 0xbe); + assert_eq!(leaves[0].1.encode().as_bytes(), &[0x11; 32]); + assert_eq!( + descriptor.script_pubkey().to_hex(), + "512063e475b284ff60b191cde79553676712ee0e67ddf15bed86d576434218d47185" + ); + assert_eq!( + descriptor + .address(None, &elements::AddressParams::LIQUID_TESTNET) + .to_string(), + "tex1pv0j8tv5ylastrywdu724xem8zthque7a79d7mpk4wep5yxx5wxzs4xp4za" + ); + } } diff --git a/src/simplicity.rs b/src/simplicity.rs index f0a3ce08..e3d83051 100644 --- a/src/simplicity.rs +++ b/src/simplicity.rs @@ -3,7 +3,7 @@ use std::fmt; use std::str::FromStr; use std::sync::Arc; -use simplicity::{FailEntropy, Policy}; +use simplicity::{Cmr, FailEntropy, Policy}; use crate::policy::concrete::PolicyError; use crate::{expression, Error, MiniscriptKey}; @@ -24,6 +24,13 @@ impl_from_tree!( ("sha256", 1) => expression::terminal(&top.args[0], |x| { Pk::Sha256::from_str(x).map(Policy::Sha256) }), + // Following rust-simplicity's `Policy::Assembly` terminology + // https://github.com/BlockstreamResearch/rust-simplicity/blob/a8896cf93ed683d8ae1430371ad7d196ab061fa5/src/policy/ast.rs#L54 + ("asm", 1) => expression::terminal(&top.args[0], |cmr| { + Cmr::from_str(cmr) + .map(Policy::Assembly) + .map_err(|e| Error::Unexpected(e.to_string())) + }), ("and", _) => { if top.args.len() != 2 { return Err(Error::PolicyError(PolicyError::NonBinaryArgAnd)); From 071d884df8d8699474f4467085923fcd86498ee9 Mon Sep 17 00:00:00 2001 From: Kyryl R Date: Sun, 23 Aug 2026 13:07:37 +0300 Subject: [PATCH 4/5] simplicity: ensure only simplicity CMR descriptors are parsed --- src/descriptor/tr.rs | 76 ++++++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index a6813bca..63f39174 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -124,6 +124,26 @@ impl TapTree { } } + #[cfg(feature = "simplicity")] + fn validate_simplicity(&self) -> Result<(), Error> { + match self { + TapTree::Tree(left, right) => { + left.validate_simplicity()?; + right.validate_simplicity() + } + TapTree::Leaf(..) => Ok(()), + TapTree::SimplicityLeaf(policy) + if matches!(policy.as_ref(), simplicity::Policy::Assembly(..)) => + { + Ok(()) + } + TapTree::SimplicityLeaf(..) => Err(Error::BadDescriptor( + "core Simplicity descriptors support only sim{asm(CMR)} leaves; compile policies separately" + .to_owned(), + )), + } + } + /// Iterates over all miniscripts in DFS walk order compatible with the /// PSBT requirements (BIP 371). pub fn iter(&self) -> TapTreeIter<'_, Pk, Ext> { @@ -217,6 +237,11 @@ impl fmt::Debug for TapTree { impl Tr { /// Create a new [`Tr`] descriptor from internal key and [`TapTree`] pub fn new(internal_key: Pk, tree: Option>) -> Result { + #[cfg(feature = "simplicity")] + if let Some(tree) = tree.as_ref() { + tree.validate_simplicity()?; + } + let nodes = tree.as_ref().map(|t| t.taptree_height()).unwrap_or(0); if nodes <= TAPROOT_CONTROL_MAX_NODE_COUNT { @@ -612,7 +637,14 @@ impl_block_str!( #[cfg(feature = "simplicity")] expression::Tree { name, args } if *name == "sim" && args.len() == 1 => { let policy = crate::simplicity::PolicyWrapper::::from_str(args[0].name)?; - Ok(TapTree::SimplicityLeaf(Arc::new(policy.0))) + if matches!(&policy.0, simplicity::Policy::Assembly(..)) { + Ok(TapTree::SimplicityLeaf(Arc::new(policy.0))) + } else { + Err(Error::BadDescriptor( + "core Simplicity descriptors support only sim{asm(CMR)} leaves; compile policies separately" + .to_owned(), + )) + } } expression::Tree { name, args } if !name.is_empty() && args.is_empty() => { let script = Miniscript::::from_str(name)?; @@ -948,6 +980,8 @@ mod tests { #[cfg(feature = "simplicity")] const INTERNAL_KEY: &str = "020000000000000000000000000000000000000000000000000000000000000001"; + #[cfg(feature = "simplicity")] + const LEAF_KEY: &str = "020000000000000000000000000000000000000000000000000000000000000002"; #[test] fn test_for_each() { @@ -999,35 +1033,15 @@ mod tests { "internal", &[TapLeafScript::Miniscript(&ms)], ); - - #[cfg(feature = "simplicity")] - { - // Simplicity key spend - let sim = simplicity::Policy::Key("a".to_string()); - verify_from_str( - "eltr(internal,sim{pk(a)})#duhmnzmm", - "internal", - &[TapLeafScript::Simplicity(&sim)], - ); - - // Mixed Miniscript and Simplicity - verify_from_str( - "eltr(internal,{pk(a),sim{pk(a)}})#7vmfhpaj", - "internal", - &[ - TapLeafScript::Miniscript(&ms), - TapLeafScript::Simplicity(&sim), - ], - ); - } } #[test] #[cfg(feature = "simplicity")] fn simplicity_satisfaction_fails_closed_without_env() { let descriptor = Tr::::from_str(&format!( - "eltr({},sim{{TRIVIAL}})", - INTERNAL_KEY + "eltr({},sim{{asm({})}})", + INTERNAL_KEY, + "11".repeat(32) )) .expect("valid concrete Taproot descriptor"); let (_, leaf) = descriptor.iter_scripts().next().unwrap(); @@ -1068,4 +1082,18 @@ mod tests { "tex1pv0j8tv5ylastrywdu724xem8zthque7a79d7mpk4wep5yxx5wxzs4xp4za" ); } + + #[test] + #[cfg(feature = "simplicity")] + fn ensure_only_simplicity_cmr_desc_supported() { + let policy = format!("eltr({},sim{{pk({})}})", INTERNAL_KEY, LEAF_KEY); + let error = Tr::::from_str(&policy) + .expect_err("compiled policy syntax is not a core descriptor"); + assert!(error.to_string().contains("only sim{asm(CMR)}")); + + let programmatic = TapTree::SimplicityLeaf(Arc::new(simplicity::Policy::Trivial)); + let error = Tr::::new("internal".to_owned(), Some(programmatic)) + .expect_err("programmatic policies must follow the same CMR-only boundary"); + assert!(error.to_string().contains("only sim{asm(CMR)}")); + } } From 2cfd0008c9e834ab2412d64298bd4e6d7bfa0c27 Mon Sep 17 00:00:00 2001 From: Kyryl R Date: Sun, 23 Aug 2026 13:19:18 +0300 Subject: [PATCH 5/5] general: update CHANGELOG.md --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f30d1b6..091fb1e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# Unreleased + +- Upgrade `rust-simplicity` to 0.8.0 and align the minimum supported Rust version [#104](https://github.com/ElementsProject/elements-miniscript/pull/104). +- Support CMR-only Simplicity leaves in Taproot descriptors using `sim{asm(CMR)}` [#104](https://github.com/ElementsProject/elements-miniscript/pull/104). + # 0.4.0 - Oct 8, 2024 - Use rust-bitcoin 0.32.0 and rust-elements 0.25.0 [#90](https://github.com/ElementsProject/elements-miniscript/pull/90) @@ -22,4 +27,3 @@ # 0.2.0 - June 15, 2023 - Still rapid iteration, very unstable. -