diff --git a/Cargo.toml b/Cargo.toml index 2a1e3a690..bb64eede9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sapio-miniscript" -version = "7.0.1" +version = "7.0.2-alpha.0" authors = ["Jeremy Rubin ", "Andrew Poelstra , Sanket Kanjalkar "] repository = "https://github.com/sapio-lang/rust-miniscript" description = "Miniscript: a subset of Bitcoin Script designed for analysis, Sapio extended edition (supports BIP-119 OP_CTV)" @@ -25,6 +25,7 @@ optional = true [dependencies.serde] version = "1.0" optional = true +features = ["derive"] [[example]] name = "htlc" diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 56159939b..f1e7be2e3 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -1052,7 +1052,7 @@ mod tests { use super::*; use bitcoin; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; - use bitcoin::secp256k1::{self, Secp256k1}; + use bitcoin::secp256k1::{self, Secp256k1, Parity}; use miniscript::context::NoChecks; use Miniscript; use MiniscriptKey; @@ -1066,7 +1066,7 @@ mod tests { Vec, secp256k1::Message, Secp256k1, - Vec, + Vec<(bitcoin::XOnlyPublicKey, Parity)>, Vec, Vec>, ) { @@ -1101,7 +1101,7 @@ mod tests { pks.push(pk); der_sigs.push(sigser); - let keypair = bitcoin::KeyPair::from_secret_key(&secp, sk); + let keypair = bitcoin::KeyPair::from_secret_key(&secp, &sk); x_only_pks.push(bitcoin::XOnlyPublicKey::from_keypair(&keypair)); let schnorr_sig = secp.sign_schnorr_with_aux_rand(&msg, &keypair, &[0u8; 32]); let schnorr_sig = bitcoin::SchnorrSig { @@ -1568,7 +1568,7 @@ mod tests { let elem = x_only_no_checks_ms(&format!( "multi_a(3,{},{},{},{},{})", - xpks[0], xpks[1], xpks[2], xpks[3], xpks[4], + xpks[0].0, xpks[1].0, xpks[2].0, xpks[3].0, xpks[4].0, )); let vfyfn = vfyfn_.clone(); // sigh rust 1.29... let constraints = from_stack(Box::new(vfyfn), txtmpl_hash, stack, &elem); @@ -1578,13 +1578,13 @@ mod tests { multi_a_satisfied.unwrap(), vec![ SatisfiedConstraint::PublicKey { - key_sig: KeySigPair::Schnorr(xpks[0], schnorr_sigs[0]) + key_sig: KeySigPair::Schnorr(xpks[0].0, schnorr_sigs[0]) }, SatisfiedConstraint::PublicKey { - key_sig: KeySigPair::Schnorr(xpks[1], schnorr_sigs[1]) + key_sig: KeySigPair::Schnorr(xpks[1].0, schnorr_sigs[1]) }, SatisfiedConstraint::PublicKey { - key_sig: KeySigPair::Schnorr(xpks[2], schnorr_sigs[2]) + key_sig: KeySigPair::Schnorr(xpks[2].0, schnorr_sigs[2]) }, ] ); @@ -1600,7 +1600,7 @@ mod tests { let elem = x_only_no_checks_ms(&format!( "multi_a(3,{},{},{},{},{})", - xpks[0], xpks[1], xpks[2], xpks[3], xpks[4], + xpks[0].0, xpks[1].0, xpks[2].0, xpks[3].0, xpks[4].0, )); let vfyfn = vfyfn_.clone(); // sigh rust 1.29... let constraints = from_stack(Box::new(vfyfn), txtmpl_hash, stack.clone(), &elem); @@ -1611,7 +1611,7 @@ mod tests { // multi_a wrong thresh: k = 2, but three sigs let elem = x_only_no_checks_ms(&format!( "multi_a(2,{},{},{},{},{})", - xpks[0], xpks[1], xpks[2], xpks[3], xpks[4], + xpks[0].0, xpks[1].0, xpks[2].0, xpks[3].0, xpks[4].0, )); let vfyfn = vfyfn_.clone(); // sigh rust 1.29... let constraints = from_stack(Box::new(vfyfn), txtmpl_hash, stack.clone(), &elem); @@ -1622,7 +1622,7 @@ mod tests { // multi_a correct thresh, but small stack let elem = x_only_no_checks_ms(&format!( "multi_a(3,{},{},{},{},{},{})", - xpks[0], xpks[1], xpks[2], xpks[3], xpks[4], xpks[5] + xpks[0].0, xpks[1].0, xpks[2].0, xpks[3].0, xpks[4].0, xpks[5].0 )); let vfyfn = vfyfn_.clone(); // sigh rust 1.29... let constraints = from_stack(Box::new(vfyfn), txtmpl_hash, stack, &elem); diff --git a/src/lib.rs b/src/lib.rs index 639541859..f972eaa3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,6 +115,8 @@ pub mod interpreter; pub mod miniscript; pub mod policy; pub mod psbt; +#[allow(missing_docs)] +pub mod ord; mod util; @@ -597,6 +599,8 @@ pub enum Error { TrNoScriptCode, /// No explicit script for Tr descriptors TrNoExplicitScript, + /// Inscription System Issue + InscriptionError(String) } #[doc(hidden)] @@ -737,6 +741,8 @@ impl fmt::Display for Error { Error::TrNoExplicitScript => { write!(f, "No script code for Tr descriptors") } + Error::InscriptionError(ref s) => + write!(f, "Inscription Error: {}", s) } } } diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index 9caca84ad..741df381d 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -23,9 +23,11 @@ use std::str::FromStr; use std::sync::Arc; use std::{fmt, str}; +use bitcoin::blockdata::script::Builder; use bitcoin::blockdata::{opcodes, script}; use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; +use bitcoin::Script; use errstr; use expression; @@ -35,6 +37,9 @@ use miniscript::ScriptContext; use script_num_size; use util::MsKeyBuilder; + +use crate::ord::envelope::{Envelope, ParsedEnvelope}; +use crate::ord::Inscription; use {Error, ForEach, ForEachKey, Miniscript, MiniscriptKey, Terminal, ToPublicKey, TranslatePk}; impl Terminal { @@ -124,6 +129,9 @@ impl Terminal { Terminal::Multi(_, ref keys) | Terminal::MultiA(_, ref keys) => { keys.iter().all(|key| pred(ForEach::Key(key))) } + Terminal::InscribePre(_, ref m) | Terminal::InscribePost(_, ref m) => { + m.real_for_each_key(pred) + } } } pub(super) fn real_translate_pk( @@ -219,6 +227,14 @@ impl Terminal { Terminal::MultiA(k, keys?) } Terminal::TxTemplate(x) => Terminal::TxTemplate(x), + Terminal::InscribePre(ref a, ref b) => Terminal::InscribePre( + a.clone(), + Arc::new(b.real_translate_pk(translatefpk, translatefpkh)?), + ), + Terminal::InscribePost(ref a, ref b) => Terminal::InscribePost( + a.clone(), + Arc::new(b.real_translate_pk(translatefpk, translatefpkh)?), + ), }; Ok(frag) } @@ -341,6 +357,24 @@ impl fmt::Debug for Terminal { impl fmt::Display for Terminal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { + Terminal::InscribePost(ref insc, ref sub) => { + let script = insc + .iter() + .fold(Builder::new(), |builder, i| { + i.append_reveal_script_to_builder(builder) + }) + .into_script(); + write!(f, "inscribe_post({:x},{})", script, sub) + } + Terminal::InscribePre(ref insc, ref sub) => { + let script = insc + .iter() + .fold(Builder::new(), |builder, i| { + i.append_reveal_script_to_builder(builder) + }) + .into_script(); + write!(f, "inscribe_pre({:x},{})", script, sub) + } Terminal::PkK(ref pk) => write!(f, "pk_k({})", pk), Terminal::PkH(ref pkh) => write!(f, "pk_h({})", pkh), Terminal::After(t) => write!(f, "after({})", t), @@ -594,6 +628,16 @@ where ("txtmpl", 1) => expression::terminal(&top.args[0], |x| { sha256::Hash::from_hex(x).map(Terminal::TxTemplate) }), + ("inscribe_pre", 2) => { + let inscription = extract_inscriptions(&top.args[0])?; + let expr = expression::FromTree::from_tree(&top.args[1])?; + Ok(Terminal::InscribePre(Arc::new(inscription), expr)) + } + ("inscribe_post", 2) => { + let expr = expression::FromTree::from_tree(&top.args[0])?; + let inscription = extract_inscriptions(&top.args[1])?; + Ok(Terminal::InscribePost(Arc::new(inscription), expr)) + } _ => Err(Error::Unexpected(format!( "{}({} args) while parsing Miniscript", top.name, @@ -643,6 +687,18 @@ where } } +fn extract_inscriptions(args: &expression::Tree<'_>) -> Result, Error> { + let inscription = expression::terminal(args, |x| -> Result, script::Error> { + let script = Script::from_hex(x).map_err(|e| script::Error::SerializationError)?; + let envelopes = Envelope::from_tapscript(&script, 0 /*Garbage Value OK */)?; + let parsed = envelopes.into_iter().map(ParsedEnvelope::from); + + Ok(parsed.map(|i| i.payload).collect::>()) + }) + .map_err(|e| Error::InscriptionError(e.to_string()))?; + Ok(inscription) +} + /// Helper trait to add a `push_astelem` method to `script::Builder` trait PushAstElem { fn push_astelem(self, ast: &Miniscript) -> Self @@ -801,6 +857,18 @@ impl Terminal { .push_slice(&h[..]) .push_opcode(opcodes::all::OP_NOP4) .push_opcode(opcodes::all::OP_DROP), + Terminal::InscribePre(ref a, ref b) => { + builder = a.iter().fold(builder, |builder, insc| { + insc.append_reveal_script_to_builder(builder) + }); + builder.push_astelem(&b) + } + Terminal::InscribePost(ref a, ref b) => { + builder = builder.push_astelem(&b); + a.iter().fold(builder, |builder, insc| { + insc.append_reveal_script_to_builder(builder) + }) + } } } @@ -862,6 +930,9 @@ impl Terminal { + pks.len() // n times CHECKSIGADD } Terminal::TxTemplate(..) => 33 + 2, + Terminal::InscribePost(ref i, ref rest) | Terminal::InscribePre(ref i, ref rest) => { + rest.script_size() + i.iter().map(|j| j.size_guess()).sum::() + } } } } diff --git a/src/miniscript/decode.rs b/src/miniscript/decode.rs index a442f2360..3165e3e84 100644 --- a/src/miniscript/decode.rs +++ b/src/miniscript/decode.rs @@ -35,6 +35,8 @@ use MiniscriptKey; use ToPublicKey; +use crate::ord::Inscription; + fn return_none(_: usize) -> Option { None } @@ -115,7 +117,7 @@ enum NonTerm { OrC, ThreshW { k: usize, n: usize }, ThreshE { k: usize, n: usize }, - // could be or_d, or_c, or_i, d:, n: + // could be or_d, or_c, or_i, d:, n:, or inscribe EndIf, // could be or_d, or_c EndIfNotIf, @@ -194,6 +196,10 @@ pub enum Terminal { // Other /// ` OP_CHECKTEMPLATEVERIFY OP_DROP` TxTemplate(sha256::Hash), + /// FALSE OP_IF ENDIF [various] + InscribePre(Arc>, Arc>), + /// [various] FALSE OP_IF ENDIF [various] + InscribePost(Arc>, Arc>) } macro_rules! match_token { diff --git a/src/miniscript/lex.rs b/src/miniscript/lex.rs index 304739e64..a185a79fd 100644 --- a/src/miniscript/lex.rs +++ b/src/miniscript/lex.rs @@ -17,14 +17,26 @@ //! Translates a script into a reversed sequence of tokens //! -use bitcoin::blockdata::{opcodes, script}; +use bitcoin::{ + blockdata::{ + opcodes::{ + self, + all::{OP_ENDIF, OP_IF}, + OP_FALSE, + }, + script::{self, Instruction}, + }, + Script, +}; -use std::fmt; +use std::{fmt, sync::Arc}; + +use crate::ord::{self, PROTOCOL_ID}; use super::Error; /// Atom of a tokenized version of a script -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] #[allow(missing_docs)] pub enum Token<'s> { BoolAnd, @@ -60,11 +72,12 @@ pub enum Token<'s> { Bytes32(&'s [u8]), Bytes33(&'s [u8]), Bytes65(&'s [u8]), + Inscription(Arc