diff --git a/Cargo.toml b/Cargo.toml index 57da5b57d..05a9a760e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ use-serde = ["serde", "bitcoin/use-serde"] rand = ["bitcoin/rand"] [dependencies] -bitcoin = "0.28.0" +bitcoin = { git = "https://github.com/tcharding/rust-bitcoin", branch = "05-17-timelock" } serde = { version = "1.0", optional = true} [[example]] diff --git a/src/interpreter/error.rs b/src/interpreter/error.rs index b9041809f..0b1c00618 100644 --- a/src/interpreter/error.rs +++ b/src/interpreter/error.rs @@ -16,6 +16,7 @@ use std::{error, fmt}; use bitcoin::hashes::hash160; use bitcoin::hashes::hex::ToHex; +use bitcoin::timelock; use bitcoin::util::taproot; use bitcoin::{self, secp256k1}; @@ -25,7 +26,7 @@ use super::BitcoinKey; #[derive(Debug)] pub enum Error { /// Could not satisfy, absolute locktime not met - AbsoluteLocktimeNotMet(u32), + AbsoluteLocktimeNotMet(timelock::Abs), /// Cannot Infer a taproot descriptor /// Key spends cannot infer the internal key of the descriptor /// Inferring script spends is possible, but is hidden nodes are currently @@ -90,7 +91,7 @@ pub enum Error { /// Parse Error while parsing a `stack::Element::Push` as a XOnlyPublicKey (32 bytes) XOnlyPublicKeyParseError, /// Could not satisfy, relative locktime not met - RelativeLocktimeNotMet(u32), + RelativeLocktimeNotMet(timelock::Rel), /// Forward-secp related errors Secp(secp256k1::Error), /// Miniscript requires the entire top level script to be satisfied. @@ -101,6 +102,8 @@ pub enum Error { SighashError(bitcoin::util::sighash::Error), /// Taproot Annex Unsupported TapAnnexUnsupported, + /// Invalid comparison of timelocks. + TimelockComparisonInvalid(timelock::Error), /// An uncompressed public key was encountered in a context where it is /// disallowed (e.g. in a Segwit script or p2wpkh output) UncompressedPubkey, @@ -196,8 +199,9 @@ impl fmt::Display for Error { Error::AbsoluteLocktimeNotMet(n) => write!( f, "required absolute locktime CLTV of {} blocks, not met", - n + n, ), + Error::TimelockComparisonInvalid(e) => write!(f, "timelock comparison error: {:?}", e), Error::CannotInferTrDescriptors => write!(f, "Cannot infer taproot descriptors"), Error::ControlBlockParse(ref e) => write!(f, "Control block parse error {}", e), Error::ControlBlockVerificationError => { diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 20a9fc52b..88ddcbc39 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -25,6 +25,7 @@ use std::str::FromStr; use bitcoin::blockdata::witness::Witness; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; +use bitcoin::timelock; use bitcoin::util::{sighash, taproot}; use bitcoin::{self, secp256k1, TxOut}; @@ -486,13 +487,13 @@ pub enum SatisfiedConstraint { }, ///Relative Timelock for CSV. RelativeTimelock { - /// The value of RelativeTimelock - time: u32, + /// The relative timelock. + time: timelock::Rel, }, ///Absolute Timelock for CLTV. AbsoluteTimelock { - /// The value of Absolute timelock - time: u32, + /// The absolute timelock. + time: timelock::Abs, }, } @@ -1197,7 +1198,7 @@ mod tests { let after_satisfied: Result, Error> = constraints.collect(); assert_eq!( after_satisfied.unwrap(), - vec![SatisfiedConstraint::AbsoluteTimelock { time: 1000 }] + vec![SatisfiedConstraint::AbsoluteTimelock { time: 1000.into() }] ); //Check Older @@ -1207,7 +1208,7 @@ mod tests { let older_satisfied: Result, Error> = constraints.collect(); assert_eq!( older_satisfied.unwrap(), - vec![SatisfiedConstraint::RelativeTimelock { time: 1000 }] + vec![SatisfiedConstraint::RelativeTimelock { time: 1000.into() }], ); //Check Sha256 diff --git a/src/interpreter/stack.rs b/src/interpreter/stack.rs index a31d56e3f..1e7a88320 100644 --- a/src/interpreter/stack.rs +++ b/src/interpreter/stack.rs @@ -17,6 +17,7 @@ use bitcoin; use bitcoin::blockdata::{opcodes, script}; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; +use bitcoin::timelock; use super::error::PkEvalErrInner; use super::{ @@ -230,14 +231,16 @@ impl<'txin> Stack<'txin> { /// booleans pub(super) fn evaluate_after( &mut self, - n: &u32, + n: &timelock::Abs, age: u32, ) -> Option> { - if age >= *n { - self.push(Element::Satisfied); - Some(Ok(SatisfiedConstraint::AbsoluteTimelock { time: *n })) - } else { - Some(Err(Error::AbsoluteLocktimeNotMet(*n))) + match n.is_expired(age) { + Ok(true) => { + self.push(Element::Satisfied); + Some(Ok(SatisfiedConstraint::AbsoluteTimelock { time: *n })) + } + Ok(false) => Some(Err(Error::AbsoluteLocktimeNotMet(*n))), + Err(e) => Some(Err(Error::TimelockComparisonInvalid(e))), } } @@ -249,14 +252,16 @@ impl<'txin> Stack<'txin> { /// booleans pub(super) fn evaluate_older( &mut self, - n: &u32, + n: &timelock::Rel, height: u32, ) -> Option> { - if height >= *n { - self.push(Element::Satisfied); - Some(Ok(SatisfiedConstraint::RelativeTimelock { time: *n })) - } else { - Some(Err(Error::RelativeLocktimeNotMet(*n))) + match n.is_expired(height) { + Ok(true) => { + self.push(Element::Satisfied); + Some(Ok(SatisfiedConstraint::RelativeTimelock { time: *n })) + } + Ok(false) => Some(Err(Error::RelativeLocktimeNotMet(*n))), + Err(e) => Some(Err(Error::TimelockComparisonInvalid(e))), } } diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index ea5c2d30c..bf27fb48f 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -491,10 +491,10 @@ where expression::terminal(&top.args[0], |x| Pk::Hash::from_str(x).map(Terminal::PkH)) } ("after", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x).map(Terminal::After) + expression::parse_num(x).map(|x| Terminal::After(x.into())) }), ("older", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x).map(Terminal::Older) + expression::parse_num(x).map(|x| Terminal::Older(x.into())) }), ("sha256", 1) => expression::terminal(&top.args[0], |x| { sha256::Hash::from_hex(x).map(Terminal::Sha256) @@ -653,9 +653,11 @@ impl Terminal { .push_slice(&Pk::hash_to_hash160(hash)[..]) .push_opcode(opcodes::all::OP_EQUALVERIFY), Terminal::After(t) => builder - .push_int(t as i64) + .push_int(t.to_u32() as i64) .push_opcode(opcodes::all::OP_CLTV), - Terminal::Older(t) => builder.push_int(t as i64).push_opcode(opcodes::all::OP_CSV), + Terminal::Older(t) => builder + .push_int(t.to_u32() as i64) + .push_opcode(opcodes::all::OP_CSV), Terminal::Sha256(h) => builder .push_opcode(opcodes::all::OP_SIZE) .push_int(32) @@ -788,8 +790,8 @@ impl Terminal { match *self { Terminal::PkK(ref pk) => Ctx::pk_len(pk), Terminal::PkH(..) => 24, - Terminal::After(n) => script_num_size(n as usize) + 1, - Terminal::Older(n) => script_num_size(n as usize) + 1, + Terminal::After(n) => script_num_size(n.to_u32() as usize) + 1, + Terminal::Older(n) => script_num_size(n.to_u32() as usize) + 1, Terminal::Sha256(..) => 33 + 6, Terminal::Hash256(..) => 33 + 6, Terminal::Ripemd160(..) => 21 + 6, diff --git a/src/miniscript/decode.rs b/src/miniscript/decode.rs index 87ae40fdf..bc7648cbf 100644 --- a/src/miniscript/decode.rs +++ b/src/miniscript/decode.rs @@ -23,6 +23,7 @@ use std::{error, fmt}; use bitcoin::blockdata::constants::MAX_BLOCK_WEIGHT; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; +use bitcoin::timelock; use crate::miniscript::lex::{Token as Tk, TokenIter}; use crate::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG; @@ -133,9 +134,9 @@ pub enum Terminal { PkH(Pk::Hash), // timelocks /// `n CHECKLOCKTIMEVERIFY` - After(u32), + After(timelock::Abs), /// `n CHECKSEQUENCEVERIFY` - Older(u32), + Older(timelock::Rel), // hashlocks /// `SIZE 32 EQUALVERIFY SHA256 EQUAL` Sha256(sha256::Hash), @@ -386,9 +387,9 @@ pub fn parse( }, // timelocks Tk::CheckSequenceVerify, Tk::Num(n) - => term.reduce0(Terminal::Older(n))?, + => term.reduce0(Terminal::Older(n.into()))?, Tk::CheckLockTimeVerify, Tk::Num(n) - => term.reduce0(Terminal::After(n))?, + => term.reduce0(Terminal::After(n.into()))?, // hashlocks Tk::Equal => match_token!( tokens, diff --git a/src/miniscript/limits.rs b/src/miniscript/limits.rs index f96b01552..3cb16f5bc 100644 --- a/src/miniscript/limits.rs +++ b/src/miniscript/limits.rs @@ -14,26 +14,6 @@ pub const MAX_SCRIPT_SIZE: usize = 10_000; /// Maximum script size allowed by standardness rules // https://github.com/bitcoin/bitcoin/blob/283a73d7eaea2907a6f7f800f529a0d6db53d7a6/src/policy/policy.h#L44 pub const MAX_STANDARD_P2WSH_SCRIPT_SIZE: usize = 3600; -/// The Threshold for deciding whether `nLockTime` is interpreted as -/// time or height. -// https://github.com/bitcoin/bitcoin/blob/9ccaee1d5e2e4b79b0a7c29aadb41b97e4741332/src/script/script.h#L39 -pub const LOCKTIME_THRESHOLD: u32 = 500_000_000; - -/// Bit flag for deciding whether sequence number is -/// interpreted as height or time -/* If nSequence encodes a relative lock-time and this flag - * is set, the relative lock-time has units of 512 seconds, - * otherwise it specifies blocks with a granularity of 1. */ -// https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki -pub const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 1 << 22; - -/// Disable flag for sequence locktime -/* Below flags apply in the context of BIP 68*/ -/* If this flag set, nSequence is NOT interpreted as a - * relative lock-time. For future soft-fork compatibility*/ -// https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki -pub const SEQUENCE_LOCKTIME_DISABLE_FLAG: u32 = 1 << 31; - /// Maximum script element size allowed by consensus rules // https://github.com/bitcoin/bitcoin/blob/42b66a6b814bca130a9ccf0a3f747cf33d628232/src/script/script.h#L23 pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index deb547ea1..de83b560a 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -25,11 +25,9 @@ use std::{cmp, i64, mem}; use bitcoin; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; use bitcoin::secp256k1::XOnlyPublicKey; +use bitcoin::timelock; use bitcoin::util::taproot::{ControlBlock, LeafVersion, TapLeafHash}; -use crate::miniscript::limits::{ - LOCKTIME_THRESHOLD, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_TYPE_FLAG, -}; use crate::util::witness_size; use crate::{Miniscript, MiniscriptKey, ScriptContext, Terminal, ToPublicKey}; @@ -110,12 +108,12 @@ pub trait Satisfier { } /// Assert whether an relative locktime is satisfied - fn check_older(&self, _: u32) -> bool { + fn check_older(&self, _: timelock::Rel) -> bool { false } /// Assert whether a absolute locktime is satisfied - fn check_after(&self, _: u32) -> bool { + fn check_after(&self, _: timelock::Abs) -> bool { false } } @@ -123,43 +121,23 @@ pub trait Satisfier { // Allow use of `()` as a "no conditions available" satisfier impl Satisfier for () {} -/// Newtype around `u32` which implements `Satisfier` using `n` as an -/// relative locktime -pub struct Older(pub u32); - -impl Satisfier for Older { - fn check_older(&self, n: u32) -> bool { - if self.0 & SEQUENCE_LOCKTIME_DISABLE_FLAG != 0 { +impl Satisfier for timelock::Rel { + fn check_older(&self, n: timelock::Rel) -> bool { + if self.is_disabled() { return true; } - /* If nSequence encodes a relative lock-time, this mask is - * applied to extract that lock-time from the sequence field. */ - const SEQUENCE_LOCKTIME_MASK: u32 = 0x0000ffff; - - let mask = SEQUENCE_LOCKTIME_MASK | SEQUENCE_LOCKTIME_TYPE_FLAG; - let masked_n = n & mask; - let masked_seq = self.0 & mask; - if masked_n < SEQUENCE_LOCKTIME_TYPE_FLAG && masked_seq >= SEQUENCE_LOCKTIME_TYPE_FLAG { - false - } else { - masked_n <= masked_seq + if !self.is_same_type(n) { + return false; } + + n.value() <= self.value() } } -/// Newtype around `u32` which implements `Satisfier` using `n` as an -/// absolute locktime -pub struct After(pub u32); - -impl Satisfier for After { - fn check_after(&self, n: u32) -> bool { - // if n > self.0; we will be returning false anyways - if n < LOCKTIME_THRESHOLD && self.0 >= LOCKTIME_THRESHOLD { - false - } else { - n <= self.0 - } +impl Satisfier for timelock::Abs { + fn check_after(&self, n: timelock::Abs) -> bool { + n.is_expired(self.to_u32()).ok().unwrap_or(false) } } @@ -273,11 +251,11 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' (**self).lookup_hash160(h) } - fn check_older(&self, t: u32) -> bool { + fn check_older(&self, t: timelock::Rel) -> bool { (**self).check_older(t) } - fn check_after(&self, t: u32) -> bool { + fn check_after(&self, t: timelock::Abs) -> bool { (**self).check_after(t) } } @@ -335,11 +313,11 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' (**self).lookup_hash160(h) } - fn check_older(&self, t: u32) -> bool { + fn check_older(&self, t: timelock::Rel) -> bool { (**self).check_older(t) } - fn check_after(&self, t: u32) -> bool { + fn check_after(&self, t: timelock::Abs) -> bool { (**self).check_after(t) } } @@ -473,7 +451,7 @@ macro_rules! impl_tuple_satisfier { None } - fn check_older(&self, n: u32) -> bool { + fn check_older(&self, n: timelock::Rel) -> bool { let &($(ref $ty,)*) = self; $( if $ty.check_older(n) { @@ -483,7 +461,7 @@ macro_rules! impl_tuple_satisfier { false } - fn check_after(&self, n: u32) -> bool { + fn check_after(&self, n: timelock::Abs) -> bool { let &($(ref $ty,)*) = self; $( if $ty.check_after(n) { diff --git a/src/miniscript/types/correctness.rs b/src/miniscript/types/correctness.rs index 82adad152..341dd9583 100644 --- a/src/miniscript/types/correctness.rs +++ b/src/miniscript/types/correctness.rs @@ -14,6 +14,8 @@ //! Correctness/Soundness type properties +use bitcoin::timelock; + use super::{ErrorKind, Property}; /// Basic type representing where the fragment can go @@ -187,6 +189,10 @@ impl Property for Correctness { } } + fn from_older(_: timelock::Rel) -> Self { + Self::from_time(0) // arg is not used. + } + 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 d282f8d04..fcff95dd6 100644 --- a/src/miniscript/types/extra_props.rs +++ b/src/miniscript/types/extra_props.rs @@ -4,10 +4,10 @@ use std::cmp; use std::iter::once; +use bitcoin::blockdata::constants::SEQUENCE_LOCKTIME_DISABLE_FLAG; +use bitcoin::timelock; + use super::{Error, ErrorKind, Property, ScriptContext}; -use crate::miniscript::limits::{ - LOCKTIME_THRESHOLD, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_TYPE_FLAG, -}; use crate::{script_num_size, MiniscriptKey, Terminal}; /// Timelock information for satisfaction of a fragment. @@ -324,9 +324,9 @@ impl Property for ExtData { unreachable!() } - fn from_after(t: u32) -> Self { + fn from_after(t: timelock::Abs) -> Self { ExtData { - pk_cost: script_num_size(t as usize) + 1, + pk_cost: script_num_size(t.to_u32() as usize) + 1, has_free_verify: false, ops: OpLimits::new(1, Some(0), None), stack_elem_count_sat: Some(0), @@ -336,8 +336,8 @@ impl Property for ExtData { timelock_info: TimelockInfo { csv_with_height: false, csv_with_time: false, - cltv_with_height: t < LOCKTIME_THRESHOLD, - cltv_with_time: t >= LOCKTIME_THRESHOLD, + cltv_with_height: t.is_block_height(), + cltv_with_time: t.is_block_time(), contains_combination: false, }, exec_stack_elem_count_sat: Some(1), // @@ -345,9 +345,9 @@ impl Property for ExtData { } } - fn from_older(t: u32) -> Self { + fn from_older(t: timelock::Rel) -> Self { ExtData { - pk_cost: script_num_size(t as usize) + 1, + pk_cost: script_num_size(t.to_u32() as usize) + 1, has_free_verify: false, ops: OpLimits::new(1, Some(0), None), stack_elem_count_sat: Some(0), @@ -355,8 +355,8 @@ impl Property for ExtData { max_sat_size: Some((0, 0)), max_dissat_size: None, timelock_info: TimelockInfo { - csv_with_height: (t & SEQUENCE_LOCKTIME_TYPE_FLAG) == 0, - csv_with_time: (t & SEQUENCE_LOCKTIME_TYPE_FLAG) != 0, + csv_with_height: t.is_block_based(), + csv_with_time: t.is_time_based(), cltv_with_height: false, cltv_with_time: false, contains_combination: false, @@ -925,7 +925,8 @@ impl Property for ExtData { // Note that for CLTV this is a limitation not of Bitcoin but Miniscript. The // number on the stack would be a 5 bytes signed integer but Miniscript's B type // only consumes 4 bytes from the stack. - if t == 0 || (t & SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0 { + let n = t.to_u32(); // FIXME: Is the following check correct? + if n == 0 || (n & SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0 { return Err(Error { fragment: fragment.clone(), error: ErrorKind::InvalidTime, @@ -934,7 +935,7 @@ impl Property for ExtData { Ok(Self::from_after(t)) } Terminal::Older(t) => { - if t == 0 || (t & SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0 { + if t.is_zero() || t.is_disabled() { return Err(Error { fragment: fragment.clone(), error: ErrorKind::InvalidTime, diff --git a/src/miniscript/types/malleability.rs b/src/miniscript/types/malleability.rs index 6c3cc8169..b370abfee 100644 --- a/src/miniscript/types/malleability.rs +++ b/src/miniscript/types/malleability.rs @@ -14,6 +14,8 @@ //! Malleability-related Type properties +use bitcoin::timelock; + use super::{ErrorKind, Property}; /// Whether the fragment has a dissatisfaction, and if so, whether @@ -134,6 +136,10 @@ impl Property for Malleability { } } + fn from_older(_: timelock::Rel) -> Self { + Self::from_time(0) // arg is not used. + } + fn cast_alt(self) -> Result { Ok(self) } diff --git a/src/miniscript/types/mod.rs b/src/miniscript/types/mod.rs index 0643b74be..37a7ea07f 100644 --- a/src/miniscript/types/mod.rs +++ b/src/miniscript/types/mod.rs @@ -22,10 +22,12 @@ pub mod malleability; use std::{error, fmt}; +use bitcoin::blockdata::constants::SEQUENCE_LOCKTIME_DISABLE_FLAG; +use bitcoin::timelock; + pub use self::correctness::{Base, Correctness, Input}; pub use self::extra_props::ExtData; pub use self::malleability::{Dissat, Malleability}; -use super::limits::SEQUENCE_LOCKTIME_DISABLE_FLAG; use super::ScriptContext; use crate::{MiniscriptKey, Terminal}; @@ -304,15 +306,12 @@ pub trait Property: Sized { /// Type property of a relative timelock. Default implementation simply /// passes through to `from_time` - fn from_after(t: u32) -> Self { - Self::from_time(t) + fn from_after(t: timelock::Abs) -> Self { + Self::from_time(t.to_u32()) } - /// Type property of an absolute timelock. Default implementation simply - /// passes through to `from_time` - fn from_older(t: u32) -> Self { - Self::from_time(t) - } + /// Type property of an absolute timelock. + fn from_older(t: timelock::Rel) -> Self; /// Cast using the `Alt` wrapper fn cast_alt(self) -> Result; @@ -438,7 +437,8 @@ pub trait Property: Sized { // Note that for CLTV this is a limitation not of Bitcoin but Miniscript. The // number on the stack would be a 5 bytes signed integer but Miniscript's B type // only consumes 4 bytes from the stack. - if t == 0 || (t & SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0 { + let n = t.to_u32(); // FIXME: Is the following check correct? + if n == 0 || (n & SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0 { return Err(Error { fragment: fragment.clone(), error: ErrorKind::InvalidTime, @@ -447,7 +447,7 @@ pub trait Property: Sized { Ok(Self::from_after(t)) } Terminal::Older(t) => { - if t == 0 || (t & SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0 { + if t.is_zero() || t.is_disabled() { return Err(Error { fragment: fragment.clone(), error: ErrorKind::InvalidTime, @@ -625,14 +625,14 @@ impl Property for Type { } } - fn from_after(t: u32) -> Self { + fn from_after(t: timelock::Abs) -> Self { Type { corr: Property::from_after(t), mall: Property::from_after(t), } } - fn from_older(t: u32) -> Self { + fn from_older(t: timelock::Rel) -> Self { Type { corr: Property::from_older(t), mall: Property::from_older(t), @@ -821,7 +821,8 @@ impl Property for Type { // Note that for CLTV this is a limitation not of Bitcoin but Miniscript. The // number on the stack would be a 5 bytes signed integer but Miniscript's B type // only consumes 4 bytes from the stack. - if t == 0 || (t & SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0 { + let n = t.to_u32(); // FIXME: Is the following check correct? + if n == 0 || (n & SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0 { return Err(Error { fragment: fragment.clone(), error: ErrorKind::InvalidTime, @@ -830,7 +831,7 @@ impl Property for Type { Ok(Self::from_after(t)) } Terminal::Older(t) => { - if t == 0 || (t & SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0 { + if t.is_zero() || t.is_disabled() { return Err(Error { fragment: fragment.clone(), error: ErrorKind::InvalidTime, diff --git a/src/policy/compiler.rs b/src/policy/compiler.rs index a86f56146..ec2bfe355 100644 --- a/src/policy/compiler.rs +++ b/src/policy/compiler.rs @@ -24,6 +24,8 @@ use std::marker::PhantomData; use std::sync::Arc; use std::{cmp, error, f64, fmt, hash, mem}; +use bitcoin::timelock; + use crate::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG; use crate::miniscript::types::{self, ErrorKind, ExtData, Property, Type}; use crate::miniscript::ScriptContext; @@ -205,6 +207,10 @@ impl Property for CompilerExtData { } } + fn from_older(_t: timelock::Rel) -> Self { + Self::from_time(0) // arg is not used. + } + fn cast_alt(self) -> Result { Ok(CompilerExtData { branch_prob: None, @@ -1156,10 +1162,10 @@ mod tests { use std::string::String; use bitcoin::blockdata::{opcodes, script}; - use bitcoin::{self, hashes, secp256k1}; + use bitcoin::{self, hashes, secp256k1, timelock}; use super::*; - use crate::miniscript::{satisfy, Legacy, Segwitv0, Tap}; + use crate::miniscript::{Legacy, Segwitv0, Tap}; use crate::policy::Liftable; use crate::script_num_size; @@ -1209,7 +1215,10 @@ mod tests { // artificially create a policy that is problematic and try to compile let pol: SPolicy = Concrete::And(vec![ Concrete::Key("A".to_string()), - Concrete::And(vec![Concrete::After(9), Concrete::After(1000_000_000)]), + Concrete::And(vec![ + Concrete::After(9.into()), + Concrete::After(1000_000_000.into()), + ]), ]); assert!(pol.compile::().is_err()); @@ -1321,7 +1330,7 @@ mod tests { ( 1, Concrete::And(vec![ - Concrete::Older(10000), + Concrete::Older(10000.into()), Concrete::Threshold(2, key_pol[5..8].to_owned()), ]), ), @@ -1378,12 +1387,12 @@ mod tests { assert!(ms.satisfy(no_sat).is_err()); assert!(ms.satisfy(&left_sat).is_ok()); - assert!(ms.satisfy((&right_sat, satisfy::Older(10001))).is_ok()); + assert!(ms.satisfy((&right_sat, timelock::Rel::from(10001))).is_ok()); //timelock not met - assert!(ms.satisfy((&right_sat, satisfy::Older(9999))).is_err()); + assert!(ms.satisfy((&right_sat, timelock::Rel::from(9999))).is_err()); assert_eq!( - ms.satisfy((left_sat, satisfy::Older(9999))).unwrap(), + ms.satisfy((left_sat, timelock::Rel::from(9999))).unwrap(), vec![ // sat for left branch vec![], @@ -1394,7 +1403,7 @@ mod tests { ); assert_eq!( - ms.satisfy((right_sat, satisfy::Older(10000))).unwrap(), + ms.satisfy((right_sat, timelock::Rel::from(10000))).unwrap(), vec![ // sat for right branch vec![], diff --git a/src/policy/concrete.rs b/src/policy/concrete.rs index 61b5145dc..f1ab02da4 100644 --- a/src/policy/concrete.rs +++ b/src/policy/concrete.rs @@ -20,10 +20,10 @@ use std::{error, fmt, str}; use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; +use bitcoin::timelock; use super::ENTAILMENT_MAX_TERMINALS; use crate::expression::{self, FromTree}; -use crate::miniscript::limits::{LOCKTIME_THRESHOLD, SEQUENCE_LOCKTIME_TYPE_FLAG}; use crate::miniscript::types::extra_props::TimelockInfo; #[cfg(feature = "compiler")] use crate::miniscript::ScriptContext; @@ -45,10 +45,10 @@ pub enum Policy { Trivial, /// A public key which must sign to satisfy the descriptor Key(Pk), - /// An absolute locktime restriction - After(u32), - /// A relative locktime restriction - Older(u32), + /// An absolute timelock restriction + After(timelock::Abs), + /// A relative timelock restriction + Older(timelock::Rel), /// A SHA256 whose preimage must be provided to satisfy the descriptor Sha256(sha256::Hash), /// A SHA256d whose preimage must be provided to satisfy the descriptor @@ -284,13 +284,13 @@ impl Policy { Policy::After(t) => TimelockInfo { csv_with_height: false, csv_with_time: false, - cltv_with_height: t < LOCKTIME_THRESHOLD, - cltv_with_time: t >= LOCKTIME_THRESHOLD, + cltv_with_height: t.is_block_height(), + cltv_with_time: t.is_block_time(), contains_combination: false, }, Policy::Older(t) => TimelockInfo { - csv_with_height: (t & SEQUENCE_LOCKTIME_TYPE_FLAG) == 0, - csv_with_time: (t & SEQUENCE_LOCKTIME_TYPE_FLAG) != 0, + csv_with_height: t.is_block_based(), + csv_with_time: t.is_time_based(), cltv_with_height: false, cltv_with_time: false, contains_combination: false, @@ -350,10 +350,21 @@ impl Policy { Ok(()) } } - Policy::After(n) | Policy::Older(n) => { - if n == 0 { + Policy::After(n) => { + if n.is_zero() { Err(PolicyError::ZeroTime) - } else if n > 2u32.pow(31) { + // FIXME: Is this check correct, why check the top bit of nLockTime? + } else if n.to_u32() > 2u32.pow(31) { + Err(PolicyError::TimeTooFar) + } else { + Ok(()) + } + } + Policy::Older(n) => { + if n.is_zero() { + Err(PolicyError::ZeroTime) + } else if n.is_disabled() { + // TODO: Return a better error. Err(PolicyError::TimeTooFar) } else { Ok(()) @@ -570,7 +581,7 @@ where } else if num == 0 { return Err(Error::PolicyError(PolicyError::ZeroTime)); } - Ok(Policy::After(num)) + Ok(Policy::After(num.into())) } ("older", 1) => { let num = expression::terminal(&top.args[0], expression::parse_num)?; @@ -579,7 +590,7 @@ where } else if num == 0 { return Err(Error::PolicyError(PolicyError::ZeroTime)); } - Ok(Policy::Older(num)) + Ok(Policy::Older(num.into())) } ("sha256", 1) => expression::terminal(&top.args[0], |x| { sha256::Hash::from_hex(x).map(Policy::Sha256) diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 565e76669..4589df0ac 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -227,6 +227,7 @@ mod tests { use std::str::FromStr; use bitcoin; + use bitcoin::timelock; use super::super::miniscript::context::Segwitv0; use super::super::miniscript::Miniscript; @@ -352,7 +353,7 @@ mod tests { 2, vec![ Semantic::KeyHash(key_a.pubkey_hash().as_hash()), - Semantic::Older(42) + Semantic::Older(timelock::Rel::from(42)) ] ), Semantic::KeyHash(key_b.pubkey_hash().as_hash()) diff --git a/src/policy/semantic.rs b/src/policy/semantic.rs index de60a3bf4..c7be4d1b3 100644 --- a/src/policy/semantic.rs +++ b/src/policy/semantic.rs @@ -19,6 +19,7 @@ use std::{fmt, str}; use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; +use bitcoin::timelock; use super::concrete::PolicyError; use super::ENTAILMENT_MAX_TERMINALS; @@ -38,10 +39,10 @@ pub enum Policy { Trivial, /// Signature and public key matching a given hash is required KeyHash(Pk::Hash), - /// An absolute locktime restriction - After(u32), - /// A relative locktime restriction - Older(u32), + /// An absolute timelock restriction + After(timelock::Abs), + /// A relative timelock restriction + Older(timelock::Rel), /// A SHA256 whose preimage must be provided to satisfy the descriptor Sha256(sha256::Hash), /// A SHA256d whose preimage must be provided to satisfy the descriptor @@ -316,10 +317,10 @@ where Pk::Hash::from_str(pk).map(Policy::KeyHash) }), ("after", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x).map(Policy::After) + expression::parse_num(x).map(|x| Policy::After(x.into())) }), ("older", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x).map(Policy::Older) + expression::parse_num(x).map(|x| Policy::Older(x.into())) }), ("sha256", 1) => expression::terminal(&top.args[0], |x| { sha256::Hash::from_hex(x).map(Policy::Sha256) @@ -464,7 +465,7 @@ impl Policy { } /// Helper function to do the recursion in `timelocks`. - fn real_relative_timelocks(&self) -> Vec { + fn real_relative_timelocks(&self) -> Vec { match *self { Policy::Unsatisfiable | Policy::Trivial @@ -484,7 +485,7 @@ impl Policy { /// Returns a list of all relative timelocks, not including 0, /// which appear in the policy - pub fn relative_timelocks(&self) -> Vec { + pub fn relative_timelocks(&self) -> Vec { let mut ret = self.real_relative_timelocks(); ret.sort_unstable(); ret.dedup(); @@ -492,7 +493,7 @@ impl Policy { } /// Helper function for recursion in `absolute timelocks` - fn real_absolute_timelocks(&self) -> Vec { + fn real_absolute_timelocks(&self) -> Vec { match *self { Policy::Unsatisfiable | Policy::Trivial @@ -512,7 +513,7 @@ impl Policy { /// Returns a list of all absolute timelocks, not including 0, /// which appear in the policy - pub fn absolute_timelocks(&self) -> Vec { + pub fn absolute_timelocks(&self) -> Vec { let mut ret = self.real_absolute_timelocks(); ret.sort_unstable(); ret.dedup(); @@ -521,17 +522,14 @@ impl Policy { /// Filter a policy by eliminating relative timelock constraints /// that are not satisfied at the given age. - pub fn at_age(mut self, time: u32) -> Policy { + pub fn at_age(mut self, n: u32) -> Policy { self = match self { - Policy::Older(t) => { - if t > time { - Policy::Unsatisfiable - } else { - Policy::Older(t) - } - } + Policy::Older(t) => match t.is_expired(n) { + Ok(true) => Policy::Older(t), + Ok(false) | Err(_) => Policy::Unsatisfiable, + }, Policy::Threshold(k, subs) => { - Policy::Threshold(k, subs.into_iter().map(|sub| sub.at_age(time)).collect()) + Policy::Threshold(k, subs.into_iter().map(|sub| sub.at_age(n)).collect()) } x => x, }; @@ -540,17 +538,14 @@ impl Policy { /// Filter a policy by eliminating absolute timelock constraints /// that are not satisfied at the given age. - pub fn at_height(mut self, time: u32) -> Policy { + pub fn at_height(mut self, n: u32) -> Policy { self = match self { - Policy::After(t) => { - if t > time { - Policy::Unsatisfiable - } else { - Policy::After(t) - } - } + Policy::After(t) => match t.is_expired(n) { + Ok(true) => Policy::After(t), + Ok(false) | Err(_) => Policy::Unsatisfiable, + }, Policy::Threshold(k, subs) => { - Policy::Threshold(k, subs.into_iter().map(|sub| sub.at_height(time)).collect()) + Policy::Threshold(k, subs.into_iter().map(|sub| sub.at_height(n)).collect()) } x => x, }; @@ -649,6 +644,10 @@ mod tests { #[test] fn semantic_analysis() { + let tl_1000 = timelock::Rel::from(1000); + let tl_2000 = timelock::Rel::from(2000); + let tl_10000 = timelock::Rel::from(10000); + let policy = StringPolicy::from_str("pkh()").unwrap(); assert_eq!(policy, Policy::KeyHash("".to_owned())); assert_eq!(policy.relative_timelocks(), vec![]); @@ -659,9 +658,9 @@ mod tests { assert_eq!(policy.minimum_n_keys(), Some(1)); let policy = StringPolicy::from_str("older(1000)").unwrap(); - assert_eq!(policy, Policy::Older(1000)); + assert_eq!(policy, Policy::Older(tl_1000)); assert_eq!(policy.absolute_timelocks(), vec![]); - assert_eq!(policy.relative_timelocks(), vec![1000]); + assert_eq!(policy.relative_timelocks(), vec![tl_1000]); assert_eq!(policy.clone().at_age(0), Policy::Unsatisfiable); assert_eq!(policy.clone().at_age(999), Policy::Unsatisfiable); assert_eq!(policy.clone().at_age(1000), policy.clone()); @@ -674,10 +673,10 @@ mod tests { policy, Policy::Threshold( 1, - vec![Policy::KeyHash("".to_owned()), Policy::Older(1000),] + vec![Policy::KeyHash("".to_owned()), Policy::Older(tl_1000)] ) ); - assert_eq!(policy.relative_timelocks(), vec![1000]); + assert_eq!(policy.relative_timelocks(), vec![timelock::Rel::from(1000)]); assert_eq!(policy.absolute_timelocks(), vec![]); assert_eq!(policy.clone().at_age(0), Policy::KeyHash("".to_owned())); assert_eq!(policy.clone().at_age(999), Policy::KeyHash("".to_owned())); @@ -723,17 +722,17 @@ mod tests { Policy::Threshold( 2, vec![ - Policy::Older(1000), - Policy::Older(10000), - Policy::Older(1000), - Policy::Older(2000), - Policy::Older(2000), + Policy::Older(tl_1000), + Policy::Older(tl_10000), + Policy::Older(tl_1000), + Policy::Older(tl_2000), + Policy::Older(tl_2000), ] ) ); assert_eq!( policy.relative_timelocks(), - vec![1000, 2000, 10000] //sorted and dedup'd + vec![tl_1000, tl_2000, tl_10000] //sorted and dedup'd ); let policy = StringPolicy::from_str( @@ -747,9 +746,9 @@ mod tests { Policy::Threshold( 2, vec![ - Policy::Older(1000), - Policy::Older(10000), - Policy::Older(1000), + Policy::Older(tl_1000), + Policy::Older(tl_10000), + Policy::Older(tl_1000), Policy::Unsatisfiable, Policy::Unsatisfiable, ] @@ -757,14 +756,14 @@ mod tests { ); assert_eq!( policy.relative_timelocks(), - vec![1000, 10000] //sorted and dedup'd + vec![tl_1000, tl_10000] //sorted and dedup'd ); assert_eq!(policy.n_keys(), 0); assert_eq!(policy.minimum_n_keys(), Some(0)); let policy = StringPolicy::from_str("after(1000)").unwrap(); - assert_eq!(policy, Policy::After(1000)); - assert_eq!(policy.absolute_timelocks(), vec![1000]); + assert_eq!(policy, Policy::After(timelock::Abs::from(1000))); + assert_eq!(policy.absolute_timelocks(), vec![timelock::Abs::from(1000)]); assert_eq!(policy.relative_timelocks(), vec![]); assert_eq!(policy.clone().at_height(0), Policy::Unsatisfiable); assert_eq!(policy.clone().at_height(999), Policy::Unsatisfiable); diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index 9c23bdf0e..d1e43b8a9 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -25,14 +25,13 @@ use std::{error, fmt}; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; use bitcoin::secp256k1::{self, Secp256k1}; +use bitcoin::timelock; use bitcoin::util::psbt::{self, PartiallySignedTransaction as Psbt}; use bitcoin::util::sighash::SighashCache; use bitcoin::util::taproot::{self, ControlBlock, LeafVersion, TapLeafHash}; use bitcoin::{self, EcdsaSighashType, SchnorrSighashType, Script}; use crate::miniscript::iter::PkPkh; -use crate::miniscript::limits::SEQUENCE_LOCKTIME_DISABLE_FLAG; -use crate::miniscript::satisfy::{After, Older}; use crate::{ descriptor, interpreter, Descriptor, DescriptorPublicKey, DescriptorTrait, MiniscriptKey, Preimage32, Satisfier, ToPublicKey, TranslatePk, TranslatePk2, @@ -297,8 +296,8 @@ impl<'psbt, Pk: MiniscriptKey + ToPublicKey> Satisfier for PsbtInputSatisfie .map(|(pk, sig)| (*pk, *sig)) } - fn check_after(&self, n: u32) -> bool { - let locktime = self.psbt.unsigned_tx.lock_time; + fn check_after(&self, n: timelock::Abs) -> bool { + let locktime = self.psbt.unsigned_tx.absolute_lock_time(); let seq = self.psbt.unsigned_tx.input[self.index].sequence; // https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki @@ -306,21 +305,21 @@ impl<'psbt, Pk: MiniscriptKey + ToPublicKey> Satisfier for PsbtInputSatisfie if seq == 0xffffffff { false } else { - >::check_after(&After(locktime), n) + >::check_after(&locktime, n) } } - fn check_older(&self, n: u32) -> bool { - let seq = self.psbt.unsigned_tx.input[self.index].sequence; + fn check_older(&self, n: timelock::Rel) -> bool { + let locktime = self.psbt.unsigned_tx.relative_lock_time(self.index); + // https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki - // Disable flag set. return true - if n & SEQUENCE_LOCKTIME_DISABLE_FLAG != 0 { + if n.is_disabled() { true - } else if self.psbt.unsigned_tx.version < 2 || (seq & SEQUENCE_LOCKTIME_DISABLE_FLAG != 0) { + } else if self.psbt.unsigned_tx.version < 2 || locktime.is_disabled() { // transaction version and sequence check false } else { - >::check_older(&Older(seq), n) + >::check_older(&locktime, n) } }