Skip to content

Commit 427222e

Browse files
policy: minimum_n_keys returns an Option
minimum_n_keys returns None if the policy is not satisfiable. It would previously (erroneously) return 0. Fixes #264
1 parent b9aa66d commit 427222e

File tree

3 files changed

+79
-19
lines changed

3 files changed

+79
-19
lines changed

src/miniscript/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -791,16 +791,16 @@ mod tests {
791791

792792
let mut abs = miniscript.lift().unwrap();
793793
assert_eq!(abs.n_keys(), 5);
794-
assert_eq!(abs.minimum_n_keys(), 2);
794+
assert_eq!(abs.minimum_n_keys(), Some(2));
795795
abs = abs.at_age(10000);
796796
assert_eq!(abs.n_keys(), 5);
797-
assert_eq!(abs.minimum_n_keys(), 2);
797+
assert_eq!(abs.minimum_n_keys(), Some(2));
798798
abs = abs.at_age(9999);
799799
assert_eq!(abs.n_keys(), 3);
800-
assert_eq!(abs.minimum_n_keys(), 3);
800+
assert_eq!(abs.minimum_n_keys(), Some(3));
801801
abs = abs.at_age(0);
802802
assert_eq!(abs.n_keys(), 3);
803-
assert_eq!(abs.minimum_n_keys(), 3);
803+
assert_eq!(abs.minimum_n_keys(), Some(3));
804804

805805
roundtrip(&ms_str!("older(921)"), "Script(OP_PUSHBYTES_2 9903 OP_CSV)");
806806

src/policy/compiler.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,16 +1352,16 @@ mod tests {
13521352

13531353
let mut abs = policy.lift().unwrap();
13541354
assert_eq!(abs.n_keys(), 8);
1355-
assert_eq!(abs.minimum_n_keys(), 2);
1355+
assert_eq!(abs.minimum_n_keys(), Some(2));
13561356
abs = abs.at_age(10000);
13571357
assert_eq!(abs.n_keys(), 8);
1358-
assert_eq!(abs.minimum_n_keys(), 2);
1358+
assert_eq!(abs.minimum_n_keys(), Some(2));
13591359
abs = abs.at_age(9999);
13601360
assert_eq!(abs.n_keys(), 5);
1361-
assert_eq!(abs.minimum_n_keys(), 3);
1361+
assert_eq!(abs.minimum_n_keys(), Some(3));
13621362
abs = abs.at_age(0);
13631363
assert_eq!(abs.n_keys(), 5);
1364-
assert_eq!(abs.minimum_n_keys(), 3);
1364+
assert_eq!(abs.minimum_n_keys(), Some(3));
13651365

13661366
let bitcoinsig = (sig, SigHashType::All);
13671367
let mut sigvec = sig.serialize_der().to_vec();

src/policy/semantic.rs

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -583,20 +583,28 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
583583

584584
/// Count the minimum number of public keys for which signatures
585585
/// could be used to satisfy the policy.
586-
pub fn minimum_n_keys(&self) -> usize {
586+
/// Returns `None` if the policy is not satisfiable.
587+
pub fn minimum_n_keys(&self) -> Option<usize> {
587588
match *self {
588-
Policy::Unsatisfiable | Policy::Trivial => 0,
589-
Policy::KeyHash(..) => 1,
589+
Policy::Unsatisfiable => None,
590+
Policy::Trivial => Some(0),
591+
Policy::KeyHash(..) => Some(1),
590592
Policy::After(..)
591593
| Policy::Older(..)
592594
| Policy::Sha256(..)
593595
| Policy::Hash256(..)
594596
| Policy::Ripemd160(..)
595-
| Policy::Hash160(..) => 0,
597+
| Policy::Hash160(..) => Some(0),
596598
Policy::Threshold(k, ref subs) => {
597-
let mut sublens: Vec<usize> = subs.iter().map(Policy::minimum_n_keys).collect();
598-
sublens.sort();
599-
sublens[0..k].iter().cloned().sum::<usize>()
599+
let mut sublens: Vec<usize> =
600+
subs.iter().filter_map(Policy::minimum_n_keys).collect();
601+
if sublens.len() < k {
602+
// Not enough branches are satisfiable
603+
None
604+
} else {
605+
sublens.sort();
606+
Some(sublens[0..k].iter().cloned().sum::<usize>())
607+
}
600608
}
601609
}
602610
}
@@ -655,7 +663,7 @@ mod tests {
655663
assert_eq!(policy.clone().at_age(0), policy.clone());
656664
assert_eq!(policy.clone().at_age(10000), policy.clone());
657665
assert_eq!(policy.n_keys(), 1);
658-
assert_eq!(policy.minimum_n_keys(), 1);
666+
assert_eq!(policy.minimum_n_keys(), Some(1));
659667

660668
let policy = StringPolicy::from_str("older(1000)").unwrap();
661669
assert_eq!(policy, Policy::Older(1000));
@@ -666,7 +674,7 @@ mod tests {
666674
assert_eq!(policy.clone().at_age(1000), policy.clone());
667675
assert_eq!(policy.clone().at_age(10000), policy.clone());
668676
assert_eq!(policy.n_keys(), 0);
669-
assert_eq!(policy.minimum_n_keys(), 0);
677+
assert_eq!(policy.minimum_n_keys(), Some(0));
670678

671679
let policy = StringPolicy::from_str("or(pkh(),older(1000))").unwrap();
672680
assert_eq!(
@@ -683,7 +691,33 @@ mod tests {
683691
assert_eq!(policy.clone().at_age(1000), policy.clone().normalized());
684692
assert_eq!(policy.clone().at_age(10000), policy.clone().normalized());
685693
assert_eq!(policy.n_keys(), 1);
686-
assert_eq!(policy.minimum_n_keys(), 0);
694+
assert_eq!(policy.minimum_n_keys(), Some(0));
695+
696+
let policy = StringPolicy::from_str("or(pkh(),UNSATISFIABLE)").unwrap();
697+
assert_eq!(
698+
policy,
699+
Policy::Threshold(
700+
1,
701+
vec![Policy::KeyHash("".to_owned()), Policy::Unsatisfiable,]
702+
)
703+
);
704+
assert_eq!(policy.relative_timelocks(), vec![]);
705+
assert_eq!(policy.absolute_timelocks(), vec![]);
706+
assert_eq!(policy.n_keys(), 1);
707+
assert_eq!(policy.minimum_n_keys(), Some(1));
708+
709+
let policy = StringPolicy::from_str("and(pkh(),UNSATISFIABLE)").unwrap();
710+
assert_eq!(
711+
policy,
712+
Policy::Threshold(
713+
2,
714+
vec![Policy::KeyHash("".to_owned()), Policy::Unsatisfiable,]
715+
)
716+
);
717+
assert_eq!(policy.relative_timelocks(), vec![]);
718+
assert_eq!(policy.absolute_timelocks(), vec![]);
719+
assert_eq!(policy.n_keys(), 1);
720+
assert_eq!(policy.minimum_n_keys(), None);
687721

688722
let policy = StringPolicy::from_str(
689723
"thresh(\
@@ -709,6 +743,32 @@ mod tests {
709743
vec![1000, 2000, 10000] //sorted and dedup'd
710744
);
711745

746+
let policy = StringPolicy::from_str(
747+
"thresh(\
748+
2,older(1000),older(10000),older(1000),UNSATISFIABLE,UNSATISFIABLE\
749+
)",
750+
)
751+
.unwrap();
752+
assert_eq!(
753+
policy,
754+
Policy::Threshold(
755+
2,
756+
vec![
757+
Policy::Older(1000),
758+
Policy::Older(10000),
759+
Policy::Older(1000),
760+
Policy::Unsatisfiable,
761+
Policy::Unsatisfiable,
762+
]
763+
)
764+
);
765+
assert_eq!(
766+
policy.relative_timelocks(),
767+
vec![1000, 10000] //sorted and dedup'd
768+
);
769+
assert_eq!(policy.n_keys(), 0);
770+
assert_eq!(policy.minimum_n_keys(), Some(0));
771+
712772
let policy = StringPolicy::from_str("after(1000)").unwrap();
713773
assert_eq!(policy, Policy::After(1000));
714774
assert_eq!(policy.absolute_timelocks(), vec![1000]);
@@ -718,7 +778,7 @@ mod tests {
718778
assert_eq!(policy.clone().at_height(1000), policy.clone());
719779
assert_eq!(policy.clone().at_height(10000), policy.clone());
720780
assert_eq!(policy.n_keys(), 0);
721-
assert_eq!(policy.minimum_n_keys(), 0);
781+
assert_eq!(policy.minimum_n_keys(), Some(0));
722782
}
723783

724784
#[test]

0 commit comments

Comments
 (0)