Skip to content

Commit a290a61

Browse files
committed
Fix panic while decoding large Miniscripts from Script
1 parent 9922e37 commit a290a61

File tree

4 files changed

+111
-169
lines changed

4 files changed

+111
-169
lines changed

src/miniscript/decode.rs

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
//!
1919
2020
use core::fmt;
21-
use core::marker::PhantomData;
2221
#[cfg(feature = "std")]
2322
use std::error;
2423

@@ -29,15 +28,10 @@ use sync::Arc;
2928
use crate::bitcoin::{LockTime, PackedLockTime, Sequence};
3029
use crate::miniscript::lex::{Token as Tk, TokenIter};
3130
use crate::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG;
32-
use crate::miniscript::types::extra_props::ExtData;
33-
use crate::miniscript::types::{Property, Type};
3431
use crate::miniscript::ScriptContext;
3532
use crate::prelude::*;
3633
use crate::{bitcoin, hash256, Error, Miniscript, MiniscriptKey, ToPublicKey};
3734

38-
fn return_none<T>(_: usize) -> Option<T> {
39-
None
40-
}
4135

4236
/// Trait for parsing keys from byte slices
4337
pub trait ParseableKey: Sized + ToPublicKey + private::Sealed {
@@ -233,15 +227,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> TerminalStack<Pk, Ctx> {
233227

234228
///reduce, type check and push a 0-arg node
235229
fn reduce0(&mut self, ms: Terminal<Pk, Ctx>) -> Result<(), Error> {
236-
let ty = Type::type_check(&ms, return_none)?;
237-
let ext = ExtData::type_check(&ms, return_none)?;
238-
let ms = Miniscript {
239-
node: ms,
240-
ty,
241-
ext,
242-
phantom: PhantomData,
243-
};
244-
Ctx::check_global_validity(&ms)?;
230+
let ms = Miniscript::from_ast(ms)?;
245231
self.0.push(ms);
246232
Ok(())
247233
}
@@ -254,15 +240,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> TerminalStack<Pk, Ctx> {
254240
let top = self.pop().unwrap();
255241
let wrapped_ms = wrap(Arc::new(top));
256242

257-
let ty = Type::type_check(&wrapped_ms, return_none)?;
258-
let ext = ExtData::type_check(&wrapped_ms, return_none)?;
259-
let ms = Miniscript {
260-
node: wrapped_ms,
261-
ty,
262-
ext,
263-
phantom: PhantomData,
264-
};
265-
Ctx::check_global_validity(&ms)?;
243+
let ms = Miniscript::from_ast(wrapped_ms)?;
266244
self.0.push(ms);
267245
Ok(())
268246
}
@@ -277,15 +255,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> TerminalStack<Pk, Ctx> {
277255

278256
let wrapped_ms = wrap(Arc::new(left), Arc::new(right));
279257

280-
let ty = Type::type_check(&wrapped_ms, return_none)?;
281-
let ext = ExtData::type_check(&wrapped_ms, return_none)?;
282-
let ms = Miniscript {
283-
node: wrapped_ms,
284-
ty,
285-
ext,
286-
phantom: PhantomData,
287-
};
288-
Ctx::check_global_validity(&ms)?;
258+
let ms = Miniscript::from_ast(wrapped_ms)?;
289259
self.0.push(ms);
290260
Ok(())
291261
}
@@ -566,15 +536,7 @@ pub fn parse<Ctx: ScriptContext>(
566536
let c = term.pop().unwrap();
567537
let wrapped_ms = Terminal::AndOr(Arc::new(a), Arc::new(c), Arc::new(b));
568538

569-
let ty = Type::type_check(&wrapped_ms, return_none)?;
570-
let ext = ExtData::type_check(&wrapped_ms, return_none)?;
571-
572-
term.0.push(Miniscript {
573-
node: wrapped_ms,
574-
ty,
575-
ext,
576-
phantom: PhantomData,
577-
});
539+
term.0.push(Miniscript::from_ast(wrapped_ms)?);
578540
}
579541
Some(NonTerm::ThreshW { n, k }) => {
580542
match_token!(

src/miniscript/mod.rs

Lines changed: 88 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,14 @@
2424
//! components of the AST.
2525
//!
2626
27-
use core::marker::PhantomData;
2827
use core::{fmt, hash, str};
2928

3029
use bitcoin::blockdata::script;
3130
use bitcoin::util::taproot::{LeafVersion, TapLeafHash};
3231

3332
use self::analyzable::ExtParams;
3433
pub use self::context::{BareCtx, Legacy, Segwitv0, Tap};
35-
use crate::{prelude::*, MAX_RECURSION_DEPTH};
34+
use crate::prelude::*;
3635

3736
pub mod analyzable;
3837
pub mod astelem;
@@ -53,25 +52,70 @@ use self::lex::{lex, TokenIter};
5352
use self::types::Property;
5453
pub use crate::miniscript::context::ScriptContext;
5554
use crate::miniscript::decode::Terminal;
56-
use crate::miniscript::types::extra_props::ExtData;
57-
use crate::miniscript::types::Type;
5855
use crate::{expression, Error, ForEachKey, MiniscriptKey, ToPublicKey, TranslatePk, Translator};
5956
#[cfg(test)]
6057
mod ms_tests;
6158

62-
/// Top-level script AST type
63-
#[derive(Clone)]
64-
pub struct Miniscript<Pk: MiniscriptKey, Ctx: ScriptContext> {
65-
///A node in the Abstract Syntax Tree(
66-
pub node: Terminal<Pk, Ctx>,
67-
///The correctness and malleability type information for the AST node
68-
pub ty: types::Type,
69-
///Additional information helpful for extra analysis.
70-
pub ext: types::extra_props::ExtData,
71-
/// Context PhantomData. Only accessible inside this crate
72-
pub(crate) phantom: PhantomData<Ctx>,
59+
mod private {
60+
use core::marker::PhantomData;
61+
62+
use super::types::{ExtData, Property, Type};
63+
pub use crate::miniscript::context::ScriptContext;
64+
use crate::miniscript::types;
65+
use crate::{Error, MiniscriptKey, Terminal, MAX_RECURSION_DEPTH};
66+
67+
/// The top-level miniscript abstract syntax tree (AST).
68+
#[derive(Clone)]
69+
pub struct Miniscript<Pk: MiniscriptKey, Ctx: ScriptContext> {
70+
/// A node in the AST.
71+
pub node: Terminal<Pk, Ctx>,
72+
/// The correctness and malleability type information for the AST node.
73+
pub ty: types::Type,
74+
/// Additional information helpful for extra analysis.
75+
pub ext: types::extra_props::ExtData,
76+
/// Context PhantomData. Only accessible inside this crate
77+
phantom: PhantomData<Ctx>,
78+
}
79+
impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
80+
81+
/// Add type information(Type and Extdata) to Miniscript based on
82+
/// `AstElem` fragment. Dependent on display and clone because of Error
83+
/// Display code of type_check.
84+
pub fn from_ast(t: Terminal<Pk, Ctx>) -> Result<Miniscript<Pk, Ctx>, Error> {
85+
let res = Miniscript {
86+
ty: Type::type_check(&t, |_| None)?,
87+
ext: ExtData::type_check(&t, |_| None)?,
88+
node: t,
89+
phantom: PhantomData,
90+
};
91+
// TODO: This recursion depth is based on segwitv0.
92+
// We can relax this in tapscript, but this should be good for almost
93+
// all practical cases and we can revisit this if needed.
94+
// casting to u32 is safe because tree_height will never go more than u32::MAX
95+
if (res.ext.tree_height as u32) > MAX_RECURSION_DEPTH {
96+
return Err(Error::MaxRecursiveDepthExceeded);
97+
}
98+
Ctx::check_global_consensus_validity(&res)?;
99+
Ok(res)
100+
}
101+
102+
/// Create a new `Miniscript` from a `Terminal` node and a `Type` annotation
103+
/// This does not check the typing rules. The user is responsible for ensuring
104+
/// that the type provided is correct.
105+
///
106+
/// You should almost always use `Miniscript::from_ast` instead of this function.
107+
pub fn from_components_unchecked(
108+
node: Terminal<Pk, Ctx>,
109+
ty: types::Type,
110+
ext: types::extra_props::ExtData,
111+
) -> Miniscript<Pk, Ctx> {
112+
Miniscript { node, ty, ext, phantom: PhantomData }
113+
}
114+
}
73115
}
74116

117+
pub use private::Miniscript;
118+
75119
/// `PartialOrd` of `Miniscript` must depend only on node and not the type information.
76120
/// The type information and extra_properties can be deterministically determined
77121
/// by the ast.
@@ -116,36 +160,14 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> hash::Hash for Miniscript<Pk, Ctx> {
116160
}
117161
}
118162

119-
impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
120-
/// Add type information(Type and Extdata) to Miniscript based on
121-
/// `AstElem` fragment. Dependent on display and clone because of Error
122-
/// Display code of type_check.
123-
pub fn from_ast(t: Terminal<Pk, Ctx>) -> Result<Miniscript<Pk, Ctx>, Error> {
124-
let res = Miniscript {
125-
ty: Type::type_check(&t, |_| None)?,
126-
ext: ExtData::type_check(&t, |_| None)?,
127-
node: t,
128-
phantom: PhantomData,
129-
};
130-
// TODO: This recursion depth is based on segwitv0.
131-
// We can relax this in tapscript, but this should be good for almost
132-
// all practical cases and we can revisit this if needed.
133-
// casting to u32 is safe because tree_height will never go more than u32::MAX
134-
if (res.ext.tree_height as u32) > MAX_RECURSION_DEPTH {
135-
Err(Error::MaxRecursiveDepthExceeded)
136-
} else {
137-
Ok(res)
138-
}
139-
}
140-
}
141-
142163
impl<Pk: MiniscriptKey, Ctx: ScriptContext> fmt::Display for Miniscript<Pk, Ctx> {
143164
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
144165
write!(f, "{}", self.node)
145166
}
146167
}
147168

148169
impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
170+
149171
/// Extracts the `AstElem` representing the root of the miniscript
150172
pub fn into_inner(self) -> Terminal<Pk, Ctx> {
151173
self.node
@@ -338,14 +360,8 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
338360
T: Translator<Pk, Q, FuncError>,
339361
{
340362
let inner = self.node.real_translate_pk(t)?;
341-
let ms = Miniscript {
342-
//directly copying the type and ext is safe because translating public
343-
//key should not change any properties
344-
ty: self.ty,
345-
ext: self.ext,
346-
node: inner,
347-
phantom: PhantomData,
348-
};
363+
let ms = Miniscript::from_ast(inner)
364+
.expect("Translator should not change the type of the AST");
349365
Ok(ms)
350366
}
351367
}
@@ -455,12 +471,7 @@ impl_from_tree!(
455471
/// should not be called directly; rather go through the descriptor API.
456472
fn from_tree(top: &expression::Tree) -> Result<Miniscript<Pk, Ctx>, Error> {
457473
let inner: Terminal<Pk, Ctx> = expression::FromTree::from_tree(top)?;
458-
Ok(Miniscript {
459-
ty: Type::type_check(&inner, |_| None)?,
460-
ext: ExtData::type_check(&inner, |_| None)?,
461-
node: inner,
462-
phantom: PhantomData,
463-
})
474+
Ok(Miniscript::from_ast(inner)?)
464475
}
465476
);
466477

@@ -482,7 +493,6 @@ serde_string_impl_pk!(Miniscript, "a miniscript", Ctx; ScriptContext);
482493
#[cfg(test)]
483494
mod tests {
484495

485-
use core::marker::PhantomData;
486496
use core::str;
487497
use core::str::FromStr;
488498

@@ -493,7 +503,7 @@ mod tests {
493503
use sync::Arc;
494504

495505
use super::{Miniscript, ScriptContext, Segwitv0, Tap};
496-
use crate::miniscript::types::{self, ExtData, Property, Type};
506+
use crate::miniscript::types;
497507
use crate::miniscript::Terminal;
498508
use crate::policy::Liftable;
499509
use crate::{prelude::*, Error};
@@ -679,32 +689,18 @@ mod tests {
679689
.unwrap();
680690
let hash = hash160::Hash::from_inner([17; 20]);
681691

682-
let pkk_ms: Miniscript<DummyKey, Segwitv0> = Miniscript {
683-
node: Terminal::Check(Arc::new(Miniscript {
684-
node: Terminal::PkK(DummyKey),
685-
ty: Type::from_pk_k::<Segwitv0>(),
686-
ext: types::extra_props::ExtData::from_pk_k::<Segwitv0>(),
687-
phantom: PhantomData,
688-
})),
689-
ty: Type::cast_check(Type::from_pk_k::<Segwitv0>()).unwrap(),
690-
ext: ExtData::cast_check(ExtData::from_pk_k::<Segwitv0>()).unwrap(),
691-
phantom: PhantomData,
692-
};
692+
let pk_node = Terminal::Check(Arc::new(
693+
Miniscript::from_ast(Terminal::PkK(DummyKey)).unwrap(),
694+
));
695+
let pkk_ms: Miniscript<DummyKey, Segwitv0> = Miniscript::from_ast(pk_node).unwrap();
693696
dummy_string_rtt(pkk_ms, "[B/onduesm]c:[K/onduesm]pk_k(DummyKey)", "pk()");
694697

695-
let pkh_ms: Miniscript<DummyKey, Segwitv0> = Miniscript {
696-
node: Terminal::Check(Arc::new(Miniscript {
697-
node: Terminal::PkH(DummyKey),
698-
ty: Type::from_pk_h::<Segwitv0>(),
699-
ext: types::extra_props::ExtData::from_pk_h::<Segwitv0>(),
700-
phantom: PhantomData,
701-
})),
702-
ty: Type::cast_check(Type::from_pk_h::<Segwitv0>()).unwrap(),
703-
ext: ExtData::cast_check(ExtData::from_pk_h::<Segwitv0>()).unwrap(),
704-
phantom: PhantomData,
705-
};
698+
let pkh_node = Terminal::Check(Arc::new(
699+
Miniscript::from_ast(Terminal::PkH(String::from(""))).unwrap(),
700+
));
701+
let pkh_ms: Miniscript<String, Segwitv0> = Miniscript::from_ast(pkh_node).unwrap();
706702

707-
let expected_debug = "[B/nduesm]c:[K/nduesm]pk_h(DummyKey)";
703+
let expected_debug = "[B/nduesm]c:[K/nduesm]pk_h(\"\")";
708704
let expected_display = "pkh()";
709705

710706
assert_eq!(pkh_ms.ty.corr.base, types::Base::B);
@@ -717,35 +713,19 @@ mod tests {
717713
assert_eq!(display, expected);
718714
}
719715

720-
let pkk_ms: Segwitv0Script = Miniscript {
721-
node: Terminal::Check(Arc::new(Miniscript {
722-
node: Terminal::PkK(pk),
723-
ty: Type::from_pk_k::<Segwitv0>(),
724-
ext: types::extra_props::ExtData::from_pk_k::<Segwitv0>(),
725-
phantom: PhantomData,
726-
})),
727-
ty: Type::cast_check(Type::from_pk_k::<Segwitv0>()).unwrap(),
728-
ext: ExtData::cast_check(ExtData::from_pk_k::<Segwitv0>()).unwrap(),
729-
phantom: PhantomData,
730-
};
716+
let pkk_node = Terminal::Check(Arc::new(Miniscript::from_ast(Terminal::PkK(pk)).unwrap()));
717+
let pkk_ms: Segwitv0Script = Miniscript::from_ast(pkk_node).unwrap();
731718

732719
script_rtt(
733720
pkk_ms,
734721
"21020202020202020202020202020202020202020202020202020202020\
735722
202020202ac",
736723
);
737724

738-
let pkh_ms: Segwitv0Script = Miniscript {
739-
node: Terminal::Check(Arc::new(Miniscript {
740-
node: Terminal::RawPkH(hash),
741-
ty: Type::from_pk_h::<Segwitv0>(),
742-
ext: types::extra_props::ExtData::from_pk_h::<Segwitv0>(),
743-
phantom: PhantomData,
744-
})),
745-
ty: Type::cast_check(Type::from_pk_h::<Segwitv0>()).unwrap(),
746-
ext: ExtData::cast_check(ExtData::from_pk_h::<Segwitv0>()).unwrap(),
747-
phantom: PhantomData,
748-
};
725+
let pkh_ms: Segwitv0Script = Miniscript::from_ast(Terminal::Check(Arc::new(
726+
Miniscript::from_ast(Terminal::RawPkH(hash)).unwrap(),
727+
)))
728+
.unwrap();
749729

750730
script_rtt(pkh_ms, "76a914111111111111111111111111111111111111111188ac");
751731
}
@@ -1160,4 +1140,13 @@ mod tests {
11601140
panic!("Unexpected error: {:?}", err);
11611141
}
11621142
}
1143+
1144+
#[test]
1145+
fn test_script_parse_dos() {
1146+
let mut script = bitcoin::blockdata::script::Builder::new().push_opcode(bitcoin::blockdata::opcodes::OP_TRUE);
1147+
for _ in 0..10000 {
1148+
script = script.push_opcode(bitcoin::blockdata::opcodes::all::OP_0NOTEQUAL);
1149+
}
1150+
Tapscript::parse_insane(&script.into_script()).unwrap_err();
1151+
}
11631152
}

0 commit comments

Comments
 (0)