diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index b172aa69e..f6f32beff 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -27,8 +27,8 @@ use crate::miniscript::{satisfy, Legacy, Miniscript, Segwitv0}; use crate::plan::{AssetProvider, Plan}; use crate::prelude::*; use crate::{ - expression, hash256, BareCtx, Error, ForEachKey, FromStrKey, MiniscriptKey, Satisfier, - ToPublicKey, TranslateErr, Translator, + expression, hash256, AnalysisError, BareCtx, Error, ExtParams, ForEachKey, FromStrKey, + MiniscriptKey, Satisfier, ToPublicKey, TranslateErr, Translator, }; mod bare; @@ -318,6 +318,36 @@ impl Descriptor { } } + /// Helper function for Wsh descriptor + fn ext_check_wsh(wsh: &Wsh, params: &ExtParams) -> Result<(), AnalysisError> { + match wsh.as_inner() { + WshInner::SortedMulti(_) => Ok(()), + WshInner::Ms(ref ms) => ms.ext_check(params), + } + } + + /// Helper function for Sh descriptor + fn ext_check_sh(sh: &Sh, params: &ExtParams) -> Result<(), AnalysisError> { + match sh.as_inner() { + ShInner::Wsh(ref wsh) => Self::ext_check_wsh(wsh, params), + ShInner::Wpkh(_) => Ok(()), + ShInner::SortedMulti(_) => Ok(()), + ShInner::Ms(ref ms) => ms.ext_check(params), + } + } + + /// Check whether the descriptor is safe under the given extra parameters + pub fn ext_check(&self, params: &ExtParams) -> Result<(), AnalysisError> { + match *self { + Descriptor::Bare(ref bare) => bare.as_inner().ext_check(params), + Descriptor::Pkh(_) => Ok(()), + Descriptor::Wpkh(_) => Ok(()), + Descriptor::Wsh(ref wsh) => Self::ext_check_wsh(wsh, params), + Descriptor::Sh(ref sh) => Self::ext_check_sh(sh, params), + Descriptor::Tr(ref tr) => tr.ext_check(params), + } + } + /// Computes an upper bound on the difference between a non-satisfied /// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight` /// @@ -1117,7 +1147,7 @@ mod tests { StdDescriptor::from_str(TEST_PK).unwrap(); let uncompressed_pk = - "0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf"; + "0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf"; // Context tests StdDescriptor::from_str(&format!("pk({})", uncompressed_pk)).unwrap(); @@ -1302,6 +1332,29 @@ mod tests { ); } + #[test] + fn ext_check() { + /// Make sure that default ext_check() catches OP_DROP but + /// ext_check() with OP_DROP explicitly allowed does not. + fn assert_opdrop_error(desc: &str) { + let desc = Descriptor::::from_str(desc).unwrap(); + assert_eq!( + desc.ext_check(&ExtParams::default()).unwrap_err(), + AnalysisError::ContainsDrop + ); + assert_eq!(desc.ext_check(&ExtParams::default().drop()), Ok(())); + } + + let secp = secp256k1::Secp256k1::new(); + let sk = + secp256k1::SecretKey::from_slice(&b"sally was a secret key, she said"[..]).unwrap(); + let pk = bitcoin::PublicKey::new(secp256k1::PublicKey::from_secret_key(&secp, &sk)); + let inner = format!("and_v(r:after(1),c:pk_k({}))", pk); + assert_opdrop_error(format!("sh({})", inner).as_str()); + assert_opdrop_error(format!("wsh({})", inner).as_str()); + assert_opdrop_error(format!("sh(wsh({}))", inner).as_str()); + } + #[test] fn satisfy() { let secp = secp256k1::Secp256k1::new(); @@ -1648,7 +1701,7 @@ mod tests { let descriptor = Descriptor::::from_str( "wsh(multi(2,03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd,03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626))", ) - .unwrap(); + .unwrap(); assert_eq!( *descriptor .script_code().unwrap() @@ -1679,7 +1732,7 @@ mod tests { bip32::ChildNumber::from_hardened_idx(0).unwrap(), bip32::ChildNumber::from_hardened_idx(0).unwrap(), ][..]) - .into(), + .into(), )), xkey: bip32::Xpub::from_str("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL").unwrap(), derivation_path: (&[bip32::ChildNumber::from_normal_idx(1).unwrap()][..]).into(), @@ -1741,7 +1794,7 @@ mod tests { key: SinglePubKey::FullKey(bitcoin::PublicKey::from_str( "04f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446a", ) - .unwrap()), + .unwrap()), origin: None, }); assert_eq!(expected, key.parse().unwrap()); diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 30d6c5c74..d64a76a7e 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -23,8 +23,8 @@ use crate::policy::Liftable; use crate::prelude::*; use crate::util::{varint_len, witness_size}; use crate::{ - errstr, Error, ForEachKey, FromStrKey, MiniscriptKey, Satisfier, ScriptContext, Tap, Threshold, - ToPublicKey, TranslateErr, Translator, + errstr, AnalysisError, Error, ExtParams, ForEachKey, FromStrKey, MiniscriptKey, Satisfier, + ScriptContext, Tap, Threshold, ToPublicKey, TranslateErr, Translator, }; /// A Taproot Tree representation. @@ -252,6 +252,14 @@ impl Tr { Ok(()) } + /// Check whether the descriptor follows certain extra constraints. + pub fn ext_check(&self, params: &ExtParams) -> Result<(), AnalysisError> { + for (_depth, ms) in self.iter_scripts() { + ms.ext_check(params)?; + } + Ok(()) + } + /// Computes an upper bound on the difference between a non-satisfied /// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight` /// diff --git a/src/miniscript/analyzable.rs b/src/miniscript/analyzable.rs index 39a40a6d7..4e4a681d8 100644 --- a/src/miniscript/analyzable.rs +++ b/src/miniscript/analyzable.rs @@ -124,6 +124,7 @@ impl ExtParams { self } + /// Builder that allows miniscripts with drop fragments. pub fn drop(mut self) -> ExtParams { self.drop = true; self @@ -227,6 +228,7 @@ impl Miniscript { self.iter().any(|ms| matches!(ms.node, Terminal::RawPkH(_))) } + /// Whether the given miniscript contains a drop fragment pub fn contains_drop(&self) -> bool { self.iter().any(|ms| matches!(ms.node, Terminal::Drop(_))) } diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index f35e92fc8..0542e9492 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -1499,7 +1499,7 @@ mod tests { fn drop_wrapper() { type SwMs = Miniscript; fn assert_error(s: &str, expected_error: Option<&str>) { - match SwMs::from_str_insane(&s) { + match SwMs::from_str_insane(s) { Ok(_) => match expected_error { Some(e) => { panic!("Expected error: {}", e); diff --git a/src/miniscript/ms_tests.rs b/src/miniscript/ms_tests.rs index 6d1a30c29..af0e90710 100644 --- a/src/miniscript/ms_tests.rs +++ b/src/miniscript/ms_tests.rs @@ -23861,7 +23861,5 @@ mod tests { #[test] pub fn test_opdrop() { Miniscript::::from_str("and_v(v:after(100000),multi(1,A,B))").unwrap(); - let ms: Miniscript = - Miniscript::from_str("and_v(v:after(100000),multi(1,A,B))").unwrap(); } } diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index fad0a5029..443aec5ec 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -1727,10 +1727,8 @@ mod tests { fn test_finalize_with_opdrop() { let secp = Secp256k1::new(); let xpriv = bip32::Xpriv::new_master(bitcoin::Network::Testnet, &[42]).expect("master key"); - let desc = format!( - "wsh(and_v(r:after(1024),pk({})))", - xpriv.to_keypair(&secp).public_key().to_string() - ); + let desc = + format!("wsh(and_v(r:after(1024),pk({})))", xpriv.to_keypair(&secp).public_key()); // let desc = format!("wsh(pk({}))", xpriv.to_keypair(&secp).public_key().to_string()); let desc = Descriptor::::from_str(&desc).unwrap(); test_sign_input_with_descriptor(desc, xpriv);