Skip to content

Commit

Permalink
Merge pull request #113 from darosior/optimize_nofn
Browse files Browse the repository at this point in the history
Optimize policy compilation for N-of-N tresholds
  • Loading branch information
apoelstra authored Aug 27, 2020
2 parents 1e7fe04 + 688b0a8 commit c2cfb12
Showing 1 changed file with 59 additions and 0 deletions.
59 changes: 59 additions & 0 deletions src/policy/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -992,9 +992,22 @@ where
}
})
.collect();

if key_vec.len() == subs.len() && subs.len() <= 20 {
insert_wrap!(AstElemExt::terminal(Terminal::Multi(k, key_vec)));
}
// Not a threshold, it's always more optimal to translate it to and()s as we save the
// resulting threshold check (N EQUAL) in any case.
else if k == subs.len() {
let mut policy = subs.first().expect("No sub policy in thresh() ?").clone();
for sub in &subs[1..] {
policy = Concrete::And(vec![sub.clone(), policy]);
}

ret = best_compilations(policy_cache, &policy, sat_prob, dissat_prob)?;
}

// FIXME: Should we also optimize thresh(1, subs) ?
}
}
for k in ret.keys() {
Expand Down Expand Up @@ -1153,6 +1166,7 @@ mod tests {

use miniscript::{satisfy, Segwitv0};
use policy::Liftable;
use script_num_size;
use BitcoinSig;
use DummyKey;

Expand Down Expand Up @@ -1377,6 +1391,51 @@ mod tests {
]
);
}

#[test]
fn compile_thresh() {
let (keys, _) = pubkeys_and_a_sig(21);

// Up until 20 keys, thresh should be compiled to a multi no matter the value of k
for k in 1..4 {
let small_thresh: BPolicy = policy_str!(
"thresh({},pk({}),pk({}),pk({}))",
k,
keys[0],
keys[1],
keys[2]
);
let small_thresh_ms: SegwitMiniScript = small_thresh.compile().unwrap();
let small_thresh_ms_expected: SegwitMiniScript =
ms_str!("multi({},{},{},{})", k, keys[0], keys[1], keys[2]);
assert_eq!(small_thresh_ms, small_thresh_ms_expected);
}

// Above 20 keys, thresh is compiled to a combination of and()s if it's a N of N,
// and to a ms thresh otherwise.
// k = 1 (or 2) does not compile, see https://github.com/rust-bitcoin/rust-miniscript/issues/114
for k in &[10, 15, 21] {
let pubkeys: Vec<Concrete<bitcoin::PublicKey>> =
keys.iter().map(|pubkey| Concrete::Key(*pubkey)).collect();
let big_thresh = Concrete::Threshold(*k, pubkeys);
let big_thresh_ms: SegwitMiniScript = big_thresh.compile().unwrap();
if *k == 21 {
// N * (PUSH + pubkey + CHECKSIGVERIFY)
assert_eq!(big_thresh_ms.script_size(), keys.len() * (1 + 33 + 1));
} else {
// N * (PUSH + pubkey + CHECKSIG + ADD + SWAP) + N EQUAL
assert_eq!(
big_thresh_ms.script_size(),
keys.len() * (1 + 33 + 3) + script_num_size(*k) + 1 - 2 // minus one SWAP and one ADD
);
let big_thresh_ms_expected = ms_str!(
"thresh({},pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}),s:pk({}))",
k, keys[0], keys[1], keys[2], keys[3], keys[4], keys[5], keys[6], keys[7], keys[8], keys[9],keys[10], keys[11], keys[12], keys[13], keys[14], keys[15], keys[16], keys[17], keys[18], keys[19], keys[20]
);
assert_eq!(big_thresh_ms, big_thresh_ms_expected);
};
}
}
}

#[cfg(all(test, feature = "unstable"))]
Expand Down

0 comments on commit c2cfb12

Please sign in to comment.