Skip to content

Commit 5a5f749

Browse files
committed
Merge #1040: fix(miniscript): count exactly k satisfactions in thresh max-size bounds
d7801d1 fix(miniscript): count exactly k satisfactions in thresh max-size bounds (russeree) Pull request description: A `thresh(k, ...)` witness satisfies exactly k children: the script checks the running sum against k with `OP_EQUAL`, so any witness with a different number of satisfied children is invalid. The maximum-satisfaction computation in `ExtData::threshold` nevertheless selected k+1 children as satisfied (`i <= k` instead of `i < k`), e.g. computing the maximum witness size of `thresh(1,pk(A),s:pk(B))` as 146 bytes (two signatures) instead of 74 (one signature plus one empty dissatisfaction). This inflates `Miniscript::max_satisfaction_weight()` and everything derived from it, making fee/weight estimates overestimate by up to one largest-child satisfaction per thresh node. Found by differential testing against Bitcoin Core's miniscript implementation (`Node::GetWitnessSize`/`GetStackSize` agree with the fixed values, and with the reference implementation's satisfaction table, which permits exactly k satisfactions). ACKs for top commit: apoelstra: ACK d7801d1; successfully ran local tests Tree-SHA512: 6b4a96f1d6fe27794d38c53b605630571a3333a9645ef7faf31014c67b2a693e4c72a02d321437938d121eab9bda86c4ea646ab2c5daa1cfd03dbac798b39f55
2 parents 83e83e1 + d7801d1 commit 5a5f749

2 files changed

Lines changed: 18 additions & 1 deletion

File tree

‎src/miniscript/mod.rs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2112,6 +2112,23 @@ mod tests {
21122112
}
21132113
}
21142114

2115+
#[test]
2116+
fn test_thresh_sat_data_exact_k_satisfactions() {
2117+
let ms =
2118+
Miniscript::<String, Segwitv0>::from_str_insane("thresh(1,pk(A),s:pk(B))").unwrap();
2119+
let sat = ms.ext.sat_data.expect("thresh(1,...) is satisfiable");
2120+
assert_eq!(sat.max_witness_stack_size, 74);
2121+
assert_eq!(sat.max_witness_stack_count, 2);
2122+
assert_eq!(sat.max_script_sig_size, 74);
2123+
2124+
let ms = Miniscript::<String, Segwitv0>::from_str_insane("thresh(2,pk(A),s:pk(B),a:pk(C))")
2125+
.unwrap();
2126+
let sat = ms.ext.sat_data.expect("thresh(2,...) is satisfiable");
2127+
assert_eq!(sat.max_witness_stack_size, 147);
2128+
assert_eq!(sat.max_witness_stack_count, 3);
2129+
assert_eq!(sat.max_script_sig_size, 147);
2130+
}
2131+
21152132
#[test]
21162133
fn test_context_global_consensus() {
21172134
// Test from string tests

‎src/miniscript/types/extra_props.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -901,7 +901,7 @@ impl ExtData {
901901
.rev()
902902
.enumerate()
903903
.try_fold(0, |acc, (i, &(sat, dissat))| {
904-
if i <= k {
904+
if i < k {
905905
sat.map(|x| cmp(acc, proj(x)))
906906
} else {
907907
dissat.map(|y| cmp(acc, proj(y)))

0 commit comments

Comments
 (0)