Skip to content

Commit

Permalink
Experimental Inscription Support
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremyRubin committed Jan 22, 2024
1 parent 53234c5 commit 4d96350
Show file tree
Hide file tree
Showing 16 changed files with 736 additions and 29 deletions.
20 changes: 10 additions & 10 deletions src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -1066,7 +1066,7 @@ mod tests {
Vec<bitcoin::EcdsaSig>,
secp256k1::Message,
Secp256k1<secp256k1::All>,
Vec<bitcoin::XOnlyPublicKey>,
Vec<(bitcoin::XOnlyPublicKey, Parity)>,
Vec<bitcoin::SchnorrSig>,
Vec<Vec<u8>>,
) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand All @@ -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])
},
]
);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ pub mod interpreter;
pub mod miniscript;
pub mod policy;
pub mod psbt;
#[allow(missing_docs)]
pub mod ord;

mod util;

Expand Down Expand Up @@ -597,6 +599,8 @@ pub enum Error {
TrNoScriptCode,
/// No explicit script for Tr descriptors
TrNoExplicitScript,
/// Inscription System Issue
InscriptionError(String)
}

#[doc(hidden)]
Expand Down Expand Up @@ -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)
}
}
}
Expand Down
52 changes: 52 additions & 0 deletions src/miniscript/astelem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
Expand Down Expand Up @@ -124,6 +128,9 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
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<FPk, FPkh, Q, Error, CtxQ>(
Expand Down Expand Up @@ -219,6 +226,14 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
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)
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -643,6 +668,18 @@ where
}
}

fn extract_inscriptions(args: &expression::Tree<'_>) -> Result<Vec<Inscription>, Error> {
let inscription = expression::terminal(args, |x| -> Result<Vec<Inscription>, 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::<Vec<_>>())
})
.map_err(|e| Error::InscriptionError(e.to_string()))?;
Ok(inscription)
}

/// Helper trait to add a `push_astelem` method to `script::Builder`
trait PushAstElem<Pk: MiniscriptKey, Ctx: ScriptContext> {
fn push_astelem(self, ast: &Miniscript<Pk, Ctx>) -> Self
Expand Down Expand Up @@ -801,6 +838,18 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
.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)
})
}
}
}

Expand Down Expand Up @@ -862,6 +911,9 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
+ 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::<usize>()
}
}
}
}
8 changes: 7 additions & 1 deletion src/miniscript/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ use MiniscriptKey;

use ToPublicKey;

use crate::ord::Inscription;

fn return_none<T>(_: usize) -> Option<T> {
None
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -194,6 +196,10 @@ pub enum Terminal<Pk: MiniscriptKey, Ctx: ScriptContext> {
// Other
/// `<hash> OP_CHECKTEMPLATEVERIFY OP_DROP`
TxTemplate(sha256::Hash),
/// FALSE OP_IF <data> ENDIF [various]
InscribePre(Arc<Vec<Inscription>>, Arc<Miniscript<Pk, Ctx>>),
/// [various] FALSE OP_IF <data> ENDIF [various]
InscribePost(Arc<Vec<Inscription>>, Arc<Miniscript<Pk, Ctx>>)
}

macro_rules! match_token {
Expand Down
70 changes: 64 additions & 6 deletions src/miniscript/lex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -60,18 +69,23 @@ pub enum Token<'s> {
Bytes32(&'s [u8]),
Bytes33(&'s [u8]),
Bytes65(&'s [u8]),
Inscription(Arc<Script>),
}

impl<'s> fmt::Display for Token<'s> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
match self.clone() {
Token::Num(n) => write!(f, "#{}", n),
Token::Hash20(b) | Token::Bytes33(b) | Token::Bytes32(b) | Token::Bytes65(b) => {
for ch in &b[..] {
write!(f, "{:02x}", *ch)?;
}
Ok(())
}
Token::Inscription(ref ins) => {
write!(f, "Inscribe({})", ins.asm())?;
Ok(())
}
x => write!(f, "{:?}", x),
}
}
Expand Down Expand Up @@ -117,7 +131,8 @@ impl<'s> Iterator for TokenIter<'s> {
pub fn lex<'s>(script: &'s script::Script) -> Result<Vec<Token<'s>>, Error> {
let mut ret = Vec::with_capacity(script.len());

for ins in script.instructions_minimal() {
let mut it = script.instructions_minimal();
while let Some(ins) = it.next() {
match ins.map_err(Error::Script)? {
script::Instruction::Op(opcodes::all::OP_BOOLAND) => {
ret.push(Token::BoolAnd);
Expand Down Expand Up @@ -182,7 +197,50 @@ pub fn lex<'s>(script: &'s script::Script) -> Result<Vec<Token<'s>>, Error> {
ret.push(Token::Add);
}
script::Instruction::Op(opcodes::all::OP_IF) => {
ret.push(Token::If);
if ret.last() == Some(&Token::Num(0)) {
// Inscription Detected
ret.pop();
if let Some(Ok(Instruction::PushBytes(b"ord"))) = it.next() {

} else {
return Err(Error::InscriptionError("Unknown Protocol Version".into()));
}
let mut scan = script::Builder::new()
.push_opcode(OP_FALSE)
.push_opcode(OP_IF)
.push_slice(&PROTOCOL_ID);
'scan_inscription: while let Some(ins) = it.next() {
let instr = ins.map_err(Error::Script)?;

match instr {
script::Instruction::Op(OP_ENDIF) => {
scan = scan.push_opcode(OP_ENDIF);
break 'scan_inscription;
}
script::Instruction::PushBytes(p) => {
scan = scan.push_slice(p);
}
script::Instruction::Op(a)
if matches!(
a.classify(
opcodes::ClassifyContext::TapScript, /*Either Works */
),
opcodes::Class::PushNum(_)
) =>
{
scan = scan.push_opcode(a);
}
_ => {
return Err(Error::InscriptionError(
"Inscription must be Push Only".into(),
))
}
}
}
ret.push(Token::Inscription(Arc::new(scan.into_script())))
} else {
ret.push(Token::If);
}
}
script::Instruction::Op(opcodes::all::OP_IFDUP) => {
ret.push(Token::IfDup);
Expand Down
6 changes: 6 additions & 0 deletions src/miniscript/satisfy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1250,6 +1250,9 @@ impl Satisfaction {
},
has_sig: true,
},
Terminal::InscribePre(_, ref n) | Terminal::InscribePost(_, ref n) => {
Self::satisfy_helper(&n.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn)
}
}
}

Expand Down Expand Up @@ -1424,6 +1427,9 @@ impl Satisfaction {
stack: Witness::Impossible,
has_sig: false,
},
Terminal::InscribePre(_, ref n) | Terminal::InscribePost(_, ref n) => {
Self::dissatisfy_helper(&n.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn)
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/miniscript/types/correctness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

//! Correctness/Soundness type properties
use std::sync::Arc;

use super::{ErrorKind, Property};

/// Basic type representing where the fragment can go
Expand Down Expand Up @@ -523,4 +525,8 @@ impl Property for Correctness {
unit: true,
})
}

fn inscribing(inscription: &Arc<Vec<crate::ord::Inscription>>, code: Self) -> Result<Self, ErrorKind> {
Ok(code)
}
}
Loading

0 comments on commit 4d96350

Please sign in to comment.