Skip to content

Commit 29f1345

Browse files
committed
miniscript: add validate() method to Miniscript type
This is a fairly simple commit and maybe should be squashed into the previous one. It still does not actually use any of the functionality that is being introduced. (Trivia: in a much earlier version of this commit, I added a validation_params field to the Miniscript type, with the idea that the validation parameters would be set at the terminals (to e.g. Segwitv0::SANE) and then propagated upward, so that it would be impossible to construct a Miniscript fragment without having the validation parameters set. However, it turned out that this is not really workable, because many of our parameters (notably allow_sigless_branch and allow_non_b) are "top level only" checks. If they are set on terminals then they will trigger incorrectly. So they must be unset, and then they won't propagate to the top level. One solution to this could be to separate out the Miniscript type into two: a recursive "fragment" type used during construction and then a "toplevel" type which the user actually uses. We have done this for `Expression` for example. I believe we will eventually get to this point in a later PR series in which we eliminate recursion from Miniscript and the two Policy types. But we're not there now, and we can only change one thing at a time without getting overwhelmed.)
1 parent 9b49fec commit 29f1345

2 files changed

Lines changed: 172 additions & 4 deletions

File tree

src/interpreter/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1568,7 +1568,7 @@ mod tests {
15681568
}
15691569

15701570
fn x_only_no_checks_ms(ms: &str) -> Miniscript<BitcoinKey, NoChecks> {
1571-
let elem: Miniscript<bitcoin::key::XOnlyPublicKey, NoChecks> =
1571+
let elem: Miniscript<bitcoin::XOnlyPublicKey, NoChecks> =
15721572
Miniscript::from_str_ext(ms, &ExtParams::allow_all()).unwrap();
15731573
elem.to_no_checks_ms()
15741574
}

src/miniscript/mod.rs

Lines changed: 171 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ use core::cmp;
3939

4040
use sync::Arc;
4141

42+
pub use self::context::ScriptContext;
4243
use self::lex::{lex, TokenIter};
4344
use crate::expression::{FromTree, TreeIterItem};
44-
pub use crate::miniscript::context::ScriptContext;
4545
use crate::miniscript::decode::Terminal;
4646
use crate::{
4747
expression, plan, Error, ForEachKey, FromStrKey, MiniscriptKey, ToPublicKey, Translator,
@@ -54,10 +54,13 @@ mod private {
5454

5555
use super::limits::{MAX_PUBKEYS_IN_CHECKSIGADD, MAX_PUBKEYS_PER_MULTISIG};
5656
use super::types::{self, ExtData, Type};
57+
use super::ScriptContext;
5758
use crate::iter::TreeLike as _;
58-
pub use crate::miniscript::context::ScriptContext;
5959
use crate::prelude::sync::Arc;
60-
use crate::{AbsLockTime, Error, MiniscriptKey, RelLockTime, Terminal, MAX_RECURSION_DEPTH};
60+
use crate::{
61+
AbsLockTime, Error, MiniscriptKey, RelLockTime, Terminal, ValidationError,
62+
ValidationParams, MAX_RECURSION_DEPTH,
63+
};
6164

6265
/// The top-level miniscript abstract syntax tree (AST).
6366
pub struct Miniscript<Pk: MiniscriptKey, Ctx: ScriptContext> {
@@ -326,6 +329,7 @@ mod private {
326329
node: t,
327330
phantom: PhantomData,
328331
};
332+
329333
// TODO: This recursion depth is based on segwitv0.
330334
// We can relax this in tapscript, but this should be good for almost
331335
// all practical cases and we can revisit this if needed.
@@ -349,6 +353,170 @@ mod private {
349353
) -> Miniscript<Pk, Ctx> {
350354
Miniscript { node, ty, ext, phantom: PhantomData }
351355
}
356+
357+
/// Validates whether a given fragment meets the given set of
358+
/// validation parameters.
359+
pub fn validate(&self, params: &ValidationParams) -> Result<(), ValidationError>
360+
where
361+
Pk: MiniscriptKey,
362+
Ctx: ScriptContext,
363+
{
364+
self.validate_non_top_level(params)?;
365+
366+
// Malleability is only a top-level check since you can fix malleability
367+
// in some cases by adding wrappers.
368+
if !params.allow_malleability && !self.is_non_malleable() {
369+
return Err(ValidationError::Malleable);
370+
}
371+
372+
// The B check is inherently top-level.
373+
if !params.allow_non_b && self.ty.corr.base != types::Base::B {
374+
return Err(ValidationError::NonBase(self.ty.corr.base));
375+
}
376+
377+
// Sigless branches can be fixed by adding a conjunction with a signature.
378+
if !params.allow_sigless_branch && !self.requires_sig() {
379+
return Err(ValidationError::SiglessBranch);
380+
}
381+
382+
// Unsatisifiable scripts can be fixed by adding a disjunction with something
383+
// satisfiable.
384+
if !params.allow_unsatisfiable && self.ext.sat_data.is_none() {
385+
return Err(ValidationError::Unsatisfiable);
386+
}
387+
388+
// All checks passed.
389+
Ok(())
390+
}
391+
392+
/// Validates a miniscript, doing only the checks that are applicable to all nodes,
393+
/// not just top-level ones.
394+
///
395+
/// In particular this excludes the "must be B" and "no sigless branches" checks.
396+
/// To get these, run [`Self::validate`] which also calls through to this method.
397+
pub fn validate_non_top_level(
398+
&self,
399+
params: &ValidationParams,
400+
) -> Result<(), ValidationError> {
401+
if self.ext.tree_height > params.max_recursive_depth {
402+
return Err(ValidationError::MaxRecursiveDepthExceeded {
403+
limit: params.max_recursive_depth,
404+
});
405+
}
406+
if !params.allow_duplicate_keys && self.has_repeated_keys() {
407+
return Err(ValidationError::DuplicateKeys);
408+
}
409+
if !params.allow_mixed_time_locks && self.has_mixed_timelocks() {
410+
return Err(ValidationError::MixedTimeLocks);
411+
}
412+
413+
let mut multipath_len = None;
414+
let mut multipath_check = |pk: &Pk| {
415+
if params.allow_inconsistent_multipath_keys {
416+
return Ok(());
417+
}
418+
match (multipath_len, pk.num_der_paths()) {
419+
(_, 0) | (_, 1) => {}
420+
(None, n) => multipath_len = Some(n),
421+
(Some(x), y) if x == y => { /* ok */ }
422+
(Some(x), y) => {
423+
return Err(ValidationError::MultipathKeyLenMismatch { len1: x, len2: y })
424+
}
425+
}
426+
Ok(())
427+
};
428+
for ms in self.iter() {
429+
match ms.node {
430+
Terminal::DupIf(..) if !params.allow_dup_if => {
431+
return Err(ValidationError::IllegalDupIf)
432+
}
433+
Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) => {
434+
if !params.allow_multi {
435+
return Err(ValidationError::IllegalMulti);
436+
}
437+
for key in thresh.iter() {
438+
params.validate_pk(key).map_err(ValidationError::Key)?;
439+
multipath_check(key)?;
440+
}
441+
}
442+
Terminal::MultiA(ref thresh) | Terminal::SortedMultiA(ref thresh) => {
443+
if !params.allow_multi_a {
444+
return Err(ValidationError::IllegalMultiA);
445+
}
446+
for key in thresh.iter() {
447+
params.validate_pk(key).map_err(ValidationError::Key)?;
448+
multipath_check(key)?;
449+
}
450+
}
451+
Terminal::OrI(..) if !params.allow_or_i => {
452+
return Err(ValidationError::IllegalOrI)
453+
}
454+
Terminal::RawPkH(..) if !params.allow_raw_pkh => {
455+
return Err(ValidationError::IllegalRawPkh)
456+
}
457+
Terminal::PkK(ref pk) | Terminal::PkH(ref pk) => {
458+
params.validate_pk(pk).map_err(ValidationError::Key)?;
459+
multipath_check(pk)?;
460+
}
461+
_ => {}
462+
}
463+
}
464+
465+
// FIXME we have to gate this check on params.max_script_size being finite
466+
// because otherwise we'll attempt to call self.script_size(), which calls
467+
// Ctx::pk_len, when validating NoChecks scripts. But NoChecks::pk_len just
468+
// panics because we've forgotten the length of keys once we're in the
469+
// NoChecks context.
470+
//
471+
// This gate will be removed in a later PR which removes Ctx::pk_len and
472+
// replaces it with a less-fragile solution.
473+
if params.max_script_size < usize::MAX && self.script_size() > params.max_script_size {
474+
return Err(ValidationError::MaxScriptSizeExceeded {
475+
actual: self.script_size(),
476+
limit: params.max_script_size,
477+
});
478+
}
479+
// Satisfiability checks -- if there is no satisfaciton data set then we will
480+
// early-return here, so all other checks should be done before this.
481+
match self.max_satisfaction_witness_elements() {
482+
// No possible satisfactions -- we are doing non-toplevel checks here
483+
// so fail gracefully.
484+
Err(..) => return Ok(()),
485+
Ok(max_n) if max_n > params.max_witness_items => {
486+
return Err(ValidationError::MaxWitnessItemsExceeded {
487+
actual: max_n,
488+
limit: params.max_witness_items,
489+
})
490+
}
491+
Ok(..) => {}
492+
}
493+
494+
let sat_op_count = self
495+
.ext
496+
.sat_op_count()
497+
.expect("checked that satisfaction was possible above");
498+
if sat_op_count > params.max_opcode_count {
499+
return Err(ValidationError::MaxOpCountExceeded {
500+
actual: sat_op_count,
501+
limit: params.max_opcode_count,
502+
});
503+
}
504+
505+
let sat_data = self
506+
.ext
507+
.sat_data
508+
.expect("checked that satisfaction was possible above");
509+
if sat_data.max_witness_stack_count + sat_data.max_exec_stack_count
510+
> params.max_exec_stack_size
511+
{
512+
return Err(ValidationError::MaxExecStackSizeExceeded {
513+
actual: sat_data.max_witness_stack_size + sat_data.max_exec_stack_count,
514+
limit: params.max_exec_stack_size,
515+
});
516+
}
517+
518+
Ok(())
519+
}
352520
}
353521
}
354522

0 commit comments

Comments
 (0)