Skip to content

Commit 92eda4d

Browse files
committed
Merge #282: Context: check for the maximum number of public keys in a CHECKMULTISIG
f365e23 miniscript: check for max num of pubkeys in Multi (Antoine Poinsot) 505ddc6 miniscript: introduce MAX_PUBKEYS_PER_MULTISIG constant (Antoine Poinsot) Pull request description: We check at parsing time, but we would still allow to create a `Multi` with `> 20` keys and not error on sanity checks! I'd add a test but wanted to push a patch quickly first. Reported by @danielabrozzoni . ACKs for top commit: apoelstra: ACK f365e23 Tree-SHA512: d5c08e6c6c3a4485e4aefb4049bee3278d66189717c8b8fe31d4b1feccb1183ae5afb2ebdd00b5330145a14cf1599c71109e73b99c87d530977d212928bedf31
2 parents 15a8aea + f365e23 commit 92eda4d

File tree

5 files changed

+36
-6
lines changed

5 files changed

+36
-6
lines changed

src/descriptor/sortedmulti.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ use std::{fmt, marker::PhantomData, str::FromStr};
2121
use bitcoin::blockdata::script;
2222

2323
use expression;
24-
use miniscript::{self, context::ScriptContext, decode::Terminal};
24+
use miniscript::{
25+
self, context::ScriptContext, decode::Terminal, limits::MAX_PUBKEYS_PER_MULTISIG,
26+
};
2527
use policy;
2628
use script_num_size;
2729
use {errstr, Error, ForEach, ForEachKey, Miniscript, MiniscriptKey, Satisfier, ToPublicKey};
@@ -43,7 +45,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
4345
/// Internally checks all the applicable size limits and pubkey types limitations according to the current `Ctx`.
4446
pub fn new(k: usize, pks: Vec<Pk>) -> Result<Self, Error> {
4547
// A sortedmulti() is only defined for <= 20 keys (it maps to CHECKMULTISIG)
46-
if pks.len() > 20 {
48+
if pks.len() > MAX_PUBKEYS_PER_MULTISIG {
4749
Error::BadDescriptor("Too many public keys".to_string());
4850
}
4951

src/miniscript/context.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ use std::{fmt, hash};
1717
use bitcoin;
1818
use bitcoin::blockdata::constants::MAX_BLOCK_WEIGHT;
1919
use miniscript::limits::{
20-
MAX_OPS_PER_SCRIPT, MAX_SCRIPTSIG_SIZE, MAX_SCRIPT_ELEMENT_SIZE, MAX_SCRIPT_SIZE,
21-
MAX_STACK_SIZE, MAX_STANDARD_P2WSH_SCRIPT_SIZE, MAX_STANDARD_P2WSH_STACK_ITEMS,
20+
MAX_OPS_PER_SCRIPT, MAX_PUBKEYS_PER_MULTISIG, MAX_SCRIPTSIG_SIZE, MAX_SCRIPT_ELEMENT_SIZE,
21+
MAX_SCRIPT_SIZE, MAX_STACK_SIZE, MAX_STANDARD_P2WSH_SCRIPT_SIZE,
22+
MAX_STANDARD_P2WSH_STACK_ITEMS,
2223
};
2324
use miniscript::types;
2425
use util::witness_to_scriptsig;
@@ -67,6 +68,8 @@ pub enum ScriptContextError {
6768
TaprootMultiDisabled,
6869
/// Stack size exceeded in script execution
6970
StackSizeLimitExceeded { actual: usize, limit: usize },
71+
/// More than 20 keys in a Multi fragment
72+
CheckMultiSigLimitExceeded,
7073
}
7174

7275
impl fmt::Display for ScriptContextError {
@@ -132,6 +135,12 @@ impl fmt::Display for ScriptContextError {
132135
actual, limit
133136
)
134137
}
138+
ScriptContextError::CheckMultiSigLimitExceeded => {
139+
write!(
140+
f,
141+
"CHECkMULTISIG ('multi()' descriptor) only supports up to 20 pubkeys"
142+
)
143+
}
135144
}
136145
}
137146
}
@@ -323,6 +332,16 @@ impl ScriptContext for Legacy {
323332
if ms.ext.pk_cost > MAX_SCRIPT_ELEMENT_SIZE {
324333
return Err(ScriptContextError::MaxRedeemScriptSizeExceeded);
325334
}
335+
336+
match ms.node {
337+
Terminal::Multi(_k, ref pks) => {
338+
if pks.len() > MAX_PUBKEYS_PER_MULTISIG {
339+
return Err(ScriptContextError::CheckMultiSigLimitExceeded);
340+
}
341+
}
342+
_ => {}
343+
}
344+
326345
Ok(())
327346
}
328347

@@ -408,6 +427,9 @@ impl ScriptContext for Segwitv0 {
408427
Ok(())
409428
}
410429
Terminal::Multi(_k, ref pks) => {
430+
if pks.len() > MAX_PUBKEYS_PER_MULTISIG {
431+
return Err(ScriptContextError::CheckMultiSigLimitExceeded);
432+
}
411433
for pk in pks.iter() {
412434
if pk.is_uncompressed() {
413435
return Err(ScriptContextError::CompressedOnly(pk.to_string()));

src/miniscript/decode.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use std::{error, fmt};
2323
use {bitcoin, Miniscript};
2424

2525
use miniscript::lex::{Token as Tk, TokenIter};
26+
use miniscript::limits::MAX_PUBKEYS_PER_MULTISIG;
2627
use miniscript::types::extra_props::ExtData;
2728
use miniscript::types::Property;
2829
use miniscript::types::Type;
@@ -465,7 +466,7 @@ pub fn parse<Ctx: ScriptContext>(
465466
},
466467
// CHECKMULTISIG based multisig
467468
Tk::CheckMultiSig, Tk::Num(n) => {
468-
if n > 20 {
469+
if n as usize > MAX_PUBKEYS_PER_MULTISIG {
469470
return Err(Error::CmsTooManyKeys(n));
470471
}
471472
let mut keys = Vec::with_capacity(n as usize);

src/miniscript/limits.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,7 @@ pub const MAX_SCRIPTSIG_SIZE: usize = 1650;
4646
pub const MAX_STACK_SIZE: usize = 1000;
4747
/** The maximum allowed weight for a block, see BIP 141 (network rule) */
4848
pub const MAX_BLOCK_WEIGHT: usize = 4000000;
49+
50+
/// Maximum pubkeys as arguments to CHECKMULTISIG
51+
// https://github.com/bitcoin/bitcoin/blob/6acda4b00b3fc1bfac02f5de590e1a5386cbc779/src/script/script.h#L30
52+
pub const MAX_PUBKEYS_PER_MULTISIG: usize = 20;

src/policy/compiler.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use std::convert::From;
2222
use std::marker::PhantomData;
2323
use std::{cmp, error, f64, fmt, mem};
2424

25+
use miniscript::limits::MAX_PUBKEYS_PER_MULTISIG;
2526
use miniscript::types::{self, ErrorKind, ExtData, Property, Type};
2627
use miniscript::ScriptContext;
2728
use policy::Concrete;
@@ -993,7 +994,7 @@ where
993994
})
994995
.collect();
995996

996-
if key_vec.len() == subs.len() && subs.len() <= 20 {
997+
if key_vec.len() == subs.len() && subs.len() <= MAX_PUBKEYS_PER_MULTISIG {
997998
insert_wrap!(AstElemExt::terminal(Terminal::Multi(k, key_vec)));
998999
}
9991000
// Not a threshold, it's always more optimal to translate it to and()s as we save the

0 commit comments

Comments
 (0)