Skip to content

Commit b9f0aa3

Browse files
committed
Add support for parsing Tapscript Miniscripts
1 parent 50a25de commit b9f0aa3

File tree

4 files changed

+147
-59
lines changed

4 files changed

+147
-59
lines changed

src/lib.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,14 @@ impl MiniscriptKey for bitcoin::PublicKey {
159159
}
160160
}
161161

162+
impl MiniscriptKey for bitcoin::schnorr::PublicKey {
163+
type Hash = hash160::Hash;
164+
165+
fn to_pubkeyhash(&self) -> Self::Hash {
166+
hash160::Hash::hash(&self.serialize())
167+
}
168+
}
169+
162170
impl MiniscriptKey for String {
163171
type Hash = String;
164172

@@ -206,6 +214,20 @@ impl ToPublicKey for bitcoin::PublicKey {
206214
}
207215
}
208216

217+
impl ToPublicKey for bitcoin::schnorr::PublicKey {
218+
fn to_public_key(&self) -> bitcoin::PublicKey {
219+
// This code should never be used.
220+
// But is implemented for completeness
221+
let mut data: Vec<u8> = vec![0x02];
222+
data.extend(self.serialize().iter());
223+
bitcoin::PublicKey::from_slice(&data).expect("Cannot fail")
224+
}
225+
226+
fn hash_to_hash160(hash: &hash160::Hash) -> hash160::Hash {
227+
*hash
228+
}
229+
}
230+
209231
/// Dummy key which de/serializes to the empty string; useful sometimes for testing
210232
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug)]
211233
pub struct DummyKey;
@@ -452,7 +474,7 @@ pub enum Error {
452474
InvalidOpcode(opcodes::All),
453475
/// Some opcode occurred followed by `OP_VERIFY` when it had
454476
/// a `VERIFY` version that should have been used instead
455-
NonMinimalVerify(miniscript::lex::Token),
477+
NonMinimalVerify(String),
456478
/// Push was illegal in some context
457479
InvalidPush(Vec<u8>),
458480
/// rust-bitcoin script error
@@ -523,6 +545,8 @@ pub enum Error {
523545
ImpossibleSatisfaction,
524546
/// Bare descriptors don't have any addresses
525547
BareDescriptorAddr,
548+
/// PubKey invalid under current context
549+
PubKeyCtxError(String, String),
526550
}
527551

528552
#[doc(hidden)]
@@ -584,7 +608,7 @@ impl fmt::Display for Error {
584608
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
585609
match *self {
586610
Error::InvalidOpcode(op) => write!(f, "invalid opcode {}", op),
587-
Error::NonMinimalVerify(tok) => write!(f, "{} VERIFY", tok),
611+
Error::NonMinimalVerify(ref tok) => write!(f, "{} VERIFY", tok),
588612
Error::InvalidPush(ref push) => write!(f, "invalid push {:?}", push), // TODO hexify this
589613
Error::Script(ref e) => fmt::Display::fmt(e, f),
590614
Error::CmsTooManyKeys(n) => write!(f, "checkmultisig with {} keys", n),
@@ -637,6 +661,9 @@ impl fmt::Display for Error {
637661
Error::AnalysisError(ref e) => e.fmt(f),
638662
Error::ImpossibleSatisfaction => write!(f, "Impossible to satisfy Miniscript"),
639663
Error::BareDescriptorAddr => write!(f, "Bare descriptors don't have address"),
664+
Error::PubKeyCtxError(ref pk, ref ctx) => {
665+
write!(f, "Pubkey error: {} under {} scriptcontext", pk, ctx)
666+
}
640667
}
641668
}
642669
}

src/miniscript/context.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ pub trait ScriptContext:
276276
/// 34/66 for Bare/Legacy based on key compressedness
277277
/// 34 for Segwitv0, 33 for Tap
278278
fn pk_len<Pk: MiniscriptKey>(pk: &Pk) -> usize;
279+
/// Local helper function to display error messages with context
280+
fn to_string() -> String;
279281
}
280282

281283
/// Legacy ScriptContext
@@ -358,6 +360,9 @@ impl ScriptContext for Legacy {
358360
34
359361
}
360362
}
363+
fn to_string() -> String {
364+
String::from("Legacy/p2sh")
365+
}
361366
}
362367

363368
/// Segwitv0 ScriptContext
@@ -451,6 +456,9 @@ impl ScriptContext for Segwitv0 {
451456
fn pk_len<Pk: MiniscriptKey>(_pk: &Pk) -> usize {
452457
34
453458
}
459+
fn to_string() -> String {
460+
String::from("Segwitv0")
461+
}
454462
}
455463

456464
/// Tap ScriptContext
@@ -546,6 +554,9 @@ impl ScriptContext for Tap {
546554
fn pk_len<Pk: MiniscriptKey>(_pk: &Pk) -> usize {
547555
33
548556
}
557+
fn to_string() -> String {
558+
String::from("TapscriptCtx")
559+
}
549560
}
550561

551562
/// Bare ScriptContext
@@ -615,6 +626,9 @@ impl ScriptContext for BareCtx {
615626
33
616627
}
617628
}
629+
fn to_string() -> String {
630+
String::from("BareCtx")
631+
}
618632
}
619633

620634
/// "No Checks" Context
@@ -664,6 +678,10 @@ impl ScriptContext for NoChecks {
664678
fn pk_len<Pk: MiniscriptKey>(_pk: &Pk) -> usize {
665679
panic!("Tried to compute a pk len bound on a no-checks miniscript")
666680
}
681+
fn to_string() -> String {
682+
// Internally used code
683+
String::from("NochecksCtx")
684+
}
667685
}
668686

669687
/// Private Mod to prevent downstream from implementing this public trait

src/miniscript/decode.rs

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
2020
use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash};
2121
use std::marker::PhantomData;
22+
use std::{error, fmt};
2223
use {bitcoin, Miniscript};
2324

2425
use miniscript::lex::{Token as Tk, TokenIter};
@@ -34,6 +35,44 @@ fn return_none<T>(_: usize) -> Option<T> {
3435
None
3536
}
3637

38+
/// Trait for parsing keys from byte slices
39+
pub trait ParseableKey: Sized {
40+
/// Parse a key from slice
41+
fn from_slice(sl: &[u8]) -> Result<Self, KeyParseError>;
42+
}
43+
44+
/// Decoding error while parsing keys
45+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
46+
pub enum KeyParseError {
47+
/// Bitcoin PublicKey parse error
48+
FullKeyParseError(bitcoin::util::key::Error),
49+
/// Wrong Input Count
50+
XonlyKeyParseError(bitcoin::secp256k1::Error),
51+
}
52+
53+
impl ParseableKey for bitcoin::PublicKey {
54+
fn from_slice(sl: &[u8]) -> Result<Self, KeyParseError> {
55+
bitcoin::PublicKey::from_slice(sl).map_err(KeyParseError::FullKeyParseError)
56+
}
57+
}
58+
59+
impl ParseableKey for bitcoin::schnorr::PublicKey {
60+
fn from_slice(sl: &[u8]) -> Result<Self, KeyParseError> {
61+
bitcoin::schnorr::PublicKey::from_slice(sl).map_err(KeyParseError::XonlyKeyParseError)
62+
}
63+
}
64+
65+
impl error::Error for KeyParseError {}
66+
67+
impl fmt::Display for KeyParseError {
68+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69+
match self {
70+
KeyParseError::FullKeyParseError(e) => write!(f, "FullKey Parse Error {}", e),
71+
KeyParseError::XonlyKeyParseError(e) => write!(f, "XonlyKey Parse Error {}", e),
72+
}
73+
}
74+
}
75+
3776
#[derive(Copy, Clone, Debug)]
3877
enum NonTerm {
3978
Expression,
@@ -214,11 +253,12 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> TerminalStack<Pk, Ctx> {
214253
}
215254
}
216255

217-
/// Parse a script fragment into an `Terminal`
256+
/// Parse a script fragment into an `Miniscript`
218257
#[allow(unreachable_patterns)]
219-
pub fn parse<Ctx: ScriptContext>(
220-
tokens: &mut TokenIter,
221-
) -> Result<Miniscript<bitcoin::PublicKey, Ctx>, Error> {
258+
pub fn parse<Ctx: ScriptContext, Pk>(tokens: &mut TokenIter) -> Result<Miniscript<Pk, Ctx>, Error>
259+
where
260+
Pk: ParseableKey + MiniscriptKey<Hash = bitcoin::hashes::hash160::Hash>,
261+
{
222262
let mut non_term = Vec::with_capacity(tokens.len());
223263
let mut term = TerminalStack(Vec::with_capacity(tokens.len()));
224264

@@ -231,7 +271,23 @@ pub fn parse<Ctx: ScriptContext>(
231271
match_token!(
232272
tokens,
233273
// pubkey
234-
Tk::Pubkey(pk) => term.reduce0(Terminal::PkK(pk))?,
274+
Tk::Bytes33(pk) => {
275+
let ret = Pk::from_slice(pk)
276+
.map_err(|e| Error::PubKeyCtxError(e.to_string(), Ctx::to_string()))?;
277+
term.reduce0(Terminal::PkK(ret))?
278+
},
279+
Tk::Bytes65(pk) => {
280+
let ret = Pk::from_slice(pk)
281+
.map_err(|e| Error::PubKeyCtxError(e.to_string(), Ctx::to_string()))?;
282+
term.reduce0(Terminal::PkK(ret))?
283+
},
284+
// Note this does not collide with hash32 because they always followed by equal
285+
// and would be parsed in different branch. If we get a naked Bytes32, it must be
286+
// a x-only key
287+
Tk::Bytes32(pk) => {
288+
let ret = Pk::from_slice(pk).map_err(|e| Error::PubKeyCtxError(e.to_string(), Ctx::to_string()))?;
289+
term.reduce0(Terminal::PkK(ret))?
290+
},
235291
// checksig
236292
Tk::CheckSig => {
237293
non_term.push(NonTerm::Check);
@@ -248,36 +304,36 @@ pub fn parse<Ctx: ScriptContext>(
248304
tokens,
249305
Tk::Dup => {
250306
term.reduce0(Terminal::PkH(
251-
hash160::Hash::from_inner(hash)
307+
hash160::Hash::from_slice(hash).expect("valid size")
252308
))?
253309
},
254310
Tk::Verify, Tk::Equal, Tk::Num(32), Tk::Size => {
255311
non_term.push(NonTerm::Verify);
256312
term.reduce0(Terminal::Hash160(
257-
hash160::Hash::from_inner(hash)
313+
hash160::Hash::from_slice(hash).expect("valid size")
258314
))?
259315
},
260316
),
261317
Tk::Ripemd160, Tk::Verify, Tk::Equal, Tk::Num(32), Tk::Size => {
262318
non_term.push(NonTerm::Verify);
263319
term.reduce0(Terminal::Ripemd160(
264-
ripemd160::Hash::from_inner(hash)
320+
ripemd160::Hash::from_slice(hash).expect("valid size")
265321
))?
266322
},
267323
),
268324
// Tk::Hash20(hash),
269-
Tk::Hash32(hash) => match_token!(
325+
Tk::Bytes32(hash) => match_token!(
270326
tokens,
271327
Tk::Sha256, Tk::Verify, Tk::Equal, Tk::Num(32), Tk::Size => {
272328
non_term.push(NonTerm::Verify);
273329
term.reduce0(Terminal::Sha256(
274-
sha256::Hash::from_inner(hash)
330+
sha256::Hash::from_slice(hash).expect("valid size")
275331
))?
276332
},
277333
Tk::Hash256, Tk::Verify, Tk::Equal, Tk::Num(32), Tk::Size => {
278334
non_term.push(NonTerm::Verify);
279335
term.reduce0(Terminal::Hash256(
280-
sha256d::Hash::from_inner(hash)
336+
sha256d::Hash::from_slice(hash).expect("valid size")
281337
))?
282338
},
283339
),
@@ -307,21 +363,21 @@ pub fn parse<Ctx: ScriptContext>(
307363
// hashlocks
308364
Tk::Equal => match_token!(
309365
tokens,
310-
Tk::Hash32(hash) => match_token!(
366+
Tk::Bytes32(hash) => match_token!(
311367
tokens,
312368
Tk::Sha256,
313369
Tk::Verify,
314370
Tk::Equal,
315371
Tk::Num(32),
316372
Tk::Size => term.reduce0(Terminal::Sha256(
317-
sha256::Hash::from_inner(hash)
373+
sha256::Hash::from_slice(hash).expect("valid size")
318374
))?,
319375
Tk::Hash256,
320376
Tk::Verify,
321377
Tk::Equal,
322378
Tk::Num(32),
323379
Tk::Size => term.reduce0(Terminal::Hash256(
324-
sha256d::Hash::from_inner(hash)
380+
sha256d::Hash::from_slice(hash).expect("valid size")
325381
))?,
326382
),
327383
Tk::Hash20(hash) => match_token!(
@@ -331,14 +387,14 @@ pub fn parse<Ctx: ScriptContext>(
331387
Tk::Equal,
332388
Tk::Num(32),
333389
Tk::Size => term.reduce0(Terminal::Ripemd160(
334-
ripemd160::Hash::from_inner(hash)
390+
ripemd160::Hash::from_slice(hash).expect("valid size")
335391
))?,
336392
Tk::Hash160,
337393
Tk::Verify,
338394
Tk::Equal,
339395
Tk::Num(32),
340396
Tk::Size => term.reduce0(Terminal::Hash160(
341-
hash160::Hash::from_inner(hash)
397+
hash160::Hash::from_slice(hash).expect("valid size")
342398
))?,
343399
),
344400
// thresholds
@@ -390,7 +446,10 @@ pub fn parse<Ctx: ScriptContext>(
390446
for _ in 0..n {
391447
match_token!(
392448
tokens,
393-
Tk::Pubkey(pk) => keys.push(pk),
449+
Tk::Bytes33(pk) => keys.push(<Pk>::from_slice(pk)
450+
.map_err(|e| Error::PubKeyCtxError(e.to_string(), Ctx::to_string()))?),
451+
Tk::Bytes65(pk) => keys.push(<Pk>::from_slice(pk)
452+
.map_err(|e| Error::PubKeyCtxError(e.to_string(), Ctx::to_string()))?),
394453
);
395454
}
396455
let k = match_token!(

0 commit comments

Comments
 (0)