Skip to content

Commit 125b059

Browse files
committed
Add parse/parse_insane for schnorr keys
1 parent eb4319c commit 125b059

File tree

4 files changed

+112
-15
lines changed

4 files changed

+112
-15
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: 107 additions & 11 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;
@@ -42,6 +42,7 @@ pub mod limits;
4242
pub mod satisfy;
4343
pub mod types;
4444

45+
use self::decode::ParseableKey;
4546
use self::lex::{lex, TokenIter};
4647
use self::types::Property;
4748
pub use miniscript::context::ScriptContext;
@@ -137,17 +138,19 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
137138
}
138139
}
139140

140-
impl<Ctx: ScriptContext> Miniscript<bitcoin::PublicKey, Ctx> {
141+
impl<Pk, Ctx: ScriptContext> Miniscript<Pk, Ctx>
142+
where
143+
Pk: ParseableKey + MiniscriptKey<Hash = bitcoin::hashes::hash160::Hash>,
144+
{
141145
/// Attempt to parse an insane(scripts don't clear sanity checks)
142146
/// script into a Miniscript representation.
143147
/// Use this to parse scripts with repeated pubkeys, timelock mixing, malleable
144148
/// scripts without sig or scripts that can exceed resource limits.
145149
/// Some of the analysis guarantees of miniscript are lost when dealing with
146150
/// insane scripts. In general, in a multi-party setting users should only
147151
/// accept sane scripts.
148-
pub fn parse_insane(
149-
script: &script::Script,
150-
) -> Result<Miniscript<bitcoin::PublicKey, Ctx>, Error> {
152+
/// This function can be used any key that follow the `[decode::ParseableKey]` trait
153+
pub fn parse_insane(script: &script::Script) -> Result<Miniscript<Pk, Ctx>, Error> {
151154
let tokens = lex(script)?;
152155
let mut iter = TokenIter::new(tokens);
153156

@@ -168,7 +171,38 @@ impl<Ctx: ScriptContext> Miniscript<bitcoin::PublicKey, Ctx> {
168171
/// This function will fail parsing for scripts that do not clear
169172
/// the [Miniscript::sanity_check] checks. Use [Miniscript::parse_insane] to
170173
/// parse such scripts.
171-
pub fn parse(script: &script::Script) -> Result<Miniscript<bitcoin::PublicKey, Ctx>, Error> {
174+
/// ## Decode/Parse a miniscript from script hex
175+
///
176+
/// ```rust
177+
/// extern crate bitcoin;
178+
/// extern crate miniscript;
179+
///
180+
/// use miniscript::Miniscript;
181+
/// use miniscript::{Segwitv0, Tap};
182+
/// type XonlyKey = bitcoin::schnorr::PublicKey;
183+
/// type Segwitv0Script = Miniscript<bitcoin::PublicKey, Segwitv0>;
184+
/// type TapScript = Miniscript<XonlyKey, Tap>;
185+
/// use bitcoin::hashes::hex::FromHex;
186+
/// fn main() {
187+
/// // parse x-only miniscript in Taproot context
188+
/// let tapscript_ms = TapScript::parse_insane(&bitcoin::Script::from(Vec::<u8>::from_hex(
189+
/// "202788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99ac",
190+
/// ).expect("Even length hex")))
191+
/// .expect("Xonly keys are valid only in taproot context");
192+
/// // tapscript fails decoding when we use them with compressed keys
193+
/// let err = TapScript::parse_insane(&bitcoin::Script::from(Vec::<u8>::from_hex(
194+
/// "21022788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99ac",
195+
/// ).expect("Even length hex")))
196+
/// .expect_err("Compressed keys cannot be used in Taproot context");
197+
/// // Segwitv0 succeeds decoding with full keys.
198+
/// Segwitv0Script::parse_insane(&bitcoin::Script::from(Vec::<u8>::from_hex(
199+
/// "21022788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99ac",
200+
/// ).expect("Even length hex")))
201+
/// .expect("Compressed keys are allowed in Segwit context");
202+
///
203+
/// }
204+
/// ```
205+
pub fn parse(script: &script::Script) -> Result<Miniscript<Pk, Ctx>, Error> {
172206
let ms = Self::parse_insane(script)?;
173207
ms.sanity_check()?;
174208
Ok(ms)
@@ -417,8 +451,8 @@ serde_string_impl_pk!(Miniscript, "a miniscript", Ctx; ScriptContext);
417451

418452
#[cfg(test)]
419453
mod tests {
420-
use super::Segwitv0;
421454
use super::{Miniscript, ScriptContext};
455+
use super::{Segwitv0, Tap};
422456
use hex_script;
423457
use miniscript::types::{self, ExtData, Property, Type};
424458
use miniscript::Terminal;
@@ -433,6 +467,7 @@ mod tests {
433467
use std::sync::Arc;
434468

435469
type Segwitv0Script = Miniscript<bitcoin::PublicKey, Segwitv0>;
470+
type Tapscript = Miniscript<bitcoin::schnorr::PublicKey, Tap>;
436471

437472
fn pubkeys(n: usize) -> Vec<bitcoin::PublicKey> {
438473
let mut ret = Vec::with_capacity(n);
@@ -669,19 +704,19 @@ mod tests {
669704
fn verify_parse() {
670705
let ms = "and_v(v:hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))";
671706
let ms: Segwitv0Script = Miniscript::from_str_insane(ms).unwrap();
672-
assert_eq!(ms, Miniscript::parse_insane(&ms.encode()).unwrap());
707+
assert_eq!(ms, Segwitv0Script::parse_insane(&ms.encode()).unwrap());
673708

674709
let ms = "and_v(v:sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))";
675710
let ms: Segwitv0Script = Miniscript::from_str_insane(ms).unwrap();
676-
assert_eq!(ms, Miniscript::parse_insane(&ms.encode()).unwrap());
711+
assert_eq!(ms, Segwitv0Script::parse_insane(&ms.encode()).unwrap());
677712

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

682717
let ms = "and_v(v:hash256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))";
683718
let ms: Segwitv0Script = Miniscript::from_str_insane(ms).unwrap();
684-
assert_eq!(ms, Miniscript::parse_insane(&ms.encode()).unwrap());
719+
assert_eq!(ms, Segwitv0Script::parse_insane(&ms.encode()).unwrap());
685720
}
686721

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

0 commit comments

Comments
 (0)