diff --git a/src/descriptor/covenants/mod.rs b/src/descriptor/covenants/mod.rs index 17a897f5..a7575a51 100644 --- a/src/descriptor/covenants/mod.rs +++ b/src/descriptor/covenants/mod.rs @@ -76,12 +76,6 @@ use {MiniscriptKey, ToPublicKey}; use {DescriptorTrait, Error, Satisfier}; pub trait CovOperations: Sized { - /// pick an element indexed from the bottom of - /// the stack. This cannot check whether the idx is within - /// stack limits. - /// Copies the element at index idx to the top of the stack - fn pick(self, idx: u32) -> Self; - /// Assuming the 10 sighash components + 1 sig on the top of /// stack for segwit sighash as created by [init_stack] /// CAT all of them and check sig from stack @@ -91,21 +85,9 @@ pub trait CovOperations: Sized { /// assuming the above construction of covenants /// which uses OP_CODESEP fn post_codesep_script(self) -> Self; - - /// Put version on top of stack - fn pick_version(self) -> Self { - self.pick(9) - } } impl CovOperations for script::Builder { - fn pick(self, idx: u32) -> Self { - self.push_int((idx + 1) as i64) // +1 for depth increase - .push_opcode(all::OP_DEPTH) - .push_opcode(all::OP_SUB) - .push_opcode(all::OP_PICK) - } - fn verify_cov(self, key: &bitcoin::PublicKey) -> Self { let mut builder = self; // The miniscript is of type B, which should have pushed 1 diff --git a/src/lib.rs b/src/lib.rs index 7cb27d07..6b2bd5a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -340,7 +340,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::InvalidOpcode(op) => write!(f, "invalid opcode {}", op), - Error::NonMinimalVerify(tok) => write!(f, "{} VERIFY", tok), + Error::NonMinimalVerify(ref tok) => write!(f, "{} VERIFY", tok), Error::InvalidPush(ref push) => write!(f, "invalid push {:?}", push), // TODO hexify this Error::Script(ref e) => fmt::Display::fmt(e, f), Error::CmsTooManyKeys(n) => write!(f, "checkmultisig with {} keys", n), diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index 94bab16c..2dc3660f 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -23,6 +23,7 @@ use std::str::FromStr; use std::sync::Arc; use std::{fmt, str}; +use elements::encode::{serialize, Encodable}; use elements::hashes::hex::FromHex; use elements::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; use elements::{opcodes, script}; @@ -98,6 +99,7 @@ impl Terminal { Terminal::Hash160(x) => Terminal::Hash160(x), Terminal::True => Terminal::True, Terminal::False => Terminal::False, + Terminal::Version(n) => Terminal::Version(n), Terminal::Alt(ref sub) => Terminal::Alt(Arc::new( sub.real_translate_pk(translatefpk, translatefpkh)?, )), @@ -230,6 +232,7 @@ impl fmt::Debug for Terminal { Terminal::Hash160(h) => write!(f, "hash160({})", h), Terminal::True => f.write_str("1"), Terminal::False => f.write_str("0"), + Terminal::Version(k) => write!(f, "ver_eq({})", k), Terminal::AndV(ref l, ref r) => write!(f, "and_v({:?},{:?})", l, r), Terminal::AndB(ref l, ref r) => write!(f, "and_b({:?},{:?})", l, r), Terminal::AndOr(ref a, ref b, ref c) => { @@ -280,6 +283,7 @@ impl fmt::Display for Terminal { Terminal::Hash160(h) => write!(f, "hash160({})", h), Terminal::True => f.write_str("1"), Terminal::False => f.write_str("0"), + Terminal::Version(n) => write!(f, "ver_eq({})", n), Terminal::AndV(ref l, ref r) if r.node != Terminal::True => { write!(f, "and_v({},{})", l, r) } @@ -448,6 +452,10 @@ where }), ("1", 0) => Ok(Terminal::True), ("0", 0) => Ok(Terminal::False), + ("ver_eq", 1) => { + let n = expression::terminal(&top.args[0], expression::parse_num)?; + Ok(Terminal::Version(n)) + } ("and_v", 2) => { let expr = expression::binary(top, Terminal::AndV)?; if let Terminal::AndV(_, ref right) = expr { @@ -580,6 +588,29 @@ impl PushAstElem for script::Bui } } +/// Additional operations required on Script +/// for supporting Miniscript fragments that +/// have access to a global context +pub trait StackCtxOperations: Sized { + /// pick an element indexed from the bottom of + /// the stack. This cannot check whether the idx is within + /// stack limits. + /// Copies the element at index idx to the top of the stack + /// Checks item equality against the specified target + fn check_item_eq(self, idx: u32, target: T) -> Self; +} + +impl StackCtxOperations for script::Builder { + fn check_item_eq(self, idx: u32, target: T) -> Self { + self.push_int((idx + 1) as i64) // +1 for depth increase + .push_opcode(opcodes::all::OP_DEPTH) + .push_opcode(opcodes::all::OP_SUB) + .push_opcode(opcodes::all::OP_PICK) + .push_slice(&serialize(&target)) + .push_opcode(opcodes::all::OP_EQUAL) + } +} + impl Terminal { /// Encode the element as a fragment of Bitcoin Script. The inverse /// function, from Script to an AST element, is implemented in the @@ -629,6 +660,7 @@ impl Terminal { .push_opcode(opcodes::all::OP_EQUAL), Terminal::True => builder.push_opcode(opcodes::OP_TRUE), Terminal::False => builder.push_opcode(opcodes::OP_FALSE), + Terminal::Version(n) => builder.check_item_eq(11, n), Terminal::Alt(ref sub) => builder .push_opcode(opcodes::all::OP_TOALTSTACK) .push_astelem(sub) @@ -725,6 +757,7 @@ impl Terminal { Terminal::Hash160(..) => 21 + 6, Terminal::True => 1, Terminal::False => 1, + Terminal::Version(_n) => 4 + 1 + 1 + 4, // opcodes + push opcodes + target size Terminal::Alt(ref sub) => sub.node.script_size() + 2, Terminal::Swap(ref sub) => sub.node.script_size() + 1, Terminal::Check(ref sub) => sub.node.script_size() + 1, diff --git a/src/miniscript/decode.rs b/src/miniscript/decode.rs index a4ec54bb..5ff3c3b2 100644 --- a/src/miniscript/decode.rs +++ b/src/miniscript/decode.rs @@ -87,6 +87,9 @@ pub enum Terminal { Ripemd160(ripemd160::Hash), /// `SIZE 32 EQUALVERIFY HASH160 EQUAL` Hash160(hash160::Hash), + // Elements + /// `DEPTH <10> SUB PICK EQUAL` + Version(u32), // Wrappers /// `TOALTSTACK [E] FROMALTSTACK` Alt(Arc>), @@ -318,6 +321,10 @@ pub fn parse( hash160::Hash::from_inner(hash) ))?, ), + Tk::Push(_bytes), Tk::Pick, Tk::Sub, Tk::Depth => match_token!( + tokens, + Tk::Num(12) => term.reduce0(Terminal::Version(4))?, // find some implementation u32 from le + ), // thresholds Tk::Num(k) => { non_term.push(NonTerm::ThreshW { diff --git a/src/miniscript/lex.rs b/src/miniscript/lex.rs index c40e0d58..6f6db9de 100644 --- a/src/miniscript/lex.rs +++ b/src/miniscript/lex.rs @@ -25,12 +25,13 @@ use std::fmt; 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 { BoolAnd, BoolOr, Add, + Sub, Equal, CheckSig, CheckSigFromStack, @@ -44,6 +45,7 @@ pub enum Token { CodeSep, Over, Pick, + Depth, Drop, Dup, If, @@ -63,11 +65,12 @@ pub enum Token { Hash20([u8; 20]), Hash32([u8; 32]), Pubkey(PublicKey), + Push(Vec), } impl fmt::Display for Token { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { + match self { Token::Num(n) => write!(f, "#{}", n), Token::Hash20(hash) => { for ch in &hash[..] { @@ -189,12 +192,18 @@ pub fn lex(script: &script::Script) -> Result, Error> { script::Instruction::Op(opcodes::all::OP_DROP) => { ret.push(Token::Drop); } + script::Instruction::Op(opcodes::all::OP_DEPTH) => { + ret.push(Token::Depth); + } script::Instruction::Op(opcodes::all::OP_DUP) => { ret.push(Token::Dup); } script::Instruction::Op(opcodes::all::OP_ADD) => { ret.push(Token::Add); } + script::Instruction::Op(opcodes::all::OP_SUB) => { + ret.push(Token::Sub); + } script::Instruction::Op(opcodes::all::OP_IF) => { ret.push(Token::If); } @@ -223,7 +232,9 @@ pub fn lex(script: &script::Script) -> Result, Error> { match ret.last() { Some(op @ &Token::Equal) | Some(op @ &Token::CheckSig) - | Some(op @ &Token::CheckMultiSig) => return Err(Error::NonMinimalVerify(*op)), + | Some(op @ &Token::CheckMultiSig) => { + return Err(Error::NonMinimalVerify(op.clone())) + } _ => {} } ret.push(Token::Verify); @@ -241,33 +252,43 @@ pub fn lex(script: &script::Script) -> Result, Error> { ret.push(Token::Hash256); } script::Instruction::PushBytes(bytes) => { - match bytes.len() { - 20 => { - let mut x = [0; 20]; - x.copy_from_slice(bytes); - ret.push(Token::Hash20(x)) - } - 32 => { - let mut x = [0; 32]; - x.copy_from_slice(bytes); - ret.push(Token::Hash32(x)) - } - 33 | 65 => { - ret.push(Token::Pubkey( - PublicKey::from_slice(bytes).map_err(Error::BadPubkey)?, - )); - } - _ => { - match script::read_scriptint(bytes) { - Ok(v) if v >= 0 => { - // check minimality of the number - if &script::Builder::new().push_int(v).into_script()[1..] != bytes { - return Err(Error::InvalidPush(bytes.to_owned())); + // Special handling of tokens for Covenants + // To determine whether some Token is actually + // 4 bytes push or a script int of 4 bytes, + // we need additional script context + if ret.last() == Some(&Token::Pick) { + ret.push(Token::Push(bytes.to_vec())) + } else { + match bytes.len() { + 20 => { + let mut x = [0; 20]; + x.copy_from_slice(bytes); + ret.push(Token::Hash20(x)) + } + 32 => { + let mut x = [0; 32]; + x.copy_from_slice(bytes); + ret.push(Token::Hash32(x)) + } + 33 | 65 => { + ret.push(Token::Pubkey( + PublicKey::from_slice(bytes).map_err(Error::BadPubkey)?, + )); + } + _ => { + match script::read_scriptint(bytes) { + Ok(v) if v >= 0 => { + // check minimality of the number + if &script::Builder::new().push_int(v).into_script()[1..] + != bytes + { + return Err(Error::InvalidPush(bytes.to_owned())); + } + ret.push(Token::Num(v as u32)); } - ret.push(Token::Num(v as u32)); + Ok(_) => return Err(Error::InvalidPush(bytes.to_owned())), + Err(e) => return Err(Error::Script(e)), } - Ok(_) => return Err(Error::InvalidPush(bytes.to_owned())), - Err(e) => return Err(Error::Script(e)), } } } diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index f1aa6999..d51e4858 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -891,4 +891,12 @@ mod tests { )) .is_err()); } + + #[test] + fn cov_script_rtt() { + roundtrip( + &ms_str!("ver_eq(4)"), + "Script(OP_PUSHNUM_12 OP_DEPTH OP_SUB OP_PICK OP_PUSHBYTES_4 04000000 OP_EQUAL)", + ); + } } diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index 0fe30d6f..83d7a064 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -724,6 +724,22 @@ impl Witness { None => Witness::Unavailable, } } + + /// Turn a hash preimage into (part of) a satisfaction + fn ver_eq_satisfy>(sat: S, n: u32) -> Self { + match sat.lookup_nversion() { + Some(k) => { + if k == n { + Witness::empty() + } else { + Witness::Impossible + } + } + // Note the unavailable instead of impossible because we don't know + // the version + None => Witness::Unavailable, + } + } } impl Witness { @@ -1065,6 +1081,10 @@ impl Satisfaction { stack: Witness::Impossible, has_sig: false, }, + Terminal::Version(n) => Satisfaction { + stack: Witness::ver_eq_satisfy(stfr, n), + has_sig: false, + }, Terminal::Alt(ref sub) | Terminal::Swap(ref sub) | Terminal::Check(ref sub) @@ -1251,6 +1271,21 @@ impl Satisfaction { stack: Witness::hash_dissatisfaction(), has_sig: false, }, + Terminal::Version(n) => { + let stk = if let Some(k) = stfr.lookup_nversion() { + if k == n { + Witness::Impossible + } else { + Witness::empty() + } + } else { + Witness::empty() + }; + Satisfaction { + stack: stk, + has_sig: false, + } + } Terminal::Alt(ref sub) | Terminal::Swap(ref sub) | Terminal::Check(ref sub) diff --git a/src/miniscript/types/correctness.rs b/src/miniscript/types/correctness.rs index 6cde8d64..b39c543b 100644 --- a/src/miniscript/types/correctness.rs +++ b/src/miniscript/types/correctness.rs @@ -191,6 +191,15 @@ impl Property for Correctness { } } + fn from_item_eq() -> Self { + Correctness { + base: Base::B, + input: Input::Zero, + dissatisfiable: true, + unit: true, + } + } + fn cast_alt(self) -> Result { Ok(Correctness { base: match self.base { diff --git a/src/miniscript/types/extra_props.rs b/src/miniscript/types/extra_props.rs index a3cf48f4..5ebbdb55 100644 --- a/src/miniscript/types/extra_props.rs +++ b/src/miniscript/types/extra_props.rs @@ -318,6 +318,25 @@ impl Property for ExtData { } } + fn from_item_eq() -> Self { + unreachable!() + } + + fn from_ver_eq() -> Self { + ExtData { + pk_cost: 4 + 1 + 1 + 4, // 4 opcodes, 1 push, (5) 4 byte push + has_free_verify: false, + ops_count_static: 4, + ops_count_sat: Some(4), + ops_count_nsat: Some(4), + stack_elem_count_sat: Some(0), + stack_elem_count_dissat: Some(0), + max_sat_size: Some((0, 0)), + max_dissat_size: Some((0, 0)), + timelock_info: TimeLockInfo::default(), + } + } + fn cast_alt(self) -> Result { Ok(ExtData { pk_cost: self.pk_cost + 2, @@ -873,6 +892,7 @@ impl Property for ExtData { Terminal::Hash256(..) => Ok(Self::from_hash256()), Terminal::Ripemd160(..) => Ok(Self::from_ripemd160()), Terminal::Hash160(..) => Ok(Self::from_hash160()), + Terminal::Version(..) => Ok(Self::from_ver_eq()), Terminal::Alt(ref sub) => wrap_err(Self::cast_alt(sub.ext.clone())), Terminal::Swap(ref sub) => wrap_err(Self::cast_swap(sub.ext.clone())), Terminal::Check(ref sub) => wrap_err(Self::cast_check(sub.ext.clone())), diff --git a/src/miniscript/types/malleability.rs b/src/miniscript/types/malleability.rs index ebef4c11..e17300f6 100644 --- a/src/miniscript/types/malleability.rs +++ b/src/miniscript/types/malleability.rs @@ -138,6 +138,14 @@ impl Property for Malleability { } } + fn from_item_eq() -> Self { + Malleability { + dissat: Dissat::Unknown, + safe: false, + non_malleable: true, + } + } + fn cast_alt(self) -> Result { Ok(self) } diff --git a/src/miniscript/types/mod.rs b/src/miniscript/types/mod.rs index e6efe0ca..eb21062c 100644 --- a/src/miniscript/types/mod.rs +++ b/src/miniscript/types/mod.rs @@ -311,6 +311,14 @@ pub trait Property: Sized { Self::from_time(t) } + /// Type property for a general global sighash item lookup + fn from_item_eq() -> Self; + + /// Type property for the ver_eq fragment + fn from_ver_eq() -> Self { + Self::from_item_eq() + } + /// Cast using the `Alt` wrapper fn cast_alt(self) -> Result; @@ -448,6 +456,7 @@ pub trait Property: Sized { Terminal::Hash256(..) => Ok(Self::from_hash256()), Terminal::Ripemd160(..) => Ok(Self::from_ripemd160()), Terminal::Hash160(..) => Ok(Self::from_hash160()), + Terminal::Version(..) => Ok(Self::from_ver_eq()), Terminal::Alt(ref sub) => wrap_err(Self::cast_alt(get_child(&sub.node, 0)?)), Terminal::Swap(ref sub) => wrap_err(Self::cast_swap(get_child(&sub.node, 0)?)), Terminal::Check(ref sub) => wrap_err(Self::cast_check(get_child(&sub.node, 0)?)), @@ -628,6 +637,13 @@ impl Property for Type { } } + fn from_item_eq() -> Self { + Type { + corr: Property::from_item_eq(), + mall: Property::from_item_eq(), + } + } + fn cast_alt(self) -> Result { Ok(Type { corr: Property::cast_alt(self.corr)?, @@ -825,6 +841,7 @@ impl Property for Type { Terminal::Hash256(..) => Ok(Self::from_hash256()), Terminal::Ripemd160(..) => Ok(Self::from_ripemd160()), Terminal::Hash160(..) => Ok(Self::from_hash160()), + Terminal::Version(..) => Ok(Self::from_item_eq()), Terminal::Alt(ref sub) => wrap_err(Self::cast_alt(sub.ty.clone())), Terminal::Swap(ref sub) => wrap_err(Self::cast_swap(sub.ty.clone())), Terminal::Check(ref sub) => wrap_err(Self::cast_check(sub.ty.clone())), diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 079fd201..4c9a4639 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -137,6 +137,7 @@ impl Liftable for Terminal { Terminal::Hash160(h) => Semantic::Hash160(h), Terminal::True => Semantic::Trivial, Terminal::False => Semantic::Unsatisfiable, + Terminal::Version(_) => return Err(CovError::CovenantLift)?, Terminal::Alt(ref sub) | Terminal::Swap(ref sub) | Terminal::Check(ref sub)