Skip to content

Commit 3eb63d5

Browse files
committed
Add parse/parse_insane for schnorr keys
1 parent 888cd62 commit 3eb63d5

File tree

4 files changed

+105
-10
lines changed

4 files changed

+105
-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: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use std::{fmt, str};
2929

3030
use bitcoin::blockdata::script;
3131

32-
pub use self::context::{BareCtx, Legacy, Segwitv0};
32+
pub use self::context::{BareCtx, Legacy, Segwitv0, Tap};
3333

3434
pub mod analyzable;
3535
pub mod astelem;
@@ -167,6 +167,38 @@ impl<Ctx: ScriptContext> Miniscript<Ctx::Key, Ctx> {
167167
/// This function will fail parsing for scripts that do not clear
168168
/// the [Miniscript::sanity_check] checks. Use [Miniscript::parse_insane] to
169169
/// parse such scripts.
170+
///
171+
/// ## Decode/Parse a miniscript from script hex
172+
///
173+
/// ```rust
174+
/// extern crate bitcoin;
175+
/// extern crate miniscript;
176+
///
177+
/// use miniscript::Miniscript;
178+
/// use miniscript::{Segwitv0, Tap};
179+
/// type XonlyKey = bitcoin::schnorr::PublicKey;
180+
/// type Segwitv0Script = Miniscript<bitcoin::PublicKey, Segwitv0>;
181+
/// type TapScript = Miniscript<XonlyKey, Tap>;
182+
/// use bitcoin::hashes::hex::FromHex;
183+
/// fn main() {
184+
/// // parse x-only miniscript in Taproot context
185+
/// let tapscript_ms = TapScript::parse(&bitcoin::Script::from(Vec::<u8>::from_hex(
186+
/// "202788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99ac",
187+
/// ).expect("Even length hex")))
188+
/// .expect("Xonly keys are valid only in taproot context");
189+
/// // tapscript fails decoding when we use them with compressed keys
190+
/// let err = TapScript::parse(&bitcoin::Script::from(Vec::<u8>::from_hex(
191+
/// "21022788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99ac",
192+
/// ).expect("Even length hex")))
193+
/// .expect_err("Compressed keys cannot be used in Taproot context");
194+
/// // Segwitv0 succeeds decoding with full keys.
195+
/// Segwitv0Script::parse(&bitcoin::Script::from(Vec::<u8>::from_hex(
196+
/// "21022788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99ac",
197+
/// ).expect("Even length hex")))
198+
/// .expect("Compressed keys are allowed in Segwit context");
199+
///
200+
/// }
201+
/// ```
170202
pub fn parse(script: &script::Script) -> Result<Miniscript<Ctx::Key, Ctx>, Error> {
171203
let ms = Self::parse_insane(script)?;
172204
ms.sanity_check()?;
@@ -416,8 +448,8 @@ serde_string_impl_pk!(Miniscript, "a miniscript", Ctx; ScriptContext);
416448

417449
#[cfg(test)]
418450
mod tests {
419-
use super::Segwitv0;
420451
use super::{Miniscript, ScriptContext};
452+
use super::{Segwitv0, Tap};
421453
use hex_script;
422454
use miniscript::types::{self, ExtData, Property, Type};
423455
use miniscript::Terminal;
@@ -432,6 +464,7 @@ mod tests {
432464
use std::sync::Arc;
433465

434466
type Segwitv0Script = Miniscript<bitcoin::PublicKey, Segwitv0>;
467+
type Tapscript = Miniscript<bitcoin::schnorr::PublicKey, Tap>;
435468

436469
fn pubkeys(n: usize) -> Vec<bitcoin::PublicKey> {
437470
let mut ret = Vec::with_capacity(n);
@@ -668,19 +701,19 @@ mod tests {
668701
fn verify_parse() {
669702
let ms = "and_v(v:hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))";
670703
let ms: Segwitv0Script = Miniscript::from_str_insane(ms).unwrap();
671-
assert_eq!(ms, Miniscript::parse_insane(&ms.encode()).unwrap());
704+
assert_eq!(ms, Segwitv0Script::parse_insane(&ms.encode()).unwrap());
672705

673706
let ms = "and_v(v:sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))";
674707
let ms: Segwitv0Script = Miniscript::from_str_insane(ms).unwrap();
675-
assert_eq!(ms, Miniscript::parse_insane(&ms.encode()).unwrap());
708+
assert_eq!(ms, Segwitv0Script::parse_insane(&ms.encode()).unwrap());
676709

677710
let ms = "and_v(v:ripemd160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))";
678711
let ms: Segwitv0Script = Miniscript::from_str_insane(ms).unwrap();
679-
assert_eq!(ms, Miniscript::parse_insane(&ms.encode()).unwrap());
712+
assert_eq!(ms, Segwitv0Script::parse_insane(&ms.encode()).unwrap());
680713

681714
let ms = "and_v(v:hash256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))";
682715
let ms: Segwitv0Script = Miniscript::from_str_insane(ms).unwrap();
683-
assert_eq!(ms, Miniscript::parse_insane(&ms.encode()).unwrap());
716+
assert_eq!(ms, Segwitv0Script::parse_insane(&ms.encode()).unwrap());
684717
}
685718

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

0 commit comments

Comments
 (0)