diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 1059a3b3c..bea539920 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -512,7 +512,7 @@ impl Descriptor<DefiniteDescriptorKey> { descriptor: self, template: stack, absolute_timelock: satisfaction.absolute_timelock.map(Into::into), - relative_timelock: satisfaction.relative_timelock, + relative_timelock: satisfaction.relative_timelock.map(Into::into), }) } else { Err(self) @@ -540,7 +540,8 @@ impl Descriptor<DefiniteDescriptorKey> { descriptor: self, template: stack, absolute_timelock: satisfaction.absolute_timelock.map(Into::into), - relative_timelock: satisfaction.relative_timelock, + // unwrap to be removed in a later commit + relative_timelock: satisfaction.relative_timelock.map(Into::into), }) } else { Err(self) diff --git a/src/interpreter/error.rs b/src/interpreter/error.rs index f0aa8fb97..d19c28bea 100644 --- a/src/interpreter/error.rs +++ b/src/interpreter/error.rs @@ -9,7 +9,7 @@ use bitcoin::hashes::hash160; use bitcoin::hex::DisplayHex; #[cfg(not(test))] // https://github.com/rust-lang/rust/issues/121684 use bitcoin::secp256k1; -use bitcoin::taproot; +use bitcoin::{absolute, relative, taproot}; use super::BitcoinKey; use crate::prelude::*; @@ -18,9 +18,9 @@ use crate::prelude::*; #[derive(Debug)] pub enum Error { /// Could not satisfy, absolute locktime not met - AbsoluteLocktimeNotMet(u32), + AbsoluteLockTimeNotMet(absolute::LockTime), /// Could not satisfy, lock time values are different units - AbsoluteLocktimeComparisonInvalid(u32, u32), + AbsoluteLockTimeComparisonInvalid(absolute::LockTime, absolute::LockTime), /// 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 @@ -85,7 +85,9 @@ 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(relative::LockTime), + /// Could not satisfy, the sequence number on the tx input had the disable flag set. + RelativeLockTimeDisabled(relative::LockTime), /// Forward-secp related errors Secp(secp256k1::Error), /// Miniscript requires the entire top level script to be satisfied. @@ -116,10 +118,10 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Error::AbsoluteLocktimeNotMet(n) => { + Error::AbsoluteLockTimeNotMet(n) => { write!(f, "required absolute locktime CLTV of {} blocks, not met", n) } - Error::AbsoluteLocktimeComparisonInvalid(n, lock_time) => write!( + Error::AbsoluteLockTimeComparisonInvalid(n, lock_time) => write!( f, "could not satisfy, lock time values are different units n: {} lock_time: {}", n, lock_time @@ -159,9 +161,12 @@ impl fmt::Display for Error { Error::PkHashVerifyFail(ref hash) => write!(f, "Pubkey Hash check failed {}", hash), Error::PubkeyParseError => f.write_str("could not parse pubkey"), Error::XOnlyPublicKeyParseError => f.write_str("could not parse x-only pubkey"), - Error::RelativeLocktimeNotMet(n) => { + Error::RelativeLockTimeNotMet(n) => { write!(f, "required relative locktime CSV of {} blocks, not met", n) } + Error::RelativeLockTimeDisabled(n) => { + write!(f, "required relative locktime CSV of {} blocks, but tx sequence number has disable-flag set", n) + } Error::ScriptSatisfactionError => f.write_str("Top level script must be satisfied"), Error::Secp(ref e) => fmt::Display::fmt(e, f), Error::SchnorrSig(ref s) => write!(f, "Schnorr sig error: {}", s), @@ -188,8 +193,8 @@ impl error::Error for Error { use self::Error::*; match self { - AbsoluteLocktimeNotMet(_) - | AbsoluteLocktimeComparisonInvalid(_, _) + AbsoluteLockTimeNotMet(_) + | AbsoluteLockTimeComparisonInvalid(_, _) | CannotInferTrDescriptors | ControlBlockVerificationError | CouldNotEvaluate @@ -212,7 +217,8 @@ impl error::Error for Error { | XOnlyPublicKeyParseError | PkEvaluationError(_) | PkHashVerifyFail(_) - | RelativeLocktimeNotMet(_) + | RelativeLockTimeNotMet(_) + | RelativeLockTimeDisabled(_) | ScriptSatisfactionError | TapAnnexUnsupported | UncompressedPubkey diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index e54a79fee..dddffa8f0 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -12,7 +12,7 @@ use core::fmt; use core::str::FromStr; use bitcoin::hashes::{hash160, ripemd160, sha256, Hash}; -use bitcoin::{absolute, secp256k1, sighash, taproot, Sequence, TxOut, Witness}; +use bitcoin::{absolute, relative, secp256k1, sighash, taproot, Sequence, TxOut, Witness}; use crate::miniscript::context::{NoChecks, SigType}; use crate::miniscript::ScriptContext; @@ -35,7 +35,7 @@ pub struct Interpreter<'txin> { /// For non-Taproot spends, the scriptCode; for Taproot script-spends, this /// is the leaf script; for key-spends it is `None`. script_code: Option<bitcoin::ScriptBuf>, - age: Sequence, + sequence: Sequence, lock_time: absolute::LockTime, } @@ -136,11 +136,11 @@ impl<'txin> Interpreter<'txin> { spk: &bitcoin::ScriptBuf, script_sig: &'txin bitcoin::Script, witness: &'txin Witness, - age: Sequence, // CSV, relative lock time. + sequence: Sequence, // CSV, relative lock time. lock_time: absolute::LockTime, // CLTV, absolute lock time. ) -> Result<Self, Error> { let (inner, stack, script_code) = inner::from_txdata(spk, script_sig, witness)?; - Ok(Interpreter { inner, stack, script_code, age, lock_time }) + Ok(Interpreter { inner, stack, script_code, sequence, lock_time }) } /// Same as [`Interpreter::iter`], but allows for a custom verification function. @@ -165,7 +165,7 @@ impl<'txin> Interpreter<'txin> { // Cloning the references to elements of stack should be fine as it allows // call interpreter.iter() without mutating interpreter stack: self.stack.clone(), - age: self.age, + sequence: self.sequence, lock_time: self.lock_time, has_errored: false, sig_type: self.sig_type(), @@ -468,7 +468,7 @@ pub enum SatisfiedConstraint { ///Relative Timelock for CSV. RelativeTimelock { /// The value of RelativeTimelock - n: Sequence, + n: relative::LockTime, }, ///Absolute Timelock for CLTV. AbsoluteTimelock { @@ -508,7 +508,7 @@ pub struct Iter<'intp, 'txin: 'intp> { public_key: Option<&'intp BitcoinKey>, state: Vec<NodeEvaluationState<'intp>>, stack: Stack<'txin>, - age: Sequence, + sequence: Sequence, lock_time: absolute::LockTime, has_errored: bool, sig_type: SigType, @@ -608,7 +608,7 @@ where Terminal::Older(ref n) => { debug_assert_eq!(node_state.n_evaluated, 0); debug_assert_eq!(node_state.n_satisfied, 0); - let res = self.stack.evaluate_older(n, self.age); + let res = self.stack.evaluate_older(&(*n).into(), self.sequence); if res.is_some() { return res; } @@ -1110,7 +1110,7 @@ mod tests { stack, public_key: None, state: vec![NodeEvaluationState { node: ms, n_evaluated: 0, n_satisfied: 0 }], - age: Sequence::from_height(1002), + sequence: Sequence::from_height(1002), lock_time: absolute::LockTime::from_height(1002).unwrap(), has_errored: false, sig_type: SigType::Ecdsa, @@ -1182,7 +1182,9 @@ mod tests { let older_satisfied: Result<Vec<SatisfiedConstraint>, Error> = constraints.collect(); assert_eq!( older_satisfied.unwrap(), - vec![SatisfiedConstraint::RelativeTimelock { n: Sequence::from_height(1000) }] + vec![SatisfiedConstraint::RelativeTimelock { + n: crate::RelLockTime::from_height(1000).into() + }] ); //Check Sha256 diff --git a/src/interpreter/stack.rs b/src/interpreter/stack.rs index 760459916..4cf5450af 100644 --- a/src/interpreter/stack.rs +++ b/src/interpreter/stack.rs @@ -5,7 +5,7 @@ use bitcoin::blockdata::{opcodes, script}; use bitcoin::hashes::{hash160, ripemd160, sha256, Hash}; -use bitcoin::{absolute, Sequence}; +use bitcoin::{absolute, relative, Sequence}; use super::error::PkEvalErrInner; use super::{verify_sersig, BitcoinKey, Error, HashLockType, KeySigPair, SatisfiedConstraint}; @@ -212,19 +212,14 @@ impl<'txin> Stack<'txin> { let is_satisfied = match (*n, lock_time) { (Blocks(n), Blocks(lock_time)) => n <= lock_time, (Seconds(n), Seconds(lock_time)) => n <= lock_time, - _ => { - return Some(Err(Error::AbsoluteLocktimeComparisonInvalid( - n.to_consensus_u32(), - lock_time.to_consensus_u32(), - ))) - } + _ => return Some(Err(Error::AbsoluteLockTimeComparisonInvalid(*n, lock_time))), }; if is_satisfied { self.push(Element::Satisfied); Some(Ok(SatisfiedConstraint::AbsoluteTimelock { n: *n })) } else { - Some(Err(Error::AbsoluteLocktimeNotMet(n.to_consensus_u32()))) + Some(Err(Error::AbsoluteLockTimeNotMet(*n))) } } @@ -236,14 +231,19 @@ impl<'txin> Stack<'txin> { /// booleans pub(super) fn evaluate_older( &mut self, - n: &Sequence, - age: Sequence, + n: &relative::LockTime, + sequence: Sequence, ) -> Option<Result<SatisfiedConstraint, Error>> { - if age >= *n { - self.push(Element::Satisfied); - Some(Ok(SatisfiedConstraint::RelativeTimelock { n: *n })) + if let Some(tx_locktime) = sequence.to_relative_lock_time() { + if n.is_implied_by(tx_locktime) { + self.push(Element::Satisfied); + Some(Ok(SatisfiedConstraint::RelativeTimelock { n: *n })) + } else { + Some(Err(Error::RelativeLockTimeNotMet(*n))) + } } else { - Some(Err(Error::RelativeLocktimeNotMet(n.to_consensus_u32()))) + // BIP 112: if the tx locktime has the disable flag set, fail CSV. + Some(Err(Error::RelativeLockTimeDisabled(*n))) } } diff --git a/src/lib.rs b/src/lib.rs index 1360bdbfe..514ccdfe8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,6 +87,7 @@ #![deny(missing_docs)] // Clippy lints that we have disabled #![allow(clippy::iter_kv_map)] // https://github.com/rust-lang/rust-clippy/issues/11752 +#![allow(clippy::manual_range_contains)] // I hate this lint -asp #[cfg(target_pointer_width = "16")] compile_error!( @@ -125,19 +126,19 @@ pub mod iter; pub mod miniscript; pub mod plan; pub mod policy; +mod primitives; pub mod psbt; #[cfg(test)] mod test_utils; mod util; -use core::{cmp, fmt, hash, str}; +use core::{fmt, hash, str}; #[cfg(feature = "std")] use std::error; use bitcoin::hashes::{hash160, ripemd160, sha256, Hash}; use bitcoin::hex::DisplayHex; -use bitcoin::locktime::absolute; use bitcoin::{script, Opcode}; pub use crate::blanket_traits::FromStrKey; @@ -149,6 +150,8 @@ pub use crate::miniscript::decode::Terminal; pub use crate::miniscript::satisfy::{Preimage32, Satisfier}; pub use crate::miniscript::{hash256, Miniscript}; use crate::prelude::*; +pub use crate::primitives::absolute_locktime::{AbsLockTime, AbsLockTimeError}; +pub use crate::primitives::relative_locktime::{RelLockTime, RelLockTimeError}; /// Public key trait which can be converted to Hash type pub trait MiniscriptKey: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Hash { @@ -501,6 +504,10 @@ pub enum Error { /// At least two BIP389 key expressions in the descriptor contain tuples of /// derivation indexes of different lengths. MultipathDescLenMismatch, + /// Invalid absolute locktime + AbsoluteLockTime(AbsLockTimeError), + /// Invalid absolute locktime + RelativeLockTime(RelLockTimeError), } // https://github.com/sipa/miniscript/pull/5 for discussion on this number @@ -576,6 +583,8 @@ impl fmt::Display for Error { Error::TrNoScriptCode => write!(f, "No script code for Tr descriptors"), Error::TrNoExplicitScript => write!(f, "No script code for Tr descriptors"), Error::MultipathDescLenMismatch => write!(f, "At least two BIP389 key expressions in the descriptor contain tuples of derivation indexes of different lengths"), + Error::AbsoluteLockTime(ref e) => e.fmt(f), + Error::RelativeLockTime(ref e) => e.fmt(f), } } } @@ -628,6 +637,8 @@ impl error::Error for Error { ContextError(e) => Some(e), AnalysisError(e) => Some(e), PubKeyCtxError(e, _) => Some(e), + AbsoluteLockTime(e) => Some(e), + RelativeLockTime(e) => Some(e), } } } @@ -711,51 +722,6 @@ fn hex_script(s: &str) -> bitcoin::ScriptBuf { bitcoin::ScriptBuf::from(v) } -/// An absolute locktime that implements `Ord`. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct AbsLockTime(absolute::LockTime); - -impl AbsLockTime { - /// Constructs an `AbsLockTime` from an nLockTime value or the argument to OP_CHEKCLOCKTIMEVERIFY. - pub fn from_consensus(n: u32) -> Self { Self(absolute::LockTime::from_consensus(n)) } - - /// Returns the inner `u32` value. This is the value used when creating this `LockTime` - /// i.e., `n OP_CHECKLOCKTIMEVERIFY` or nLockTime. - /// - /// This calls through to `absolute::LockTime::to_consensus_u32()` and the same usage warnings - /// apply. - pub fn to_consensus_u32(self) -> u32 { self.0.to_consensus_u32() } - - /// Returns the inner `u32` value. - /// - /// Equivalent to `AbsLockTime::to_consensus_u32()`. - pub fn to_u32(self) -> u32 { self.to_consensus_u32() } -} - -impl From<absolute::LockTime> for AbsLockTime { - fn from(lock_time: absolute::LockTime) -> Self { Self(lock_time) } -} - -impl From<AbsLockTime> for absolute::LockTime { - fn from(lock_time: AbsLockTime) -> absolute::LockTime { lock_time.0 } -} - -impl cmp::PartialOrd for AbsLockTime { - fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { Some(self.cmp(other)) } -} - -impl cmp::Ord for AbsLockTime { - fn cmp(&self, other: &Self) -> cmp::Ordering { - let this = self.0.to_consensus_u32(); - let that = other.0.to_consensus_u32(); - this.cmp(&that) - } -} - -impl fmt::Display for AbsLockTime { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } -} - #[cfg(test)] mod tests { use core::str::FromStr; diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index db9d068f1..d6f47d9b9 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -11,7 +11,7 @@ use core::fmt; use core::str::FromStr; use bitcoin::hashes::{hash160, Hash}; -use bitcoin::{absolute, opcodes, script, Sequence}; +use bitcoin::{absolute, opcodes, script}; use sync::Arc; use crate::miniscript::context::SigType; @@ -19,8 +19,8 @@ use crate::miniscript::{types, ScriptContext}; use crate::prelude::*; use crate::util::MsKeyBuilder; use crate::{ - errstr, expression, AbsLockTime, Error, FromStrKey, Miniscript, MiniscriptKey, Terminal, - ToPublicKey, + errstr, expression, AbsLockTime, Error, FromStrKey, Miniscript, MiniscriptKey, RelLockTime, + Terminal, ToPublicKey, }; impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> { @@ -256,12 +256,14 @@ impl<Pk: FromStrKey, Ctx: ScriptContext> crate::expression::FromTree for Termina expression::terminal(&top.args[0], |x| Pk::from_str(x).map(Terminal::PkH)) } ("after", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x).map(|x| { - Terminal::After(AbsLockTime::from(absolute::LockTime::from_consensus(x))) - }) + expression::parse_num(x) + .and_then(|x| AbsLockTime::from_consensus(x).map_err(Error::AbsoluteLockTime)) + .map(Terminal::After) }), ("older", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x).map(|x| Terminal::Older(Sequence::from_consensus(x))) + expression::parse_num(x) + .and_then(|x| RelLockTime::from_consensus(x).map_err(Error::RelativeLockTime)) + .map(Terminal::Older) }), ("sha256", 1) => expression::terminal(&top.args[0], |x| { Pk::Sha256::from_str(x).map(Terminal::Sha256) diff --git a/src/miniscript/decode.rs b/src/miniscript/decode.rs index a058cf4b4..de2ab195f 100644 --- a/src/miniscript/decode.rs +++ b/src/miniscript/decode.rs @@ -11,7 +11,7 @@ use core::marker::PhantomData; use std::error; use bitcoin::hashes::{hash160, ripemd160, sha256, Hash}; -use bitcoin::{Sequence, Weight}; +use bitcoin::Weight; use sync::Arc; use crate::miniscript::lex::{Token as Tk, TokenIter}; @@ -22,7 +22,7 @@ use crate::miniscript::ScriptContext; use crate::prelude::*; #[cfg(doc)] use crate::Descriptor; -use crate::{hash256, AbsLockTime, Error, Miniscript, MiniscriptKey, ToPublicKey}; +use crate::{hash256, AbsLockTime, Error, Miniscript, MiniscriptKey, RelLockTime, ToPublicKey}; /// Trait for parsing keys from byte slices pub trait ParseableKey: Sized + ToPublicKey + private::Sealed { @@ -137,7 +137,7 @@ pub enum Terminal<Pk: MiniscriptKey, Ctx: ScriptContext> { /// `n CHECKLOCKTIMEVERIFY` After(AbsLockTime), /// `n CHECKSEQUENCEVERIFY` - Older(Sequence), + Older(RelLockTime), // hashlocks /// `SIZE 32 EQUALVERIFY SHA256 <hash> EQUAL` Sha256(Pk::Sha256), @@ -357,9 +357,9 @@ pub fn parse<Ctx: ScriptContext>( }, // timelocks Tk::CheckSequenceVerify, Tk::Num(n) - => term.reduce0(Terminal::Older(Sequence::from_consensus(n)))?, + => term.reduce0(Terminal::Older(RelLockTime::from_consensus(n).map_err(Error::RelativeLockTime)?))?, Tk::CheckLockTimeVerify, Tk::Num(n) - => term.reduce0(Terminal::After(AbsLockTime::from_consensus(n)))?, + => term.reduce0(Terminal::After(AbsLockTime::from_consensus(n).map_err(Error::AbsoluteLockTime)?))?, // hashlocks Tk::Equal => match_token!( tokens, diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index f73b41a5f..26f220a9f 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -698,7 +698,6 @@ mod tests { use bitcoin::hashes::{hash160, sha256, Hash}; use bitcoin::secp256k1::XOnlyPublicKey; use bitcoin::taproot::TapLeafHash; - use bitcoin::Sequence; use sync::Arc; use super::{Miniscript, ScriptContext, Segwitv0, Tap}; @@ -707,7 +706,7 @@ mod tests { use crate::policy::Liftable; use crate::prelude::*; use crate::test_utils::{StrKeyTranslator, StrXOnlyKeyTranslator}; - use crate::{hex_script, ExtParams, Satisfier, ToPublicKey, TranslatePk}; + use crate::{hex_script, ExtParams, RelLockTime, Satisfier, ToPublicKey, TranslatePk}; type Segwitv0Script = Miniscript<bitcoin::PublicKey, Segwitv0>; type Tapscript = Miniscript<bitcoin::secp256k1::XOnlyPublicKey, Tap>; @@ -1084,13 +1083,13 @@ mod tests { let mut abs = miniscript.lift().unwrap(); assert_eq!(abs.n_keys(), 5); assert_eq!(abs.minimum_n_keys(), Some(2)); - abs = abs.at_age(Sequence::from_height(10000)); + abs = abs.at_age(RelLockTime::from_height(10000).into()); assert_eq!(abs.n_keys(), 5); assert_eq!(abs.minimum_n_keys(), Some(2)); - abs = abs.at_age(Sequence::from_height(9999)); + abs = abs.at_age(RelLockTime::from_height(9999).into()); assert_eq!(abs.n_keys(), 3); assert_eq!(abs.minimum_n_keys(), Some(3)); - abs = abs.at_age(Sequence::ZERO); + abs = abs.at_age(RelLockTime::ZERO.into()); assert_eq!(abs.n_keys(), 3); assert_eq!(abs.minimum_n_keys(), Some(3)); @@ -1355,7 +1354,7 @@ mod tests { #[test] fn template_timelocks() { - use crate::AbsLockTime; + use crate::{AbsLockTime, RelLockTime}; let key_present = bitcoin::PublicKey::from_str( "0327a6ed0e71b451c79327aa9e4a6bb26ffb1c0056abc02c25e783f6096b79bb4f", ) @@ -1370,7 +1369,7 @@ mod tests { (format!("t:or_c(pk({}),v:pkh({}))", key_present, key_missing), None, None), ( format!("thresh(2,pk({}),s:pk({}),snl:after(1))", key_present, key_missing), - Some(AbsLockTime::from_consensus(1)), + Some(AbsLockTime::from_consensus(1).unwrap()), None, ), ( @@ -1381,20 +1380,20 @@ mod tests { ( format!("or_d(pk({}),and_v(v:pk({}),older(12960)))", key_missing, key_present), None, - Some(bitcoin::Sequence(12960)), + Some(RelLockTime::from_height(12960)), ), ( format!( "thresh(3,pk({}),s:pk({}),snl:older(10),snl:after(11))", key_present, key_missing ), - Some(AbsLockTime::from_consensus(11)), - Some(bitcoin::Sequence(10)), + Some(AbsLockTime::from_consensus(11).unwrap()), + Some(RelLockTime::from_height(10)), ), ( format!("and_v(v:and_v(v:pk({}),older(10)),older(20))", key_present), None, - Some(bitcoin::Sequence(20)), + Some(RelLockTime::from_height(20)), ), ( format!( @@ -1402,7 +1401,7 @@ mod tests { key_present, key_missing ), None, - Some(bitcoin::Sequence(10)), + Some(RelLockTime::from_height(10)), ), ]; @@ -1426,7 +1425,7 @@ mod tests { } } - fn check_older(&self, _: bitcoin::Sequence) -> bool { true } + fn check_older(&self, _: bitcoin::relative::LockTime) -> bool { true } fn check_after(&self, _: bitcoin::absolute::LockTime) -> bool { true } } diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index 68ae90429..bf0a696fd 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -11,14 +11,16 @@ use core::{cmp, fmt, i64, mem}; use bitcoin::hashes::hash160; use bitcoin::key::XOnlyPublicKey; use bitcoin::taproot::{ControlBlock, LeafVersion, TapLeafHash, TapNodeHash}; -use bitcoin::{absolute, ScriptBuf, Sequence}; +use bitcoin::{absolute, relative, ScriptBuf, Sequence}; use sync::Arc; use super::context::SigType; use crate::plan::AssetProvider; use crate::prelude::*; use crate::util::witness_size; -use crate::{AbsLockTime, Miniscript, MiniscriptKey, ScriptContext, Terminal, ToPublicKey}; +use crate::{ + AbsLockTime, Miniscript, MiniscriptKey, RelLockTime, ScriptContext, Terminal, ToPublicKey, +}; /// Type alias for 32 byte Preimage. pub type Preimage32 = [u8; 32]; @@ -94,7 +96,7 @@ pub trait Satisfier<Pk: MiniscriptKey + ToPublicKey> { /// NOTE: If a descriptor mixes time-based and height-based timelocks, the implementation of /// this method MUST only allow timelocks of either unit, but not both. Allowing both could cause /// miniscript to construct an invalid witness. - fn check_older(&self, _: Sequence) -> bool { false } + fn check_older(&self, _: relative::LockTime) -> bool { false } /// Assert whether a absolute locktime is satisfied /// @@ -108,41 +110,29 @@ pub trait Satisfier<Pk: MiniscriptKey + ToPublicKey> { impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for () {} impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for Sequence { - fn check_older(&self, n: Sequence) -> bool { - if !self.is_relative_lock_time() { - return false; - } - - // We need a relative lock time type in rust-bitcoin to clean this up. - - /* 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; - const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 0x00400000; - - let mask = SEQUENCE_LOCKTIME_MASK | SEQUENCE_LOCKTIME_TYPE_FLAG; - let masked_n = n.to_consensus_u32() & mask; - let masked_seq = self.to_consensus_u32() & mask; - if masked_n < SEQUENCE_LOCKTIME_TYPE_FLAG && masked_seq >= SEQUENCE_LOCKTIME_TYPE_FLAG { - false + fn check_older(&self, n: relative::LockTime) -> bool { + if let Some(lt) = self.to_relative_lock_time() { + Satisfier::<Pk>::check_older(<, n) } else { - masked_n <= masked_seq + false } } } -impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for absolute::LockTime { - fn check_after(&self, n: absolute::LockTime) -> bool { - use absolute::LockTime::*; - - match (n, *self) { - (Blocks(n), Blocks(lock_time)) => n <= lock_time, - (Seconds(n), Seconds(lock_time)) => n <= lock_time, - _ => false, // Not the same units. - } +impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for RelLockTime { + fn check_older(&self, n: relative::LockTime) -> bool { + <relative::LockTime as Satisfier<Pk>>::check_older(&(*self).into(), n) } } +impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for relative::LockTime { + fn check_older(&self, n: relative::LockTime) -> bool { n.is_implied_by(*self) } +} + +impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for absolute::LockTime { + fn check_after(&self, n: absolute::LockTime) -> bool { n.is_implied_by(*self) } +} + macro_rules! impl_satisfier_for_map_key_to_ecdsa_sig { ($(#[$($attr:meta)*])* impl Satisfier<Pk> for $map:ident<$key:ty, $val:ty>) => { $(#[$($attr)*])* @@ -323,7 +313,7 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier<Pk>> Satisfier<Pk> for &' fn lookup_hash160(&self, h: &Pk::Hash160) -> Option<Preimage32> { (**self).lookup_hash160(h) } - fn check_older(&self, t: Sequence) -> bool { (**self).check_older(t) } + fn check_older(&self, t: relative::LockTime) -> bool { (**self).check_older(t) } fn check_after(&self, n: absolute::LockTime) -> bool { (**self).check_after(n) } } @@ -383,7 +373,7 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier<Pk>> Satisfier<Pk> for &' fn lookup_hash160(&self, h: &Pk::Hash160) -> Option<Preimage32> { (**self).lookup_hash160(h) } - fn check_older(&self, t: Sequence) -> bool { (**self).check_older(t) } + fn check_older(&self, t: relative::LockTime) -> bool { (**self).check_older(t) } fn check_after(&self, n: absolute::LockTime) -> bool { (**self).check_after(n) } } @@ -529,7 +519,7 @@ macro_rules! impl_tuple_satisfier { None } - fn check_older(&self, n: Sequence) -> bool { + fn check_older(&self, n: relative::LockTime) -> bool { let &($(ref $ty,)*) = self; $( if $ty.check_older(n) { @@ -894,7 +884,7 @@ pub struct Satisfaction<T> { /// The absolute timelock used by this satisfaction pub absolute_timelock: Option<AbsLockTime>, /// The relative timelock used by this satisfaction - pub relative_timelock: Option<Sequence>, + pub relative_timelock: Option<RelLockTime>, } impl<Pk: MiniscriptKey + ToPublicKey> Satisfaction<Placeholder<Pk>> { @@ -1295,7 +1285,7 @@ impl<Pk: MiniscriptKey + ToPublicKey> Satisfaction<Placeholder<Pk>> { Satisfaction { stack, has_sig: false, relative_timelock: None, absolute_timelock } } Terminal::Older(t) => { - let (stack, relative_timelock) = if stfr.check_older(t) { + let (stack, relative_timelock) = if stfr.check_older(t.into()) { (Witness::empty(), Some(t)) } else if root_has_sig { // If the root terminal has signature, the diff --git a/src/miniscript/types/correctness.rs b/src/miniscript/types/correctness.rs index ad7597f5f..2fc490d42 100644 --- a/src/miniscript/types/correctness.rs +++ b/src/miniscript/types/correctness.rs @@ -467,13 +467,12 @@ impl Correctness { /// Constructor for the correctness properties of the `thresh` fragment // Cannot be constfn because it takes a closure. - pub fn threshold<S>(_k: usize, n: usize, mut sub_ck: S) -> Result<Self, ErrorKind> + pub fn threshold<'a, I>(_k: usize, subs: I) -> Result<Self, ErrorKind> where - S: FnMut(usize) -> Self, + I: Iterator<Item = &'a Self>, { let mut num_args = 0; - for i in 0..n { - let subtype = sub_ck(i); + for (i, subtype) in subs.enumerate() { num_args += match subtype.input { Input::Zero => 0, Input::One | Input::OneNonZero => 1, diff --git a/src/miniscript/types/extra_props.rs b/src/miniscript/types/extra_props.rs index 9d8c9274c..dbdf1657c 100644 --- a/src/miniscript/types/extra_props.rs +++ b/src/miniscript/types/extra_props.rs @@ -6,12 +6,10 @@ use core::cmp; use core::iter::once; -use bitcoin::{absolute, Sequence}; - use super::{Error, ErrorKind, ScriptContext}; use crate::miniscript::context::SigType; use crate::prelude::*; -use crate::{script_num_size, MiniscriptKey, Terminal}; +use crate::{script_num_size, AbsLockTime, MiniscriptKey, RelLockTime, Terminal}; /// Timelock information for satisfaction of a fragment. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default, Hash)] @@ -343,7 +341,7 @@ impl ExtData { } /// Extra properties for the `after` fragment. - pub fn after(t: absolute::LockTime) -> Self { + pub fn after(t: AbsLockTime) -> Self { ExtData { pk_cost: script_num_size(t.to_consensus_u32() as usize) + 1, has_free_verify: false, @@ -365,7 +363,7 @@ impl ExtData { } /// Extra properties for the `older` fragment. - pub fn older(t: Sequence) -> Self { + pub fn older(t: RelLockTime) -> Self { ExtData { pk_cost: script_num_size(t.to_consensus_u32() as usize) + 1, has_free_verify: false, @@ -923,27 +921,8 @@ impl ExtData { _ => unreachable!(), } } - Terminal::After(t) => { - // 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 == absolute::LockTime::ZERO.into() { - return Err(Error { - fragment_string: fragment.to_string(), - error: ErrorKind::InvalidTime, - }); - } - Self::after(t.into()) - } - Terminal::Older(t) => { - if t == Sequence::ZERO || !t.is_relative_lock_time() { - return Err(Error { - fragment_string: fragment.to_string(), - error: ErrorKind::InvalidTime, - }); - } - Self::older(t) - } + Terminal::After(t) => Self::after(t), + Terminal::Older(t) => Self::older(t), Terminal::Sha256(..) => Self::sha256(), Terminal::Hash256(..) => Self::hash256(), Terminal::Ripemd160(..) => Self::ripemd160(), diff --git a/src/miniscript/types/malleability.rs b/src/miniscript/types/malleability.rs index 4285e2ac3..2658e4699 100644 --- a/src/miniscript/types/malleability.rs +++ b/src/miniscript/types/malleability.rs @@ -278,19 +278,20 @@ impl Malleability { /// Constructor for the malleabilitiy properties of the `thresh` fragment. // Cannot be constfn because it takes a closure. - pub fn threshold<S>(k: usize, n: usize, mut sub_ck: S) -> Self + pub fn threshold<'a, I>(k: usize, subs: I) -> Self where - S: FnMut(usize) -> Self, + I: ExactSizeIterator<Item = &'a Self>, { + let n = subs.len(); let mut safe_count = 0; let mut all_are_dissat_unique = true; let mut all_are_non_malleable = true; - for i in 0..n { - let subtype = sub_ck(i); + for subtype in subs { safe_count += usize::from(subtype.safe); all_are_dissat_unique &= subtype.dissat == Dissat::Unique; all_are_non_malleable &= subtype.non_malleable; } + Malleability { dissat: if all_are_dissat_unique && safe_count == n { Dissat::Unique diff --git a/src/miniscript/types/mod.rs b/src/miniscript/types/mod.rs index 887aa8b8d..2ab554ca1 100644 --- a/src/miniscript/types/mod.rs +++ b/src/miniscript/types/mod.rs @@ -14,8 +14,6 @@ use core::fmt; #[cfg(feature = "std")] use std::error; -use bitcoin::{absolute, Sequence}; - pub use self::correctness::{Base, Correctness, Input}; pub use self::extra_props::ExtData; pub use self::malleability::{Dissat, Malleability}; @@ -25,8 +23,6 @@ use crate::{MiniscriptKey, Terminal}; /// Detailed type of a typechecker error #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub enum ErrorKind { - /// Relative or absolute timelock had an invalid time value (either 0, or >=0x80000000) - InvalidTime, /// Passed a `z` argument to a `d` wrapper when `z` was expected NonZeroDupIf, /// Multisignature or threshold policy had a `k` value of 0 @@ -34,12 +30,6 @@ pub enum ErrorKind { /// Multisignature or threshold policy has a `k` value in /// excess of the number of subfragments OverThreshold(usize, usize), - /// Attempted to construct a disjunction (or `andor`) for which - /// none of the child nodes were strong. This means that a 3rd - /// party could produce a satisfaction for any branch, meaning - /// that no matter which one an honest signer chooses, it is - /// possible to malleate the transaction. - NoStrongChild, /// Many fragments (all disjunctions except `or_i` as well as /// `andor` require their left child be dissatisfiable. LeftNotDissatisfiable, @@ -71,15 +61,6 @@ pub enum ErrorKind { ThresholdDissat(usize), /// The nth child of a threshold fragment was not a unit ThresholdNonUnit(usize), - /// Insufficiently many children of a threshold fragment were strong - ThresholdNotStrong { - /// Threshold parameter - k: usize, - /// Number of children - n: usize, - /// Number of strong children - n_strong: usize, - }, } /// Error type for typechecking @@ -94,11 +75,6 @@ pub struct Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.error { - ErrorKind::InvalidTime => write!( - f, - "fragment «{}» represents a timelock which value is invalid (time must be in [1; 0x80000000])", - self.fragment_string, - ), ErrorKind::NonZeroDupIf => write!( f, "fragment «{}» represents needs to be `z`, needs to consume zero elements from the stack", @@ -115,13 +91,6 @@ impl fmt::Display for Error { make sense", self.fragment_string, k, n, ), - ErrorKind::NoStrongChild => write!( - f, - "fragment «{}» requires at least one strong child \ - (a 3rd party cannot create a witness without having \ - seen one before) to prevent malleability", - self.fragment_string, - ), ErrorKind::LeftNotDissatisfiable => write!( f, "fragment «{}» requires its left child be dissatisfiable", @@ -185,17 +154,6 @@ impl fmt::Display for Error { exactly 1 on the stack given a satisfying input)", self.fragment_string, idx, ), - ErrorKind::ThresholdNotStrong { k, n, n_strong } => write!( - f, - "fragment «{}» is a {}-of-{} threshold, and needs {} of \ - its children to be strong to prevent malleability; however \ - only {} children were strong.", - self.fragment_string, - k, - n, - n - k, - n_strong, - ), } } } @@ -464,13 +422,13 @@ impl Type { /// Constructor for the type of the `thresh` fragment. // Cannot be a constfn because it takes a closure. - pub fn threshold<S>(k: usize, n: usize, mut sub_ck: S) -> Result<Self, ErrorKind> + pub fn threshold<'a, I>(k: usize, subs: I) -> Result<Self, ErrorKind> where - S: FnMut(usize) -> Self, + I: Clone + ExactSizeIterator<Item = &'a Self>, { Ok(Type { - corr: Correctness::threshold(k, n, |n| sub_ck(n).corr)?, - mall: Malleability::threshold(k, n, |n| sub_ck(n).mall), + corr: Correctness::threshold(k, subs.clone().map(|s| &s.corr))?, + mall: Malleability::threshold(k, subs.map(|s| &s.mall)), }) } } @@ -511,27 +469,8 @@ impl Type { _ => unreachable!(), } } - Terminal::After(t) => { - // 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 == absolute::LockTime::ZERO.into() { - return Err(Error { - fragment_string: fragment.to_string(), - error: ErrorKind::InvalidTime, - }); - } - Ok(Self::time()) - } - Terminal::Older(t) => { - if t == Sequence::ZERO || !t.is_relative_lock_time() { - return Err(Error { - fragment_string: fragment.to_string(), - error: ErrorKind::InvalidTime, - }); - } - Ok(Self::time()) - } + Terminal::After(_) => Ok(Self::time()), + Terminal::Older(_) => Ok(Self::time()), Terminal::Sha256(..) => Ok(Self::hash()), Terminal::Hash256(..) => Ok(Self::hash()), Terminal::Ripemd160(..) => Ok(Self::hash()), @@ -593,7 +532,7 @@ impl Type { }); } - let res = Self::threshold(k, subs.len(), |n| subs[n].ty); + let res = Self::threshold(k, subs.iter().map(|ms| &ms.ty)); res.map_err(|kind| Error { fragment_string: fragment.to_string(), error: kind }) } diff --git a/src/plan.rs b/src/plan.rs index c26fc5168..1e93274b1 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -14,15 +14,13 @@ //! Once you've obtained signatures, hash pre-images etc required by the plan, it can create a //! witness/script_sig for the input. -use core::cmp::Ordering; use core::iter::FromIterator; -use bitcoin::absolute::LockTime; use bitcoin::hashes::{hash160, ripemd160, sha256}; use bitcoin::key::XOnlyPublicKey; use bitcoin::script::PushBytesBuf; use bitcoin::taproot::{ControlBlock, LeafVersion, TapLeafHash}; -use bitcoin::{bip32, psbt, ScriptBuf, Sequence, WitnessVersion}; +use bitcoin::{absolute, bip32, psbt, relative, ScriptBuf, WitnessVersion}; use crate::descriptor::{self, Descriptor, DescriptorType, KeyMap}; use crate::miniscript::hash256; @@ -102,10 +100,10 @@ pub trait AssetProvider<Pk: MiniscriptKey> { fn provider_lookup_hash160(&self, _: &Pk::Hash160) -> bool { false } /// Assert whether a relative locktime is satisfied - fn check_older(&self, _: Sequence) -> bool { false } + fn check_older(&self, _: relative::LockTime) -> bool { false } /// Assert whether an absolute locktime is satisfied - fn check_after(&self, _: LockTime) -> bool { false } + fn check_after(&self, _: absolute::LockTime) -> bool { false } } /// Wrapper around [`Assets`] that logs every query and value returned @@ -138,8 +136,8 @@ impl AssetProvider<DefiniteDescriptorKey> for LoggerAssetProvider { impl_log_method!(provider_lookup_hash256, hash: &hash256::Hash, -> bool); impl_log_method!(provider_lookup_ripemd160, hash: &ripemd160::Hash, -> bool); impl_log_method!(provider_lookup_hash160, hash: &hash160::Hash, -> bool); - impl_log_method!(check_older, s: Sequence, -> bool); - impl_log_method!(check_after, t: LockTime, -> bool); + impl_log_method!(check_older, s: relative::LockTime, -> bool); + impl_log_method!(check_after, t: absolute::LockTime, -> bool); } impl<T, Pk> AssetProvider<Pk> for T @@ -208,9 +206,9 @@ where Satisfier::lookup_hash160(self, hash).is_some() } - fn check_older(&self, s: Sequence) -> bool { Satisfier::check_older(self, s) } + fn check_older(&self, s: relative::LockTime) -> bool { Satisfier::check_older(self, s) } - fn check_after(&self, l: LockTime) -> bool { Satisfier::check_after(self, l) } + fn check_after(&self, l: absolute::LockTime) -> bool { Satisfier::check_after(self, l) } } /// Representation of a particular spending path on a descriptor. Contains the witness template @@ -222,9 +220,9 @@ pub struct Plan { /// This plan's witness template pub(crate) template: Vec<Placeholder<DefiniteDescriptorKey>>, /// The absolute timelock this plan uses - pub absolute_timelock: Option<LockTime>, + pub absolute_timelock: Option<absolute::LockTime>, /// The relative timelock this plan uses - pub relative_timelock: Option<Sequence>, + pub relative_timelock: Option<relative::LockTime>, pub(crate) descriptor: Descriptor<DefiniteDescriptorKey>, } @@ -517,9 +515,9 @@ pub struct Assets { /// Set of available hash160 preimages pub hash160_preimages: BTreeSet<hash160::Hash>, /// Maximum absolute timelock allowed - pub absolute_timelock: Option<LockTime>, + pub absolute_timelock: Option<absolute::LockTime>, /// Maximum relative timelock allowed - pub relative_timelock: Option<Sequence>, + pub relative_timelock: Option<relative::LockTime>, } // Checks if the `pk` is a "direct child" of the `derivation_path` provided. @@ -618,23 +616,20 @@ impl AssetProvider<DefiniteDescriptorKey> for Assets { self.hash160_preimages.contains(hash) } - fn check_older(&self, s: Sequence) -> bool { - if let Some(rt) = &self.relative_timelock { - return rt.is_relative_lock_time() - && rt.is_height_locked() == s.is_height_locked() - && s <= *rt; + fn check_older(&self, s: relative::LockTime) -> bool { + if let Some(timelock) = self.relative_timelock { + s.is_implied_by(timelock) + } else { + false } - - false } - fn check_after(&self, l: LockTime) -> bool { - if let Some(at) = &self.absolute_timelock { - let cmp = l.partial_cmp(at); - return cmp == Some(Ordering::Less) || cmp == Some(Ordering::Equal); + fn check_after(&self, l: absolute::LockTime) -> bool { + if let Some(timelock) = self.absolute_timelock { + l.is_implied_by(timelock) + } else { + false } - - false } } @@ -708,13 +703,13 @@ impl Assets { } /// Set the maximum relative timelock allowed - pub fn older(mut self, seq: Sequence) -> Self { + pub fn older(mut self, seq: relative::LockTime) -> Self { self.relative_timelock = Some(seq); self } /// Set the maximum absolute timelock allowed - pub fn after(mut self, lt: LockTime) -> Self { + pub fn after(mut self, lt: absolute::LockTime) -> Self { self.absolute_timelock = Some(lt); self } @@ -746,7 +741,13 @@ mod test { keys: Vec<DescriptorPublicKey>, hashes: Vec<hash160::Hash>, // [ (key_indexes, hash_indexes, older, after, expected) ] - tests: Vec<(Vec<usize>, Vec<usize>, Option<Sequence>, Option<LockTime>, Option<usize>)>, + tests: Vec<( + Vec<usize>, + Vec<usize>, + Option<relative::LockTime>, + Option<absolute::LockTime>, + Option<usize>, + )>, ) { let desc = Descriptor::<DefiniteDescriptorKey>::from_str(desc).unwrap(); @@ -860,6 +861,8 @@ mod test { #[test] fn test_thresh() { + // relative::LockTime has no constructors except by going through Sequence + use bitcoin::Sequence; let keys = vec![ DescriptorPublicKey::from_str( "02c2fd50ceae468857bb7eb32ae9cd4083e6c7e42fbbec179d81134b3e3830586c", @@ -875,19 +878,41 @@ mod test { let tests = vec![ (vec![], vec![], None, None, None), - (vec![], vec![], Some(Sequence(1000)), None, None), + ( + vec![], + vec![], + Some(Sequence(1000).to_relative_lock_time().unwrap()), + None, + None, + ), (vec![0], vec![], None, None, None), // expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) + 1 (OP_0) + 1 (OP_ZERO) - (vec![0], vec![], Some(Sequence(1000)), None, Some(80)), + ( + vec![0], + vec![], + Some(Sequence(1000).to_relative_lock_time().unwrap()), + None, + Some(80), + ), // expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) * 2 + 2 (OP_PUSHBYTE_1 0x01) (vec![0, 1], vec![], None, None, Some(153)), // expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) + 1 (OP_0) + 1 (OP_ZERO) - (vec![0, 1], vec![], Some(Sequence(1000)), None, Some(80)), + ( + vec![0, 1], + vec![], + Some(Sequence(1000).to_relative_lock_time().unwrap()), + None, + Some(80), + ), // expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) * 2 + 2 (OP_PUSHBYTE_1 0x01) ( vec![0, 1], vec![], - Some(Sequence::from_512_second_intervals(10)), + Some( + Sequence::from_512_second_intervals(10) + .to_relative_lock_time() + .unwrap(), + ), None, Some(153), ), // incompatible timelock @@ -899,13 +924,19 @@ mod test { let tests = vec![ // expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) + 1 (OP_0) + 1 (OP_ZERO) - (vec![0], vec![], None, Some(LockTime::from_height(1000).unwrap()), Some(80)), + ( + vec![0], + vec![], + None, + Some(absolute::LockTime::from_height(1000).unwrap()), + Some(80), + ), // expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) * 2 + 2 (OP_PUSHBYTE_1 0x01) ( vec![0, 1], vec![], None, - Some(LockTime::from_time(500_001_000).unwrap()), + Some(absolute::LockTime::from_time(500_001_000).unwrap()), Some(153), ), // incompatible timelock ]; @@ -989,15 +1020,21 @@ mod test { vec![4], vec![], None, - Some(LockTime::from_height(10).unwrap()), + Some(absolute::LockTime::from_height(10).unwrap()), third_leaf_sat_weight, ), // Spend with third leaf (key + timelock), // but timelock is too low (=impossible) - (vec![4], vec![], None, Some(LockTime::from_height(9).unwrap()), None), + (vec![4], vec![], None, Some(absolute::LockTime::from_height(9).unwrap()), None), // Spend with third leaf (key + timelock), // but timelock is in the wrong unit (=impossible) - (vec![4], vec![], None, Some(LockTime::from_time(1296000000).unwrap()), None), + ( + vec![4], + vec![], + None, + Some(absolute::LockTime::from_time(1296000000).unwrap()), + None, + ), // Spend with third leaf (key + timelock), // but don't give the timelock (=impossible) (vec![4], vec![], None, None, None), @@ -1012,7 +1049,7 @@ mod test { vec![2, 3, 4], vec![], None, - Some(LockTime::from_consensus(11)), + Some(absolute::LockTime::from_consensus(11)), third_leaf_sat_weight, ), ]; diff --git a/src/policy/compiler.rs b/src/policy/compiler.rs index c24946d57..d9ec92455 100644 --- a/src/policy/compiler.rs +++ b/src/policy/compiler.rs @@ -9,7 +9,6 @@ use core::{cmp, f64, fmt, hash, mem}; #[cfg(feature = "std")] use std::error; -use bitcoin::{absolute, Sequence}; use sync::Arc; use crate::miniscript::context::SigType; @@ -446,27 +445,8 @@ impl CompilerExtData { _ => unreachable!(), } } - Terminal::After(t) => { - // 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 == absolute::LockTime::ZERO.into() { - return Err(types::Error { - fragment_string: fragment.to_string(), - error: types::ErrorKind::InvalidTime, - }); - } - Ok(Self::time()) - } - Terminal::Older(t) => { - if t == Sequence::ZERO || !t.is_relative_lock_time() { - return Err(types::Error { - fragment_string: fragment.to_string(), - error: types::ErrorKind::InvalidTime, - }); - } - Ok(Self::time()) - } + Terminal::After(_) => Ok(Self::time()), + Terminal::Older(_) => Ok(Self::time()), Terminal::Sha256(..) => Ok(Self::hash()), Terminal::Hash256(..) => Ok(Self::hash()), Terminal::Ripemd160(..) => Ok(Self::hash()), @@ -1265,7 +1245,7 @@ mod tests { use super::*; use crate::miniscript::{Legacy, Segwitv0, Tap}; use crate::policy::Liftable; - use crate::{script_num_size, ToPublicKey}; + use crate::{script_num_size, AbsLockTime, RelLockTime, ToPublicKey}; type SPolicy = Concrete<String>; type BPolicy = Concrete<bitcoin::PublicKey>; @@ -1311,8 +1291,8 @@ mod tests { let pol: SPolicy = Concrete::And(vec![ Arc::new(Concrete::Key("A".to_string())), Arc::new(Concrete::And(vec![ - Arc::new(Concrete::after(9)), - Arc::new(Concrete::after(1000_000_000)), + Arc::new(Concrete::After(AbsLockTime::from_consensus(9).unwrap())), + Arc::new(Concrete::After(AbsLockTime::from_consensus(1_000_000_000).unwrap())), ])), ]); assert!(pol.compile::<Segwitv0>().is_err()); @@ -1417,7 +1397,7 @@ mod tests { ( 1, Arc::new(Concrete::And(vec![ - Arc::new(Concrete::Older(Sequence::from_height(10000))), + Arc::new(Concrete::Older(RelLockTime::from_height(10000))), Arc::new(Concrete::Threshold( 2, key_pol[5..8].iter().map(|p| (p.clone()).into()).collect(), @@ -1447,13 +1427,13 @@ mod tests { let mut abs = policy.lift().unwrap(); assert_eq!(abs.n_keys(), 8); assert_eq!(abs.minimum_n_keys(), Some(2)); - abs = abs.at_age(Sequence::from_height(10000)); + abs = abs.at_age(RelLockTime::from_height(10000).into()); assert_eq!(abs.n_keys(), 8); assert_eq!(abs.minimum_n_keys(), Some(2)); - abs = abs.at_age(Sequence::from_height(9999)); + abs = abs.at_age(RelLockTime::from_height(9999).into()); assert_eq!(abs.n_keys(), 5); assert_eq!(abs.minimum_n_keys(), Some(3)); - abs = abs.at_age(Sequence::ZERO); + abs = abs.at_age(RelLockTime::ZERO.into()); assert_eq!(abs.n_keys(), 5); assert_eq!(abs.minimum_n_keys(), Some(3)); @@ -1478,15 +1458,16 @@ mod tests { assert!(ms.satisfy(no_sat).is_err()); assert!(ms.satisfy(&left_sat).is_ok()); assert!(ms - .satisfy((&right_sat, Sequence::from_height(10001))) + .satisfy((&right_sat, RelLockTime::from_height(10001))) .is_ok()); //timelock not met assert!(ms - .satisfy((&right_sat, Sequence::from_height(9999))) + .satisfy((&right_sat, RelLockTime::from_height(9999))) .is_err()); assert_eq!( - ms.satisfy((left_sat, Sequence::from_height(9999))).unwrap(), + ms.satisfy((left_sat, RelLockTime::from_height(9999))) + .unwrap(), vec![ // sat for left branch vec![], @@ -1497,7 +1478,7 @@ mod tests { ); assert_eq!( - ms.satisfy((right_sat, Sequence::from_height(10000))) + ms.satisfy((right_sat, RelLockTime::from_height(10000))) .unwrap(), vec![ // sat for right branch diff --git a/src/policy/concrete.rs b/src/policy/concrete.rs index bfb40a85f..75481ff1d 100644 --- a/src/policy/concrete.rs +++ b/src/policy/concrete.rs @@ -7,7 +7,7 @@ use core::{fmt, str}; #[cfg(feature = "std")] use std::error; -use bitcoin::{absolute, Sequence}; +use bitcoin::absolute; #[cfg(feature = "compiler")] use { crate::descriptor::TapTree, @@ -29,7 +29,9 @@ use crate::prelude::*; use crate::sync::Arc; #[cfg(all(doc, not(feature = "compiler")))] use crate::Descriptor; -use crate::{errstr, AbsLockTime, Error, ForEachKey, FromStrKey, MiniscriptKey, Translator}; +use crate::{ + errstr, AbsLockTime, Error, ForEachKey, FromStrKey, MiniscriptKey, RelLockTime, Translator, +}; /// Maximum TapLeafs allowed in a compiled TapTree #[cfg(feature = "compiler")] @@ -52,7 +54,7 @@ pub enum Policy<Pk: MiniscriptKey> { /// An absolute locktime restriction. After(AbsLockTime), /// A relative locktime restriction. - Older(Sequence), + Older(RelLockTime), /// A SHA256 whose preimage must be provided to satisfy the descriptor. Sha256(Pk::Sha256), /// A SHA256d whose preimage must be provided to satisfy the descriptor. @@ -70,21 +72,6 @@ pub enum Policy<Pk: MiniscriptKey> { Threshold(usize, Vec<Arc<Policy<Pk>>>), } -impl<Pk> Policy<Pk> -where - Pk: MiniscriptKey, -{ - /// Construct a `Policy::After` from `n`. Helper function equivalent to - /// `Policy::After(absolute::LockTime::from_consensus(n))`. - pub fn after(n: u32) -> Policy<Pk> { - Policy::After(AbsLockTime::from(absolute::LockTime::from_consensus(n))) - } - - /// Construct a `Policy::Older` from `n`. Helper function equivalent to - /// `Policy::Older(Sequence::from_consensus(n))`. - pub fn older(n: u32) -> Policy<Pk> { Policy::Older(Sequence::from_consensus(n)) } -} - /// Detailed error type for concrete policies. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum PolicyError { @@ -94,10 +81,6 @@ pub enum PolicyError { NonBinaryArgOr, /// `Thresh` fragment can only have `1<=k<=n`. IncorrectThresh, - /// `older` or `after` fragment can only have `n = 0`. - ZeroTime, - /// `after` fragment can only have `n < 2^31`. - TimeTooFar, /// Semantic Policy Error: `And` `Or` fragments must take args: `k > 1`. InsufficientArgsforAnd, /// Semantic policy error: `And` `Or` fragments must take args: `k > 1`. @@ -135,10 +118,6 @@ impl fmt::Display for PolicyError { PolicyError::IncorrectThresh => { f.write_str("Threshold k must be greater than 0 and less than or equal to n 0<k<=n") } - PolicyError::TimeTooFar => { - f.write_str("Relative/Absolute time must be less than 2^31; n < 2^31") - } - PolicyError::ZeroTime => f.write_str("Time must be greater than 0; n > 0"), PolicyError::InsufficientArgsforAnd => { f.write_str("Semantic Policy 'And' fragment must have at least 2 args ") } @@ -165,8 +144,6 @@ impl error::Error for PolicyError { NonBinaryArgAnd | NonBinaryArgOr | IncorrectThresh - | ZeroTime - | TimeTooFar | InsufficientArgsforAnd | InsufficientArgsforOr | EntailmentMaxTerminals @@ -756,20 +733,8 @@ impl<Pk: MiniscriptKey> Policy<Pk> { for policy in self.pre_order_iter() { match *policy { - After(n) => { - if n == absolute::LockTime::ZERO.into() { - return Err(PolicyError::ZeroTime); - } else if n.to_u32() > 2u32.pow(31) { - return Err(PolicyError::TimeTooFar); - } - } - Older(n) => { - if n == Sequence::ZERO { - return Err(PolicyError::ZeroTime); - } else if n.to_consensus_u32() > 2u32.pow(31) { - return Err(PolicyError::TimeTooFar); - } - } + After(_) => {} + Older(_) => {} And(ref subs) => { if subs.len() != 2 { return Err(PolicyError::NonBinaryArgAnd); @@ -978,24 +943,16 @@ impl<Pk: FromStrKey> Policy<Pk> { ("UNSATISFIABLE", 0) => Ok(Policy::Unsatisfiable), ("TRIVIAL", 0) => Ok(Policy::Trivial), ("pk", 1) => expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Policy::Key)), - ("after", 1) => { - let num = expression::terminal(&top.args[0], expression::parse_num)?; - if num > 2u32.pow(31) { - return Err(Error::PolicyError(PolicyError::TimeTooFar)); - } else if num == 0 { - return Err(Error::PolicyError(PolicyError::ZeroTime)); - } - Ok(Policy::after(num)) - } - ("older", 1) => { - let num = expression::terminal(&top.args[0], expression::parse_num)?; - if num > 2u32.pow(31) { - return Err(Error::PolicyError(PolicyError::TimeTooFar)); - } else if num == 0 { - return Err(Error::PolicyError(PolicyError::ZeroTime)); - } - Ok(Policy::older(num)) - } + ("after", 1) => expression::terminal(&top.args[0], |x| { + expression::parse_num(x) + .and_then(|x| AbsLockTime::from_consensus(x).map_err(Error::AbsoluteLockTime)) + .map(Policy::After) + }), + ("older", 1) => expression::terminal(&top.args[0], |x| { + expression::parse_num(x) + .and_then(|x| RelLockTime::from_consensus(x).map_err(Error::RelativeLockTime)) + .map(Policy::Older) + }), ("sha256", 1) => expression::terminal(&top.args[0], |x| { <Pk::Sha256 as core::str::FromStr>::from_str(x).map(Policy::Sha256) }), diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 8eb24b617..99dade57e 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -241,13 +241,12 @@ impl<Pk: MiniscriptKey> Liftable<Pk> for Arc<Concrete<Pk>> { mod tests { use core::str::FromStr; - use bitcoin::Sequence; - use super::*; #[cfg(feature = "compiler")] use crate::descriptor::Tr; use crate::miniscript::context::Segwitv0; use crate::prelude::*; + use crate::RelLockTime; #[cfg(feature = "compiler")] use crate::{descriptor::TapTree, Tap}; @@ -323,18 +322,20 @@ mod tests { ConcretePol::from_str("or(pk())").unwrap_err().to_string(), "Or policy fragment must take 2 arguments" ); + // these weird "unexpected" wrapping of errors will go away in a later PR + // which rewrites the expression parser assert_eq!( ConcretePol::from_str("thresh(3,after(0),pk(),pk())") .unwrap_err() .to_string(), - "Time must be greater than 0; n > 0" + "unexpected «absolute locktimes in Miniscript have a minimum value of 1»", ); assert_eq!( ConcretePol::from_str("thresh(2,older(2147483650),pk(),pk())") .unwrap_err() .to_string(), - "Relative/Absolute time must be less than 2^31; n < 2^31" + "unexpected «locktime value 2147483650 is not a valid BIP68 relative locktime»" ); } @@ -368,7 +369,7 @@ mod tests { 2, vec![ Arc::new(Semantic::Key(key_a)), - Arc::new(Semantic::Older(Sequence::from_height(42))) + Arc::new(Semantic::Older(RelLockTime::from_height(42))) ] )), Arc::new(Semantic::Key(key_b)) diff --git a/src/policy/semantic.rs b/src/policy/semantic.rs index 8b00d0bd8..3895e0552 100644 --- a/src/policy/semantic.rs +++ b/src/policy/semantic.rs @@ -8,7 +8,7 @@ use core::str::FromStr; use core::{fmt, str}; -use bitcoin::{absolute, Sequence}; +use bitcoin::{absolute, relative}; use super::concrete::PolicyError; use super::ENTAILMENT_MAX_TERMINALS; @@ -16,7 +16,8 @@ use crate::iter::{Tree, TreeLike}; use crate::prelude::*; use crate::sync::Arc; use crate::{ - errstr, expression, AbsLockTime, Error, ForEachKey, FromStrKey, MiniscriptKey, Translator, + errstr, expression, AbsLockTime, Error, ForEachKey, FromStrKey, MiniscriptKey, RelLockTime, + Translator, }; /// Abstract policy which corresponds to the semantics of a miniscript and @@ -36,7 +37,7 @@ pub enum Policy<Pk: MiniscriptKey> { /// An absolute locktime restriction. After(AbsLockTime), /// A relative locktime restriction. - Older(Sequence), + Older(RelLockTime), /// A SHA256 whose preimage must be provided to satisfy the descriptor. Sha256(Pk::Sha256), /// A SHA256d whose preimage must be provided to satisfy the descriptor. @@ -49,23 +50,6 @@ pub enum Policy<Pk: MiniscriptKey> { Threshold(usize, Vec<Arc<Policy<Pk>>>), } -impl<Pk> Policy<Pk> -where - Pk: MiniscriptKey, -{ - /// Constructs a `Policy::After` from `n`. - /// - /// Helper function equivalent to `Policy::After(absolute::LockTime::from_consensus(n))`. - pub fn after(n: u32) -> Policy<Pk> { - Policy::After(AbsLockTime::from(absolute::LockTime::from_consensus(n))) - } - - /// Construct a `Policy::Older` from `n`. - /// - /// Helper function equivalent to `Policy::Older(Sequence::from_consensus(n))`. - pub fn older(n: u32) -> Policy<Pk> { Policy::Older(Sequence::from_consensus(n)) } -} - impl<Pk: MiniscriptKey> ForEachKey<Pk> for Policy<Pk> { fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, mut pred: F) -> bool { self.pre_order_iter().all(|policy| match policy { @@ -329,10 +313,14 @@ impl<Pk: FromStrKey> expression::FromTree for Policy<Pk> { ("TRIVIAL", 0) => Ok(Policy::Trivial), ("pk", 1) => expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Policy::Key)), ("after", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x).map(|x| Policy::after(x)) + expression::parse_num(x) + .and_then(|x| AbsLockTime::from_consensus(x).map_err(Error::AbsoluteLockTime)) + .map(Policy::After) }), ("older", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x).map(|x| Policy::older(x)) + expression::parse_num(x) + .and_then(|x| RelLockTime::from_consensus(x).map_err(Error::RelativeLockTime)) + .map(Policy::Older) }), ("sha256", 1) => { expression::terminal(&top.args[0], |x| Pk::Sha256::from_str(x).map(Policy::Sha256)) @@ -500,7 +488,7 @@ impl<Pk: MiniscriptKey> Policy<Pk> { fn real_absolute_timelocks(&self) -> Vec<u32> { self.pre_order_iter() .filter_map(|policy| match policy { - Policy::After(t) => Some(t.to_u32()), + Policy::After(t) => Some(t.to_consensus_u32()), _ => None, }) .collect() @@ -517,7 +505,7 @@ impl<Pk: MiniscriptKey> Policy<Pk> { /// Filters a policy by eliminating relative timelock constraints /// that are not satisfied at the given `age`. - pub fn at_age(self, age: Sequence) -> Policy<Pk> { + pub fn at_age(self, age: relative::LockTime) -> Policy<Pk> { use Policy::*; let mut at_age = vec![]; @@ -526,13 +514,10 @@ impl<Pk: MiniscriptKey> Policy<Pk> { let new_policy = match data.node.as_ref() { Older(ref t) => { - if t.is_height_locked() && age.is_time_locked() - || t.is_time_locked() && age.is_height_locked() - || t.to_consensus_u32() > age.to_consensus_u32() - { - Some(Policy::Unsatisfiable) - } else { + if relative::LockTime::from(*t).is_implied_by(age) { Some(Policy::Older(*t)) + } else { + Some(Policy::Unsatisfiable) } } Threshold(k, ref subs) => { @@ -555,7 +540,6 @@ impl<Pk: MiniscriptKey> Policy<Pk> { /// Filters a policy by eliminating absolute timelock constraints /// that are not satisfied at the given `n` (`n OP_CHECKLOCKTIMEVERIFY`). pub fn at_lock_time(self, n: absolute::LockTime) -> Policy<Pk> { - use absolute::LockTime::*; use Policy::*; let mut at_age = vec![]; @@ -564,16 +548,10 @@ impl<Pk: MiniscriptKey> Policy<Pk> { let new_policy = match data.node.as_ref() { After(t) => { - let t = absolute::LockTime::from(*t); - let is_satisfied_by = match (t, n) { - (Blocks(t), Blocks(n)) => t <= n, - (Seconds(t), Seconds(n)) => t <= n, - _ => false, - }; - if !is_satisfied_by { - Some(Unsatisfiable) + if absolute::LockTime::from(*t).is_implied_by(n) { + Some(After(*t)) } else { - Some(After(t.into())) + Some(Unsatisfiable) } } Threshold(k, ref subs) => { @@ -734,19 +712,32 @@ mod tests { assert_eq!(policy, Policy::Key("".to_owned())); assert_eq!(policy.relative_timelocks(), vec![]); assert_eq!(policy.absolute_timelocks(), vec![]); - assert_eq!(policy.clone().at_age(Sequence::ZERO), policy); - assert_eq!(policy.clone().at_age(Sequence::from_height(10000)), policy); + assert_eq!(policy.clone().at_age(RelLockTime::ZERO.into()), policy); + assert_eq!( + policy + .clone() + .at_age(RelLockTime::from_height(10000).into()), + policy + ); assert_eq!(policy.n_keys(), 1); assert_eq!(policy.minimum_n_keys(), Some(1)); let policy = StringPolicy::from_str("older(1000)").unwrap(); - assert_eq!(policy, Policy::Older(Sequence::from_height(1000))); + assert_eq!(policy, Policy::Older(RelLockTime::from_height(1000))); assert_eq!(policy.absolute_timelocks(), vec![]); assert_eq!(policy.relative_timelocks(), vec![1000]); - assert_eq!(policy.clone().at_age(Sequence::ZERO), Policy::Unsatisfiable); - assert_eq!(policy.clone().at_age(Sequence::from_height(999)), Policy::Unsatisfiable); - assert_eq!(policy.clone().at_age(Sequence::from_height(1000)), policy); - assert_eq!(policy.clone().at_age(Sequence::from_height(10000)), policy); + assert_eq!(policy.clone().at_age(RelLockTime::ZERO.into()), Policy::Unsatisfiable); + assert_eq!( + policy.clone().at_age(RelLockTime::from_height(999).into()), + Policy::Unsatisfiable + ); + assert_eq!(policy.clone().at_age(RelLockTime::from_height(1000).into()), policy); + assert_eq!( + policy + .clone() + .at_age(RelLockTime::from_height(10000).into()), + policy + ); assert_eq!(policy.n_keys(), 0); assert_eq!(policy.minimum_n_keys(), Some(0)); @@ -757,17 +748,25 @@ mod tests { 1, vec![ Policy::Key("".to_owned()).into(), - Policy::Older(Sequence::from_height(1000)).into(), + Policy::Older(RelLockTime::from_height(1000)).into(), ] ) ); assert_eq!(policy.relative_timelocks(), vec![1000]); assert_eq!(policy.absolute_timelocks(), vec![]); - assert_eq!(policy.clone().at_age(Sequence::ZERO), Policy::Key("".to_owned())); - assert_eq!(policy.clone().at_age(Sequence::from_height(999)), Policy::Key("".to_owned())); - assert_eq!(policy.clone().at_age(Sequence::from_height(1000)), policy.clone().normalized()); + assert_eq!(policy.clone().at_age(RelLockTime::ZERO.into()), Policy::Key("".to_owned())); assert_eq!( - policy.clone().at_age(Sequence::from_height(10000)), + policy.clone().at_age(RelLockTime::from_height(999).into()), + Policy::Key("".to_owned()) + ); + assert_eq!( + policy.clone().at_age(RelLockTime::from_height(1000).into()), + policy.clone().normalized() + ); + assert_eq!( + policy + .clone() + .at_age(RelLockTime::from_height(10000).into()), policy.clone().normalized() ); assert_eq!(policy.n_keys(), 1); @@ -816,11 +815,11 @@ mod tests { Policy::Threshold( 2, vec![ - Policy::Older(Sequence::from_height(1000)).into(), - Policy::Older(Sequence::from_height(10000)).into(), - Policy::Older(Sequence::from_height(1000)).into(), - Policy::Older(Sequence::from_height(2000)).into(), - Policy::Older(Sequence::from_height(2000)).into(), + Policy::Older(RelLockTime::from_height(1000)).into(), + Policy::Older(RelLockTime::from_height(10000)).into(), + Policy::Older(RelLockTime::from_height(1000)).into(), + Policy::Older(RelLockTime::from_height(2000)).into(), + Policy::Older(RelLockTime::from_height(2000)).into(), ] ) ); @@ -840,9 +839,9 @@ mod tests { Policy::Threshold( 2, vec![ - Policy::Older(Sequence::from_height(1000)).into(), - Policy::Older(Sequence::from_height(10000)).into(), - Policy::Older(Sequence::from_height(1000)).into(), + Policy::Older(RelLockTime::from_height(1000)).into(), + Policy::Older(RelLockTime::from_height(10000)).into(), + Policy::Older(RelLockTime::from_height(1000)).into(), Policy::Unsatisfiable.into(), Policy::Unsatisfiable.into(), ] @@ -857,7 +856,7 @@ mod tests { // Block height 1000. let policy = StringPolicy::from_str("after(1000)").unwrap(); - assert_eq!(policy, Policy::after(1000)); + assert_eq!(policy, Policy::After(AbsLockTime::from_consensus(1000).unwrap())); assert_eq!(policy.absolute_timelocks(), vec![1000]); assert_eq!(policy.relative_timelocks(), vec![]); assert_eq!(policy.clone().at_lock_time(absolute::LockTime::ZERO), Policy::Unsatisfiable); @@ -891,7 +890,7 @@ mod tests { // UNIX timestamp of 10 seconds after the epoch. let policy = StringPolicy::from_str("after(500000010)").unwrap(); - assert_eq!(policy, Policy::after(500_000_010)); + assert_eq!(policy, Policy::After(AbsLockTime::from_consensus(500_000_010).unwrap())); assert_eq!(policy.absolute_timelocks(), vec![500_000_010]); assert_eq!(policy.relative_timelocks(), vec![]); // Pass a block height to at_lock_time while policy uses a UNIX timestapm. @@ -959,7 +958,11 @@ mod tests { // test liquid backup policy before the emergency timeout let backup_policy = StringPolicy::from_str("thresh(2,pk(A),pk(B),pk(C))").unwrap(); assert!(!backup_policy - .entails(liquid_pol.clone().at_age(Sequence::from_height(4095))) + .entails( + liquid_pol + .clone() + .at_age(RelLockTime::from_height(4095).into()) + ) .unwrap()); // Finally test both spending paths diff --git a/src/primitives/absolute_locktime.rs b/src/primitives/absolute_locktime.rs new file mode 100644 index 000000000..e2373d781 --- /dev/null +++ b/src/primitives/absolute_locktime.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Absolute Locktimes + +use core::{cmp, fmt}; + +use bitcoin::absolute; + +/// Maximum allowed absolute locktime value. +pub const MAX_ABSOLUTE_LOCKTIME: u32 = 0x8000_0000; + +/// Minimum allowed absolute locktime value. +/// +/// In Bitcoin 0 is an allowed value, but in Miniscript it is not, because we +/// (ab)use the locktime value as a boolean in our Script fragments, and avoiding +/// this would reduce efficiency. +pub const MIN_ABSOLUTE_LOCKTIME: u32 = 1; + +/// Error parsing an absolute locktime. +#[derive(Debug, PartialEq)] +pub struct AbsLockTimeError { + value: u32, +} + +impl fmt::Display for AbsLockTimeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.value < MIN_ABSOLUTE_LOCKTIME { + f.write_str("absolute locktimes in Miniscript have a minimum value of 1") + } else { + debug_assert!(self.value > MAX_ABSOLUTE_LOCKTIME); + write!( + f, + "absolute locktimes in Miniscript have a maximum value of 0x{:08x}; got 0x{:08x}", + MAX_ABSOLUTE_LOCKTIME, self.value + ) + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for AbsLockTimeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } +} + +/// An absolute locktime that implements `Ord`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct AbsLockTime(absolute::LockTime); + +impl AbsLockTime { + /// Constructs an `AbsLockTime` from an nLockTime value or the argument to `CHEKCLOCKTIMEVERIFY`. + pub fn from_consensus(n: u32) -> Result<Self, AbsLockTimeError> { + if n >= MIN_ABSOLUTE_LOCKTIME && n <= MAX_ABSOLUTE_LOCKTIME { + Ok(AbsLockTime(absolute::LockTime::from_consensus(n))) + } else { + Err(AbsLockTimeError { value: n }) + } + } + + /// Returns the inner `u32` value. This is the value used when creating this `LockTime` + /// i.e., `n OP_CHECKLOCKTIMEVERIFY` or nLockTime. + /// + /// This calls through to `absolute::LockTime::to_consensus_u32()` and the same usage warnings + /// apply. + pub fn to_consensus_u32(self) -> u32 { self.0.to_consensus_u32() } + + /// Whether this is a height-based locktime. + pub fn is_block_height(&self) -> bool { self.0.is_block_height() } + + /// Whether this is a time-based locktime. + pub fn is_block_time(&self) -> bool { self.0.is_block_time() } +} + +impl From<AbsLockTime> for absolute::LockTime { + fn from(lock_time: AbsLockTime) -> absolute::LockTime { lock_time.0 } +} + +impl cmp::PartialOrd for AbsLockTime { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { Some(self.cmp(other)) } +} + +impl cmp::Ord for AbsLockTime { + fn cmp(&self, other: &Self) -> cmp::Ordering { + let this = self.0.to_consensus_u32(); + let that = other.0.to_consensus_u32(); + this.cmp(&that) + } +} + +impl fmt::Display for AbsLockTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } +} diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs new file mode 100644 index 000000000..84406f182 --- /dev/null +++ b/src/primitives/mod.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Primitive Types +//! +//! In Miniscript we have a few types which have stronger constraints than +//! their equivalents in Bitcoin (or Rust). In particular, locktimes which +//! appear in `after` and `older` fragments are constrained to be nonzero, +//! and the relative locktimes in `older` fragments are only allowed to be +//! the subset of sequence numbers which form valid locktimes. +//! +//! This module exists for code organization and any types defined here +//! should be re-exported at the crate root. + +pub mod absolute_locktime; +pub mod relative_locktime; diff --git a/src/primitives/relative_locktime.rs b/src/primitives/relative_locktime.rs new file mode 100644 index 000000000..05239a498 --- /dev/null +++ b/src/primitives/relative_locktime.rs @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Relative Locktimes + +use core::{cmp, convert, fmt}; + +use bitcoin::{relative, Sequence}; + +/// Error parsing an absolute locktime. +#[derive(Debug, PartialEq)] +pub struct RelLockTimeError { + value: u32, +} + +impl fmt::Display for RelLockTimeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.value == 0 { + f.write_str("relative locktimes in Miniscript have a minimum value of 1") + } else { + debug_assert!(Sequence::from_consensus(self.value) + .to_relative_lock_time() + .is_none()); + write!(f, "locktime value {} is not a valid BIP68 relative locktime", self.value) + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RelLockTimeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } +} + +/// A relative locktime which implements `Ord`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct RelLockTime(Sequence); + +impl RelLockTime { + /// The "0 blocks" constant. + pub const ZERO: Self = RelLockTime(Sequence::ZERO); + + /// Constructs an `RelLockTime` from an nLockTime value or the argument to `CHEKCLOCKTIMEVERIFY`. + pub fn from_consensus(n: u32) -> Result<Self, RelLockTimeError> { + convert::TryFrom::try_from(Sequence::from_consensus(n)) + } + + /// Returns the inner `u32` value. This is the value used when creating this `LockTime` + /// i.e., `n OP_CHECKSEQUENCEVERIFY` or `nSequence`. + pub fn to_consensus_u32(self) -> u32 { self.0.to_consensus_u32() } + + /// Takes a 16-bit number of blocks and produces a relative locktime from it. + pub fn from_height(height: u16) -> Self { RelLockTime(Sequence::from_height(height)) } + + /// Takes a 16-bit number of 512-second time intervals and produces a relative locktime from it. + pub fn from_512_second_intervals(time: u16) -> Self { + RelLockTime(Sequence::from_512_second_intervals(time)) + } + + /// Whether this timelock is blockheight-based. + pub fn is_height_locked(&self) -> bool { self.0.is_height_locked() } + + /// Whether this timelock is time-based. + pub fn is_time_locked(&self) -> bool { self.0.is_time_locked() } +} + +impl convert::TryFrom<Sequence> for RelLockTime { + type Error = RelLockTimeError; + fn try_from(seq: Sequence) -> Result<Self, RelLockTimeError> { + if seq.is_relative_lock_time() { + Ok(RelLockTime(seq)) + } else { + Err(RelLockTimeError { value: seq.to_consensus_u32() }) + } + } +} + +impl From<RelLockTime> for Sequence { + fn from(lock_time: RelLockTime) -> Sequence { lock_time.0 } +} + +impl From<RelLockTime> for relative::LockTime { + fn from(lock_time: RelLockTime) -> relative::LockTime { + lock_time.0.to_relative_lock_time().unwrap() + } +} + +impl cmp::PartialOrd for RelLockTime { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { Some(self.cmp(other)) } +} + +impl cmp::Ord for RelLockTime { + fn cmp(&self, other: &Self) -> cmp::Ordering { + let this = self.0.to_consensus_u32(); + let that = other.0.to_consensus_u32(); + this.cmp(&that) + } +} + +impl fmt::Display for RelLockTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } +} diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index 9e7027193..61027d6ad 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -19,7 +19,7 @@ use bitcoin::secp256k1; use bitcoin::secp256k1::{Secp256k1, VerifyOnly}; use bitcoin::sighash::{self, SighashCache}; use bitcoin::taproot::{self, ControlBlock, LeafVersion, TapLeafHash}; -use bitcoin::{absolute, bip32, transaction, Script, ScriptBuf, Sequence}; +use bitcoin::{absolute, bip32, relative, transaction, Script, ScriptBuf}; use crate::miniscript::context::SigType; use crate::prelude::*; @@ -324,15 +324,9 @@ impl<'psbt, Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for PsbtInputSatisfie <dyn Satisfier<Pk>>::check_after(&lock_time, n) } - fn check_older(&self, n: Sequence) -> bool { + fn check_older(&self, n: relative::LockTime) -> bool { let seq = self.psbt.unsigned_tx.input[self.index].sequence; - // https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki - // Disable flag set => return true. - if !n.is_relative_lock_time() { - return true; - } - if self.psbt.unsigned_tx.version < transaction::Version::TWO || !seq.is_relative_lock_time() { return false;