Skip to content

Commit 04956a4

Browse files
feat(descriptor): add ext_check to validate descriptors with extra params
Adds check for extended descriptor parameters. Available in `ext_check()` where OP_DROP and other features can be opted in. Issue: opdrop.add_ext_check
1 parent eb98485 commit 04956a4

File tree

2 files changed

+69
-8
lines changed

2 files changed

+69
-8
lines changed

src/descriptor/mod.rs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ use crate::miniscript::{satisfy, Legacy, Miniscript, Segwitv0};
2727
use crate::plan::{AssetProvider, Plan};
2828
use crate::prelude::*;
2929
use crate::{
30-
expression, hash256, BareCtx, Error, ForEachKey, FromStrKey, MiniscriptKey, Satisfier,
31-
ToPublicKey, TranslateErr, Translator,
30+
expression, hash256, AnalysisError, BareCtx, Error, ExtParams, ForEachKey, FromStrKey,
31+
MiniscriptKey, Satisfier, ToPublicKey, TranslateErr, Translator,
3232
};
3333

3434
mod bare;
@@ -318,6 +318,36 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {
318318
}
319319
}
320320

321+
/// Helper function for Wsh descriptor
322+
fn ext_check_wsh(wsh: &Wsh<Pk>, params: &ExtParams) -> Result<(), AnalysisError> {
323+
match wsh.as_inner() {
324+
WshInner::SortedMulti(ref smv) => Ok(()),
325+
WshInner::Ms(ref ms) => ms.ext_check(params),
326+
}
327+
}
328+
329+
/// Helper function for Sh descriptor
330+
fn ext_check_sh(sh: &Sh<Pk>, params: &ExtParams) -> Result<(), AnalysisError> {
331+
match sh.as_inner() {
332+
ShInner::Wsh(ref wsh) => Self::ext_check_wsh(&wsh, params),
333+
ShInner::Wpkh(_) => Ok(()),
334+
ShInner::SortedMulti(ref smv) => Ok(()),
335+
ShInner::Ms(ref ms) => ms.ext_check(params),
336+
}
337+
}
338+
339+
/// Check whether the descriptor is safe under the given extra parameters
340+
pub fn ext_check(&self, params: &ExtParams) -> Result<(), AnalysisError> {
341+
match *self {
342+
Descriptor::Bare(ref bare) => bare.as_inner().ext_check(params),
343+
Descriptor::Pkh(_) => Ok(()),
344+
Descriptor::Wpkh(ref wpkh) => Ok(()),
345+
Descriptor::Wsh(ref wsh) => Self::ext_check_wsh(&wsh, params),
346+
Descriptor::Sh(ref sh) => Self::ext_check_sh(&sh, params),
347+
Descriptor::Tr(ref tr) => tr.ext_check(params),
348+
}
349+
}
350+
321351
/// Computes an upper bound on the difference between a non-satisfied
322352
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
323353
///
@@ -1117,7 +1147,7 @@ mod tests {
11171147
StdDescriptor::from_str(TEST_PK).unwrap();
11181148

11191149
let uncompressed_pk =
1120-
"0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf";
1150+
"0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf";
11211151

11221152
// Context tests
11231153
StdDescriptor::from_str(&format!("pk({})", uncompressed_pk)).unwrap();
@@ -1302,6 +1332,29 @@ mod tests {
13021332
);
13031333
}
13041334

1335+
#[test]
1336+
fn ext_check() {
1337+
/// Make sure that default ext_check() catches OP_DROP but
1338+
/// ext_check() with OP_DROP explicitly allowed does not.
1339+
fn assert_opdrop_error(desc: &str) {
1340+
let desc = Descriptor::<PublicKey>::from_str(desc).unwrap();
1341+
assert_eq!(
1342+
desc.ext_check(&ExtParams::default()).unwrap_err(),
1343+
AnalysisError::ContainsDrop
1344+
);
1345+
assert_eq!(desc.ext_check(&ExtParams::default().drop()), Ok(()));
1346+
}
1347+
1348+
let secp = secp256k1::Secp256k1::new();
1349+
let sk =
1350+
secp256k1::SecretKey::from_slice(&b"sally was a secret key, she said"[..]).unwrap();
1351+
let pk = bitcoin::PublicKey::new(secp256k1::PublicKey::from_secret_key(&secp, &sk));
1352+
let inner = format!("and_v(r:after(1),c:pk_k({}))", pk);
1353+
assert_opdrop_error(format!("sh({})", inner).as_str());
1354+
assert_opdrop_error(format!("wsh({})", inner).as_str());
1355+
assert_opdrop_error(format!("sh(wsh({}))", inner).as_str());
1356+
}
1357+
13051358
#[test]
13061359
fn satisfy() {
13071360
let secp = secp256k1::Secp256k1::new();
@@ -1648,7 +1701,7 @@ mod tests {
16481701
let descriptor = Descriptor::<PublicKey>::from_str(
16491702
"wsh(multi(2,03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd,03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626))",
16501703
)
1651-
.unwrap();
1704+
.unwrap();
16521705
assert_eq!(
16531706
*descriptor
16541707
.script_code().unwrap()
@@ -1679,7 +1732,7 @@ mod tests {
16791732
bip32::ChildNumber::from_hardened_idx(0).unwrap(),
16801733
bip32::ChildNumber::from_hardened_idx(0).unwrap(),
16811734
][..])
1682-
.into(),
1735+
.into(),
16831736
)),
16841737
xkey: bip32::Xpub::from_str("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL").unwrap(),
16851738
derivation_path: (&[bip32::ChildNumber::from_normal_idx(1).unwrap()][..]).into(),
@@ -1741,7 +1794,7 @@ mod tests {
17411794
key: SinglePubKey::FullKey(bitcoin::PublicKey::from_str(
17421795
"04f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446a",
17431796
)
1744-
.unwrap()),
1797+
.unwrap()),
17451798
origin: None,
17461799
});
17471800
assert_eq!(expected, key.parse().unwrap());

src/descriptor/tr.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ use crate::policy::Liftable;
2323
use crate::prelude::*;
2424
use crate::util::{varint_len, witness_size};
2525
use crate::{
26-
errstr, Error, ForEachKey, FromStrKey, MiniscriptKey, Satisfier, ScriptContext, Tap, Threshold,
27-
ToPublicKey, TranslateErr, Translator,
26+
errstr, AnalysisError, Error, ExtParams, ForEachKey, FromStrKey, MiniscriptKey, Satisfier,
27+
ScriptContext, Tap, Threshold, ToPublicKey, TranslateErr, Translator,
2828
};
2929

3030
/// A Taproot Tree representation.
@@ -252,6 +252,14 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
252252
Ok(())
253253
}
254254

255+
/// Check whether the descriptor follows certain extra constraints.
256+
pub fn ext_check(&self, params: &ExtParams) -> Result<(), AnalysisError> {
257+
for (_depth, ms) in self.iter_scripts() {
258+
ms.ext_check(params)?;
259+
}
260+
Ok(())
261+
}
262+
255263
/// Computes an upper bound on the difference between a non-satisfied
256264
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
257265
///

0 commit comments

Comments
 (0)