From 4d96350ddf83093306f62632ca0a9aad4c83c42c Mon Sep 17 00:00:00 2001 From: Jeremy Rubin Date: Sun, 21 Jan 2024 19:53:41 -0500 Subject: [PATCH 1/6] Experimental Inscription Support --- src/interpreter/mod.rs | 20 +- src/lib.rs | 6 + src/miniscript/astelem.rs | 52 +++++ src/miniscript/decode.rs | 8 +- src/miniscript/lex.rs | 70 ++++++- src/miniscript/satisfy.rs | 6 + src/miniscript/types/correctness.rs | 6 + src/miniscript/types/extra_props.rs | 41 +++- src/miniscript/types/malleability.rs | 6 + src/miniscript/types/mod.rs | 27 ++- src/ord/envelope.rs | 275 +++++++++++++++++++++++++++ src/ord/mod.rs | 101 ++++++++++ src/ord/tag.rs | 84 ++++++++ src/policy/concrete.rs | 17 ++ src/policy/mod.rs | 16 +- src/policy/semantic.rs | 30 ++- 16 files changed, 736 insertions(+), 29 deletions(-) create mode 100644 src/ord/envelope.rs create mode 100644 src/ord/mod.rs create mode 100644 src/ord/tag.rs 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..b58533850 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -26,6 +26,7 @@ use std::{fmt, str}; 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 +36,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 +128,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 +226,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) } @@ -594,6 +609,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 +668,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, usize::MAX /*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 +838,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 +911,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..9728e8a06 100644 --- a/src/miniscript/lex.rs +++ b/src/miniscript/lex.rs @@ -17,14 +17,23 @@ //! 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::{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 +69,12 @@ pub enum Token<'s> { Bytes32(&'s [u8]), Bytes33(&'s [u8]), Bytes65(&'s [u8]), + Inscription(Arc