Skip to content

Commit 761a81f

Browse files
committed
Add parse/parse_insane for schnorr keys
1 parent 754b635 commit 761a81f

File tree

4 files changed

+113
-10
lines changed

4 files changed

+113
-10
lines changed

fuzz/fuzz_targets/roundtrip_miniscript_script.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ fn do_test(data: &[u8]) {
88
// Try round-tripping as a script
99
let script = script::Script::from(data.to_owned());
1010

11-
if let Ok(pt) = Miniscript::<_, Segwitv0>::parse(&script) {
11+
if let Ok(pt) = Miniscript::<miniscript::bitcoin::PublicKey, Segwitv0>::parse(&script) {
1212
let output = pt.encode();
1313
assert_eq!(pt.script_size(), output.len());
1414
assert_eq!(output, script);

src/interpreter/inner.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ fn script_from_stackelem<'a>(
5050
) -> Result<Miniscript<bitcoin::PublicKey, NoChecks>, Error> {
5151
match *elem {
5252
stack::Element::Push(sl) => {
53-
Miniscript::parse_insane(&bitcoin::Script::from(sl.to_owned())).map_err(Error::from)
53+
Miniscript::<bitcoin::PublicKey, _>::parse_insane(&bitcoin::Script::from(sl.to_owned()))
54+
.map_err(Error::from)
5455
}
5556
stack::Element::Satisfied => Miniscript::from_ast(::Terminal::True).map_err(Error::from),
5657
stack::Element::Dissatisfied => {
@@ -270,7 +271,7 @@ pub fn from_txdata<'txin>(
270271
// ** bare script **
271272
} else {
272273
if wit_stack.is_empty() {
273-
let miniscript = Miniscript::parse_insane(spk)?;
274+
let miniscript = Miniscript::<bitcoin::PublicKey, _>::parse_insane(spk)?;
274275
Ok((
275276
Inner::Script(miniscript, ScriptType::Bare),
276277
ssig_stack,

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ use bitcoin::hashes::{hash160, sha256, Hash};
124124

125125
pub use descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
126126
pub use interpreter::Interpreter;
127-
pub use miniscript::context::{BareCtx, Legacy, ScriptContext, Segwitv0};
127+
pub use miniscript::context::{BareCtx, Legacy, ScriptContext, Segwitv0, Tap};
128128
pub use miniscript::decode::Terminal;
129129
pub use miniscript::satisfy::{BitcoinSig, Preimage32, Satisfier};
130130
pub use miniscript::Miniscript;

src/miniscript/mod.rs

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use std::{fmt, str};
3030
use bitcoin;
3131
use bitcoin::blockdata::script;
3232

33-
pub use self::context::{BareCtx, Legacy, Segwitv0};
33+
pub use self::context::{BareCtx, Legacy, Segwitv0, Tap};
3434

3535
pub mod analyzable;
3636
pub mod astelem;
@@ -175,6 +175,46 @@ impl<Ctx: ScriptContext> Miniscript<bitcoin::PublicKey, Ctx> {
175175
}
176176
}
177177

178+
impl<Ctx: ScriptContext> Miniscript<bitcoin::schnorr::PublicKey, Ctx> {
179+
/// Attempt to parse an insane(scripts don't clear sanity checks)
180+
/// script into a Miniscript representation.
181+
/// Use this to parse scripts with repeated pubkeys, timelock mixing, malleable
182+
/// scripts without sig or scripts that can exceed resource limits.
183+
/// Some of the analysis guarantees of miniscript are lost when dealing with
184+
/// insane scripts. In general, in a multi-party setting users should only
185+
/// accept sane scripts.
186+
pub fn parse_insane(
187+
script: &script::Script,
188+
) -> Result<Miniscript<bitcoin::schnorr::PublicKey, Ctx>, Error> {
189+
let tokens = lex(script)?;
190+
let mut iter = TokenIter::new(tokens);
191+
192+
let top = decode::parse_tapscript(&mut iter)?;
193+
Ctx::check_global_validity(&top)?;
194+
let type_check = types::Type::type_check(&top.node, |_| None)?;
195+
if type_check.corr.base != types::Base::B {
196+
return Err(Error::NonTopLevel(format!("{:?}", top)));
197+
};
198+
if let Some(leading) = iter.next() {
199+
Err(Error::Trailing(leading.to_string()))
200+
} else {
201+
Ok(top)
202+
}
203+
}
204+
205+
/// Attempt to parse a Script into Miniscript representation.
206+
/// This function will fail parsing for scripts that do not clear
207+
/// the [Miniscript::sanity_check] checks. Use [Miniscript::parse_insane] to
208+
/// parse such scripts.
209+
pub fn parse(
210+
script: &script::Script,
211+
) -> Result<Miniscript<bitcoin::schnorr::PublicKey, Ctx>, Error> {
212+
let ms = Self::parse_insane(script)?;
213+
ms.sanity_check()?;
214+
Ok(ms)
215+
}
216+
}
217+
178218
impl<Pk, Ctx> Miniscript<Pk, Ctx>
179219
where
180220
Pk: MiniscriptKey,
@@ -417,8 +457,8 @@ serde_string_impl_pk!(Miniscript, "a miniscript", Ctx; ScriptContext);
417457

418458
#[cfg(test)]
419459
mod tests {
420-
use super::Segwitv0;
421460
use super::{Miniscript, ScriptContext};
461+
use super::{Segwitv0, Tap};
422462
use hex_script;
423463
use miniscript::types::{self, ExtData, Property, Type};
424464
use miniscript::Terminal;
@@ -433,6 +473,7 @@ mod tests {
433473
use std::sync::Arc;
434474

435475
type Segwitv0Script = Miniscript<bitcoin::PublicKey, Segwitv0>;
476+
type Tapscript = Miniscript<bitcoin::schnorr::PublicKey, Tap>;
436477

437478
fn pubkeys(n: usize) -> Vec<bitcoin::PublicKey> {
438479
let mut ret = Vec::with_capacity(n);
@@ -669,19 +710,19 @@ mod tests {
669710
fn verify_parse() {
670711
let ms = "and_v(v:hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))";
671712
let ms: Segwitv0Script = Miniscript::from_str_insane(ms).unwrap();
672-
assert_eq!(ms, Miniscript::parse_insane(&ms.encode()).unwrap());
713+
assert_eq!(ms, Segwitv0Script::parse_insane(&ms.encode()).unwrap());
673714

674715
let ms = "and_v(v:sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))";
675716
let ms: Segwitv0Script = Miniscript::from_str_insane(ms).unwrap();
676-
assert_eq!(ms, Miniscript::parse_insane(&ms.encode()).unwrap());
717+
assert_eq!(ms, Segwitv0Script::parse_insane(&ms.encode()).unwrap());
677718

678719
let ms = "and_v(v:ripemd160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))";
679720
let ms: Segwitv0Script = Miniscript::from_str_insane(ms).unwrap();
680-
assert_eq!(ms, Miniscript::parse_insane(&ms.encode()).unwrap());
721+
assert_eq!(ms, Segwitv0Script::parse_insane(&ms.encode()).unwrap());
681722

682723
let ms = "and_v(v:hash256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))";
683724
let ms: Segwitv0Script = Miniscript::from_str_insane(ms).unwrap();
684-
assert_eq!(ms, Miniscript::parse_insane(&ms.encode()).unwrap());
725+
assert_eq!(ms, Segwitv0Script::parse_insane(&ms.encode()).unwrap());
685726
}
686727

687728
#[test]
@@ -907,4 +948,65 @@ mod tests {
907948
.to_string()
908949
.contains("unprintable character"));
909950
}
951+
952+
#[test]
953+
fn test_tapscript_rtt() {
954+
// Test x-only invalid under segwitc0 context
955+
let ms = Segwitv0Script::from_str_insane(&format!(
956+
"pk(2788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99)"
957+
));
958+
assert_eq!(
959+
ms.unwrap_err().to_string(),
960+
"unexpected «Key secp256k1 error: secp: malformed public key»"
961+
);
962+
Tapscript::from_str_insane(&format!(
963+
"pk(2788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99)"
964+
))
965+
.unwrap();
966+
967+
// Now test that bitcoin::PublicKey works with Taproot context
968+
Miniscript::<bitcoin::PublicKey, Tap>::from_str_insane(&format!(
969+
"pk(022788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99)"
970+
))
971+
.unwrap();
972+
973+
// uncompressed keys should not be allowed
974+
Miniscript::<bitcoin::PublicKey, Tap>::from_str_insane(&format!(
975+
"pk(04eed24a081bf1b1e49e3300df4bebe04208ac7e516b6f3ea8eb6e094584267c13483f89dcf194132e12238cc5a34b6b286fc7990d68ed1db86b69ebd826c63b29)"
976+
))
977+
.unwrap_err();
978+
979+
//---------------- test script <-> miniscript ---------------
980+
// Test parsing from scripts: x-only fails decoding in segwitv0 ctx
981+
Segwitv0Script::parse_insane(&hex_script(
982+
"202788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99ac",
983+
))
984+
.unwrap_err();
985+
// x-only succeeds in tap ctx
986+
Tapscript::parse_insane(&hex_script(
987+
"202788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99ac",
988+
))
989+
.unwrap();
990+
// tapscript fails decoding with compressed
991+
Tapscript::parse_insane(&hex_script(
992+
"21022788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99ac",
993+
))
994+
.unwrap_err();
995+
// Segwitv0 succeeds decoding with tapscript.
996+
Segwitv0Script::parse_insane(&hex_script(
997+
"21022788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99ac",
998+
))
999+
.unwrap();
1000+
1001+
// multi not allowed in tapscript
1002+
Tapscript::from_str_insane(&format!(
1003+
"multi(1,2788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99)"
1004+
))
1005+
.unwrap_err();
1006+
// but allowed in segwit
1007+
Segwitv0Script::from_str_insane(&format!(
1008+
"multi(1,022788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99)"
1009+
))
1010+
.unwrap();
1011+
}
9101012
}

0 commit comments

Comments
 (0)