diff --git a/src/descriptor/dsl.rs b/src/descriptor/dsl.rs index 518b8750..988b9a8d 100644 --- a/src/descriptor/dsl.rs +++ b/src/descriptor/dsl.rs @@ -765,8 +765,10 @@ macro_rules! fragment { (keys_acc, net_acc) }); - let thresh = $crate::miniscript::Threshold::new($thresh, items).expect("valid threshold and pks collection"); - $crate::impl_leaf_opcode_value!(Thresh, thresh) + $crate::miniscript::Threshold::new($thresh, items) + .map_err($crate::miniscript::Error::Threshold) + .map_err($crate::descriptor::DescriptorError::Miniscript) + .and_then(|thresh| $crate::impl_leaf_opcode_value!(Thresh, thresh)) .map(|(minisc, _, _)| (minisc, key_maps, valid_network_kinds)) }); ( thresh ( $thresh:expr, $( $inner:tt )* ) ) => ({ @@ -779,8 +781,10 @@ macro_rules! fragment { let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); let fun = |k, pks| { - let thresh = $crate::miniscript::Threshold::new(k, pks).expect("valid threshold and pks collection"); - $crate::miniscript::Terminal::Multi(thresh) + $crate::miniscript::Threshold::new(k, pks) + .map($crate::miniscript::Terminal::Multi) + .map_err($crate::miniscript::Error::Threshold) + .map_err($crate::descriptor::DescriptorError::Miniscript) }; $crate::keys::make_multi($thresh, fun, $keys, &secp) @@ -793,8 +797,10 @@ macro_rules! fragment { let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); let fun = |k, pks| { - let thresh = $crate::miniscript::Threshold::new(k, pks).expect("valid threshold and pks collection"); - $crate::miniscript::Terminal::MultiA(thresh) + $crate::miniscript::Threshold::new(k, pks) + .map($crate::miniscript::Terminal::MultiA) + .map_err($crate::miniscript::Error::Threshold) + .map_err($crate::descriptor::DescriptorError::Miniscript) }; $crate::keys::make_multi($thresh, fun, $keys, &secp) @@ -1117,6 +1123,71 @@ mod test { ); } + #[test] + fn test_dsl_multi_invalid_threshold_returns_error_not_panic() { + let key_1 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + let path_1 = bip32::DerivationPath::from_str("m/0").unwrap(); + let key_2 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF").unwrap(); + let path_2 = bip32::DerivationPath::from_str("m/1").unwrap(); + let desc_key1 = (key_1, path_1).into_descriptor_key().unwrap(); + let desc_key2 = (key_2, path_2).into_descriptor_key().unwrap(); + + let result = descriptor!(wsh(multi(3, desc_key1, desc_key2))); + + assert!( + result.is_err(), + "invalid threshold (k > n) should return Err, not panic" + ); + assert!(matches!( + result, + Err(DescriptorError::Miniscript(miniscript::Error::Threshold(_))) + )); + } + + #[test] + fn test_dsl_thresh_invalid_threshold_returns_error_not_panic() { + let key_1 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + let path_1 = bip32::DerivationPath::from_str("m/0").unwrap(); + let key_2 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF").unwrap(); + let path_2 = bip32::DerivationPath::from_str("m/1").unwrap(); + let desc_key1 = (key_1, path_1).into_descriptor_key().unwrap(); + let desc_key2 = (key_2, path_2).into_descriptor_key().unwrap(); + + let result = descriptor!(wsh(thresh(3, pk(desc_key1), pk(desc_key2)))); + + assert!( + result.is_err(), + "invalid threshold (k > n) should return Err, not panic" + ); + assert!(matches!( + result, + Err(DescriptorError::Miniscript(miniscript::Error::Threshold(_))) + )); + } + + #[test] + fn test_dsl_multi_a_invalid_threshold_returns_error_not_panic() { + let internal_key = + PrivateKey::from_wif("cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR").unwrap(); + let key_1 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + let path_1 = bip32::DerivationPath::from_str("m/0").unwrap(); + let key_2 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF").unwrap(); + let path_2 = bip32::DerivationPath::from_str("m/1").unwrap(); + let desc_key1 = (key_1, path_1).into_descriptor_key().unwrap(); + let desc_key2 = (key_2, path_2).into_descriptor_key().unwrap(); + + let result = descriptor!(tr(internal_key, multi_a(3, desc_key1, desc_key2))); + + assert!( + result.is_err(), + "invalid threshold (k > n) should return Err, not panic" + ); + assert!(matches!( + result, + Err(DescriptorError::Miniscript(miniscript::Error::Threshold(_))) + )); + } + // Verify that the `valid_network_kinds` returned is correctly computed based on the keys // present in the descriptor. #[test] diff --git a/src/keys/mod.rs b/src/keys/mod.rs index ef44e87f..2fedbafa 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -864,7 +864,10 @@ pub fn make_pkh, Ctx: ScriptContext>( pub fn make_multi< Pk: IntoDescriptorKey, Ctx: ScriptContext, - V: Fn(usize, Vec) -> Terminal, + V: Fn( + usize, + Vec, + ) -> Result, DescriptorError>, >( thresh: usize, variant: V, @@ -879,7 +882,7 @@ pub fn make_multi< DescriptorError, > { let (pks, key_map, valid_network_kinds) = expand_multi_keys(pks, secp)?; - let minisc = Miniscript::from_ast(variant(thresh, pks))?; + let minisc = Miniscript::from_ast(variant(thresh, pks)?)?; minisc.check_miniscript()?;