From 363b438779dda0f4602c5ae2b9a21d5e6f5f85fa Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Mon, 25 Jul 2022 15:49:55 +1000 Subject: [PATCH] Depend on bitcoin 0.29.0 Depend on the newly released bitcoin 0.29.0 --- Cargo.toml | 10 +- examples/sign_multisig.rs | 6 +- examples/taproot.rs | 2 +- examples/verify_tx.rs | 5 +- src/descriptor/mod.rs | 20 +-- src/descriptor/tr.rs | 23 +-- src/interpreter/error.rs | 8 ++ src/interpreter/mod.rs | 37 ++--- src/interpreter/stack.rs | 32 +++-- src/lib.rs | 1 - src/miniscript/astelem.rs | 15 +- src/miniscript/decode.rs | 9 +- src/miniscript/hash256.rs | 18 ++- src/miniscript/limits.rs | 19 --- src/miniscript/mod.rs | 10 +- src/miniscript/satisfy.rs | 64 ++++----- src/miniscript/types/extra_props.rs | 27 ++-- src/miniscript/types/mod.rs | 27 ++-- src/policy/compiler.rs | 27 ++-- src/policy/concrete.rs | 50 +++++-- src/policy/mod.rs | 4 +- src/policy/semantic.rs | 208 +++++++++++++++++++++------- src/psbt/finalizer.rs | 2 +- src/psbt/mod.rs | 47 +++---- src/test_utils.rs | 5 +- src/timelock.rs | 21 --- tests/setup/test_util.rs | 4 +- tests/test_cpp.rs | 12 +- tests/test_desc.rs | 19 +-- 29 files changed, 426 insertions(+), 306 deletions(-) delete mode 100644 src/timelock.rs diff --git a/Cargo.toml b/Cargo.toml index 668b4c944..cf511fb87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,22 +16,22 @@ std = ["bitcoin/std", "bitcoin/secp-recovery"] no-std = ["hashbrown", "bitcoin/no-std"] compiler = [] trace = [] + unstable = [] -serde = ["actual-serde", "bitcoin/use-serde"] +serde = ["actual-serde", "bitcoin/serde"] rand = ["bitcoin/rand"] [dependencies] -bitcoin = { version = "0.28.1", default-features = false } +bitcoin = { version = "0.29.1", default-features = false } hashbrown = { version = "0.11", optional = true } # Do NOT use this as a feature! Use the `serde` feature instead. actual-serde = { package = "serde", version = "1.0", optional = true } - [dev-dependencies] -bitcoind = {version = "0.26.1", features=["22_0"]} +bitcoind = { version = "0.27.0", features=["23_0"] } actual-rand = { package = "rand", version = "0.8.4"} -secp256k1 = {version = "0.22.1", features = ["rand-std"]} +secp256k1 = {version = "0.24.0", features = ["rand-std"]} [[example]] name = "htlc" diff --git a/examples/sign_multisig.rs b/examples/sign_multisig.rs index 5c58bf5d2..7471ff16f 100644 --- a/examples/sign_multisig.rs +++ b/examples/sign_multisig.rs @@ -18,7 +18,7 @@ use std::collections::HashMap; use std::str::FromStr; use bitcoin::blockdata::witness::Witness; -use bitcoin::secp256k1; +use bitcoin::{secp256k1, PackedLockTime, Sequence}; fn main() { let mut tx = spending_transaction(); @@ -91,11 +91,11 @@ fn main() { fn spending_transaction() -> bitcoin::Transaction { bitcoin::Transaction { version: 2, - lock_time: 0, + lock_time: PackedLockTime::ZERO, input: vec![bitcoin::TxIn { previous_output: Default::default(), script_sig: bitcoin::Script::new(), - sequence: 0xffffffff, + sequence: Sequence::MAX, witness: Witness::default(), }], output: vec![bitcoin::TxOut { diff --git a/examples/taproot.rs b/examples/taproot.rs index fcabc5e54..8af46f0c2 100644 --- a/examples/taproot.rs +++ b/examples/taproot.rs @@ -102,7 +102,7 @@ fn main() { let secp = secp256k1::Secp256k1::new(); let key_pair = KeyPair::new(&secp, &mut rand::thread_rng()); // Random unspendable XOnlyPublicKey provided for compilation to Taproot Descriptor - let unspendable_pubkey = bitcoin::XOnlyPublicKey::from_keypair(&key_pair); + let (unspendable_pubkey, _parity) = bitcoin::XOnlyPublicKey::from_keypair(&key_pair); pk_map.insert("UNSPENDABLE_KEY".to_string(), unspendable_pubkey); let pubkeys = hardcoded_xonlypubkeys(); diff --git a/examples/verify_tx.rs b/examples/verify_tx.rs index dd8cc75ff..9b0e9731b 100644 --- a/examples/verify_tx.rs +++ b/examples/verify_tx.rs @@ -19,6 +19,7 @@ use std::str::FromStr; use bitcoin::consensus::Decodable; use bitcoin::secp256k1::{self, Secp256k1}; use bitcoin::util::sighash; +use bitcoin::{LockTime, Sequence}; use miniscript::interpreter::KeySigPair; fn main() { @@ -33,8 +34,8 @@ fn main() { &spk_input_1, &tx.input[0].script_sig, &tx.input[0].witness, - 0, - 0, + Sequence::ZERO, + LockTime::ZERO, ) .unwrap(); diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 1c8083d22..fb7cd1687 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -860,7 +860,7 @@ mod tests { use bitcoin::hashes::hex::{FromHex, ToHex}; use bitcoin::hashes::{hash160, sha256}; use bitcoin::util::bip32; - use bitcoin::{self, secp256k1, EcdsaSighashType, PublicKey}; + use bitcoin::{self, secp256k1, EcdsaSighashType, PublicKey, Sequence}; use super::checksum::desc_checksum; use super::tr::Tr; @@ -1155,7 +1155,7 @@ mod tests { let mut txin = bitcoin::TxIn { previous_output: bitcoin::OutPoint::default(), script_sig: bitcoin::Script::new(), - sequence: 100, + sequence: Sequence::from_height(100), witness: Witness::default(), }; let bare = Descriptor::new_bare(ms.clone()).unwrap(); @@ -1166,7 +1166,7 @@ mod tests { bitcoin::TxIn { previous_output: bitcoin::OutPoint::default(), script_sig: script::Builder::new().push_slice(&sigser[..]).into_script(), - sequence: 100, + sequence: Sequence::from_height(100), witness: Witness::default(), } ); @@ -1182,7 +1182,7 @@ mod tests { .push_slice(&sigser[..]) .push_key(&pk) .into_script(), - sequence: 100, + sequence: Sequence::from_height(100), witness: Witness::default(), } ); @@ -1195,7 +1195,7 @@ mod tests { bitcoin::TxIn { previous_output: bitcoin::OutPoint::default(), script_sig: bitcoin::Script::new(), - sequence: 100, + sequence: Sequence::from_height(100), witness: Witness::from_vec(vec![sigser.clone(), pk.to_bytes(),]), } ); @@ -1216,7 +1216,7 @@ mod tests { script_sig: script::Builder::new() .push_slice(&redeem_script[..]) .into_script(), - sequence: 100, + sequence: Sequence::from_height(100), witness: Witness::from_vec(vec![sigser.clone(), pk.to_bytes(),]), } ); @@ -1238,7 +1238,7 @@ mod tests { .push_slice(&sigser[..]) .push_slice(&ms.encode()[..]) .into_script(), - sequence: 100, + sequence: Sequence::from_height(100), witness: Witness::default(), } ); @@ -1253,7 +1253,7 @@ mod tests { bitcoin::TxIn { previous_output: bitcoin::OutPoint::default(), script_sig: bitcoin::Script::new(), - sequence: 100, + sequence: Sequence::from_height(100), witness: Witness::from_vec(vec![sigser.clone(), ms.encode().into_bytes(),]), } ); @@ -1268,7 +1268,7 @@ mod tests { script_sig: script::Builder::new() .push_slice(&ms.encode().to_v0_p2wsh()[..]) .into_script(), - sequence: 100, + sequence: Sequence::from_height(100), witness: Witness::from_vec(vec![sigser.clone(), ms.encode().into_bytes(),]), } ); @@ -1401,7 +1401,7 @@ mod tests { let mut txin = bitcoin::TxIn { previous_output: bitcoin::OutPoint::default(), script_sig: bitcoin::Script::new(), - sequence: 0, + sequence: Sequence::ZERO, witness: Witness::default(), }; let satisfier = { diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 7280893f7..f1ba69d01 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -5,7 +5,7 @@ use core::{fmt, hash}; use bitcoin::blockdata::opcodes; use bitcoin::util::taproot::{ - LeafVersion, TaprootBuilder, TaprootBuilderError, TaprootSpendInfo, TAPROOT_CONTROL_BASE_SIZE, + LeafVersion, TaprootBuilder, TaprootSpendInfo, TAPROOT_CONTROL_BASE_SIZE, TAPROOT_CONTROL_MAX_NODE_COUNT, TAPROOT_CONTROL_NODE_SIZE, }; use bitcoin::{secp256k1, Address, Network, Script}; @@ -238,26 +238,7 @@ impl Tr { // Assert builder cannot error here because we have a well formed descriptor match builder.finalize(&secp, self.internal_key.to_x_only_pubkey()) { Ok(data) => data, - Err(e) => match e { - TaprootBuilderError::InvalidMerkleTreeDepth(_) => { - unreachable!("Depth checked in struct construction") - } - TaprootBuilderError::NodeNotInDfsOrder => { - unreachable!("Insertion is called in DFS order") - } - TaprootBuilderError::OverCompleteTree => { - unreachable!("Taptree is a well formed tree") - } - TaprootBuilderError::InvalidInternalKey(_) => { - unreachable!("Internal key checked for validity") - } - TaprootBuilderError::IncompleteTree => { - unreachable!("Taptree is a well formed tree") - } - TaprootBuilderError::EmptyTree => { - unreachable!("Taptree is a well formed tree with atleast 1 element") - } - }, + Err(_) => unreachable!("We know the builder can be finalized"), } }; let spend_info = Arc::new(data); diff --git a/src/interpreter/error.rs b/src/interpreter/error.rs index 65fe2b848..b9aca53ff 100644 --- a/src/interpreter/error.rs +++ b/src/interpreter/error.rs @@ -29,6 +29,8 @@ use crate::prelude::*; pub enum Error { /// Could not satisfy, absolute locktime not met AbsoluteLocktimeNotMet(u32), + /// Could not satisfy, lock time values are different units + AbsoluteLocktimeComparisonInvalid(u32, u32), /// 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 @@ -129,6 +131,11 @@ impl fmt::Display for Error { "required absolute locktime CLTV of {} blocks, not met", n ), + Error::AbsoluteLocktimeComparisonInvalid(n, lock_time) => write!( + f, + "could not satisfy, lock time values are different units n: {} lock_time: {}", + n, lock_time + ), Error::CannotInferTrDescriptors => write!(f, "Cannot infer taproot descriptors"), Error::ControlBlockParse(ref e) => write!(f, "Control block parse error {}", e), Error::ControlBlockVerificationError => { @@ -198,6 +205,7 @@ impl error::Error for Error { match self { AbsoluteLocktimeNotMet(_) + | AbsoluteLocktimeComparisonInvalid(_, _) | CannotInferTrDescriptors | ControlBlockVerificationError | CouldNotEvaluate diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 2c2c4b917..24d6e9276 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -25,7 +25,7 @@ use core::str::FromStr; use bitcoin::blockdata::witness::Witness; use bitcoin::hashes::{hash160, ripemd160, sha256}; use bitcoin::util::{sighash, taproot}; -use bitcoin::{self, secp256k1, TxOut}; +use bitcoin::{self, secp256k1, LockTime, Sequence, TxOut}; use crate::miniscript::context::NoChecks; use crate::miniscript::ScriptContext; @@ -48,8 +48,8 @@ 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, - age: u32, - lock_time: u32, + age: Sequence, + lock_time: LockTime, } // A type representing functions for checking signatures that accept both @@ -173,8 +173,8 @@ impl<'txin> Interpreter<'txin> { spk: &bitcoin::Script, script_sig: &'txin bitcoin::Script, witness: &'txin Witness, - age: u32, // CSV, relative lock time. - lock_time: u32, // CLTV, absolute lock time. + age: Sequence, // CSV, relative lock time. + lock_time: LockTime, // CLTV, absolute lock time. ) -> Result { let (inner, stack, script_code) = inner::from_txdata(spk, script_sig, witness)?; Ok(Interpreter { @@ -491,12 +491,12 @@ pub enum SatisfiedConstraint { ///Relative Timelock for CSV. RelativeTimelock { /// The value of RelativeTimelock - time: u32, + n: Sequence, }, ///Absolute Timelock for CLTV. AbsoluteTimelock { /// The value of Absolute timelock - time: u32, + n: LockTime, }, } @@ -531,8 +531,8 @@ pub struct Iter<'intp, 'txin: 'intp> { public_key: Option<&'intp BitcoinKey>, state: Vec>, stack: Stack<'txin>, - age: u32, - lock_time: u32, + age: Sequence, + lock_time: LockTime, has_errored: bool, } @@ -619,7 +619,7 @@ where Terminal::After(ref n) => { debug_assert_eq!(node_state.n_evaluated, 0); debug_assert_eq!(node_state.n_satisfied, 0); - let res = self.stack.evaluate_after(n, self.lock_time); + let res = self.stack.evaluate_after(&n.into(), self.lock_time); if res.is_some() { return res; } @@ -1094,8 +1094,9 @@ mod tests { pks.push(pk); der_sigs.push(sigser); - let keypair = bitcoin::KeyPair::from_secret_key(&secp, sk); - x_only_pks.push(bitcoin::XOnlyPublicKey::from_keypair(&keypair)); + let keypair = bitcoin::KeyPair::from_secret_key(&secp, &sk); + let (x_only_pk, _parity) = bitcoin::XOnlyPublicKey::from_keypair(&keypair); + x_only_pks.push(x_only_pk); let schnorr_sig = secp.sign_schnorr_with_aux_rand(&msg, &keypair, &[0u8; 32]); let schnorr_sig = bitcoin::SchnorrSig { sig: schnorr_sig, @@ -1144,8 +1145,8 @@ mod tests { n_evaluated: 0, n_satisfied: 0, }], - age: 1002, - lock_time: 1002, + age: Sequence::from_height(1002), + lock_time: LockTime::from_height(1002).unwrap(), has_errored: false, } } @@ -1208,7 +1209,9 @@ mod tests { let after_satisfied: Result, Error> = constraints.collect(); assert_eq!( after_satisfied.unwrap(), - vec![SatisfiedConstraint::AbsoluteTimelock { time: 1000 }] + vec![SatisfiedConstraint::AbsoluteTimelock { + n: LockTime::from_height(1000).unwrap() + }] ); //Check Older @@ -1218,7 +1221,9 @@ mod tests { let older_satisfied: Result, Error> = constraints.collect(); assert_eq!( older_satisfied.unwrap(), - vec![SatisfiedConstraint::RelativeTimelock { time: 1000 }] + vec![SatisfiedConstraint::RelativeTimelock { + n: Sequence::from_height(1000) + }] ); //Check Sha256 diff --git a/src/interpreter/stack.rs b/src/interpreter/stack.rs index 6591a6afd..7fee93b4c 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, Hash}; +use bitcoin::{LockTime, Sequence}; use super::error::PkEvalErrInner; use super::{ @@ -230,14 +231,27 @@ impl<'txin> Stack<'txin> { /// booleans pub(super) fn evaluate_after( &mut self, - n: &u32, - lock_time: u32, + n: &LockTime, + lock_time: LockTime, ) -> Option> { - if lock_time >= *n { + use LockTime::*; + + 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(), + ))) + } + }; + + if is_satisfied { self.push(Element::Satisfied); - Some(Ok(SatisfiedConstraint::AbsoluteTimelock { time: *n })) + Some(Ok(SatisfiedConstraint::AbsoluteTimelock { n: *n })) } else { - Some(Err(Error::AbsoluteLocktimeNotMet(*n))) + Some(Err(Error::AbsoluteLocktimeNotMet(n.to_consensus_u32()))) } } @@ -249,14 +263,14 @@ impl<'txin> Stack<'txin> { /// booleans pub(super) fn evaluate_older( &mut self, - n: &u32, - age: u32, + n: &Sequence, + age: Sequence, ) -> Option> { if age >= *n { self.push(Element::Satisfied); - Some(Ok(SatisfiedConstraint::RelativeTimelock { time: *n })) + Some(Ok(SatisfiedConstraint::RelativeTimelock { n: *n })) } else { - Some(Err(Error::RelativeLocktimeNotMet(*n))) + Some(Err(Error::RelativeLocktimeNotMet(n.to_consensus_u32()))) } } diff --git a/src/lib.rs b/src/lib.rs index c3f5c5099..6683e2796 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,7 +121,6 @@ pub mod interpreter; pub mod miniscript; pub mod policy; pub mod psbt; -pub mod timelock; #[cfg(test)] mod test_utils; diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index b21756430..f60bcd92f 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -23,6 +23,7 @@ use core::fmt; use core::str::FromStr; use bitcoin::blockdata::{opcodes, script}; +use bitcoin::{LockTime, Sequence}; use sync::Arc; use crate::miniscript::context::SigType; @@ -459,10 +460,10 @@ impl_from_tree!( } ("pk_h", 1) => 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(Terminal::After) + expression::parse_num(x).map(|x| Terminal::After(LockTime::from_consensus(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(Sequence::from_consensus(x))) }), ("sha256", 1) => expression::terminal(&top.args[0], |x| { Pk::Sha256::from_str(x).map(Terminal::Sha256) @@ -620,9 +621,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().into()) .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_consensus_u32().into()) + .push_opcode(opcodes::all::OP_CSV), Terminal::Sha256(ref h) => builder .push_opcode(opcodes::all::OP_SIZE) .push_int(32) @@ -755,8 +758,8 @@ impl Terminal { match *self { Terminal::PkK(ref pk) => Ctx::pk_len(pk), Terminal::PkH(..) | Terminal::RawPkH(..) => 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_consensus_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 d7f72c1d8..c80b2a1a2 100644 --- a/src/miniscript/decode.rs +++ b/src/miniscript/decode.rs @@ -26,6 +26,7 @@ use bitcoin::blockdata::constants::MAX_BLOCK_WEIGHT; use bitcoin::hashes::{hash160, ripemd160, sha256, Hash}; use sync::Arc; +use crate::bitcoin::{LockTime, PackedLockTime, Sequence}; use crate::miniscript::lex::{Token as Tk, TokenIter}; use crate::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG; use crate::miniscript::types::extra_props::ExtData; @@ -139,9 +140,9 @@ pub enum Terminal { RawPkH(Pk::RawPkHash), // timelocks /// `n CHECKLOCKTIMEVERIFY` - After(u32), + After(PackedLockTime), /// `n CHECKSEQUENCEVERIFY` - Older(u32), + Older(Sequence), // hashlocks /// `SIZE 32 EQUALVERIFY SHA256 EQUAL` Sha256(Pk::Sha256), @@ -392,9 +393,9 @@ pub fn parse( }, // timelocks Tk::CheckSequenceVerify, Tk::Num(n) - => term.reduce0(Terminal::Older(n))?, + => term.reduce0(Terminal::Older(Sequence::from_consensus(n)))?, Tk::CheckLockTimeVerify, Tk::Num(n) - => term.reduce0(Terminal::After(n))?, + => term.reduce0(Terminal::After(LockTime::from_consensus(n).into()))?, // hashlocks Tk::Equal => match_token!( tokens, diff --git a/src/miniscript/hash256.rs b/src/miniscript/hash256.rs index 013b2ebe2..e37961e78 100644 --- a/src/miniscript/hash256.rs +++ b/src/miniscript/hash256.rs @@ -17,10 +17,12 @@ //! This module is _identical_ in functionality to the `bitcoin_hashes::sha256d` hash type //! but the `FromHex/FromStr` and `ToHex/Display` implementations use `DISPLAY_BACKWARDS = false`. //! +use core::ops::Index; +use core::slice::SliceIndex; use core::str; use bitcoin::hashes::{ - self, borrow_slice_impl, hex, hex_fmt_impl, index_impl, serde_impl, sha256, Hash as HashTrait, + self, borrow_slice_impl, hex, hex_fmt_impl, serde_impl, sha256, Hash as HashTrait, }; /// Output of the SHA256d hash function @@ -31,10 +33,18 @@ pub struct Hash([u8; 32]); hex_fmt_impl!(Debug, Hash); hex_fmt_impl!(Display, Hash); hex_fmt_impl!(LowerHex, Hash); -index_impl!(Hash); serde_impl!(Hash, 32); borrow_slice_impl!(Hash); +impl> Index for Hash { + type Output = I::Output; + + #[inline] + fn index(&self, index: I) -> &Self::Output { + &self.0[index] + } +} + impl str::FromStr for Hash { type Err = hex::Error; fn from_str(s: &str) -> Result { @@ -85,4 +95,8 @@ impl HashTrait for Hash { fn from_inner(inner: Self::Inner) -> Self { Hash(inner) } + + fn all_zeros() -> Self { + Self([0u8; 32]) + } } diff --git a/src/miniscript/limits.rs b/src/miniscript/limits.rs index f96b01552..78e0622b0 100644 --- a/src/miniscript/limits.rs +++ b/src/miniscript/limits.rs @@ -14,25 +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 diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index 9d50d63d1..c69179bf4 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -453,7 +453,7 @@ mod tests { use bitcoin::hashes::{hash160, sha256, Hash}; use bitcoin::secp256k1::XOnlyPublicKey; use bitcoin::util::taproot::TapLeafHash; - use bitcoin::{self, secp256k1}; + use bitcoin::{self, secp256k1, Sequence}; use sync::Arc; use super::{Miniscript, ScriptContext, Segwitv0, Tap}; @@ -856,13 +856,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(10000); + abs = abs.at_age(Sequence::from_height(10000)); assert_eq!(abs.n_keys(), 5); assert_eq!(abs.minimum_n_keys(), Some(2)); - abs = abs.at_age(9999); + abs = abs.at_age(Sequence::from_height(9999)); assert_eq!(abs.n_keys(), 3); assert_eq!(abs.minimum_n_keys(), Some(3)); - abs = abs.at_age(0); + abs = abs.at_age(Sequence::ZERO); assert_eq!(abs.n_keys(), 3); assert_eq!(abs.minimum_n_keys(), Some(3)); @@ -980,7 +980,7 @@ mod tests { )); assert_eq!( ms.unwrap_err().to_string(), - "unexpected «Key hex decoding error: bad hex string length 64 (expected 66)»" + "unexpected «key hex decoding error»", ); Tapscript::from_str_insane(&format!( "pk(2788ee41e76f4f3af603da5bc8fa22997bc0344bb0f95666ba6aaff0242baa99)" diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index 37723117c..85c2b2e93 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -20,14 +20,11 @@ use core::{cmp, i64, mem}; -use bitcoin; use bitcoin::secp256k1::XOnlyPublicKey; use bitcoin::util::taproot::{ControlBlock, LeafVersion, TapLeafHash}; +use bitcoin::{LockTime, Sequence}; use sync::Arc; -use crate::miniscript::limits::{ - LOCKTIME_THRESHOLD, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_TYPE_FLAG, -}; use crate::prelude::*; use crate::util::witness_size; use crate::{Miniscript, MiniscriptKey, ScriptContext, Terminal, ToPublicKey}; @@ -109,12 +106,12 @@ pub trait Satisfier { } /// Assert whether an relative locktime is satisfied - fn check_older(&self, _: u32) -> bool { + fn check_older(&self, _: Sequence) -> bool { false } /// Assert whether a absolute locktime is satisfied - fn check_after(&self, _: u32) -> bool { + fn check_after(&self, _: LockTime) -> bool { false } } @@ -122,23 +119,22 @@ 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 { - return true; +impl Satisfier 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 & mask; - let masked_seq = self.0 & mask; + 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 } else { @@ -147,21 +143,17 @@ impl Satisfier for Older { } } -/// Newtype around `u32` which implements `Satisfier` using `n` as an -/// absolute locktime -pub struct After(pub u32); +impl Satisfier for LockTime { + fn check_after(&self, n: LockTime) -> bool { + use LockTime::*; -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 + 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 Satisfier for HashMap { fn lookup_ecdsa_sig(&self, key: &Pk) -> Option { self.get(key).copied() @@ -273,12 +265,12 @@ 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: Sequence) -> bool { (**self).check_older(t) } - fn check_after(&self, t: u32) -> bool { - (**self).check_after(t) + fn check_after(&self, n: LockTime) -> bool { + (**self).check_after(n) } } @@ -335,12 +327,12 @@ 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: Sequence) -> bool { (**self).check_older(t) } - fn check_after(&self, t: u32) -> bool { - (**self).check_after(t) + fn check_after(&self, n: LockTime) -> bool { + (**self).check_after(n) } } @@ -473,7 +465,7 @@ macro_rules! impl_tuple_satisfier { None } - fn check_older(&self, n: u32) -> bool { + fn check_older(&self, n: Sequence) -> bool { let &($(ref $ty,)*) = self; $( if $ty.check_older(n) { @@ -483,7 +475,7 @@ macro_rules! impl_tuple_satisfier { false } - fn check_after(&self, n: u32) -> bool { + fn check_after(&self, n: LockTime) -> bool { let &($(ref $ty,)*) = self; $( if $ty.check_after(n) { @@ -947,7 +939,7 @@ impl Satisfaction { has_sig: true, }, Terminal::After(t) => Satisfaction { - stack: if stfr.check_after(t) { + stack: if stfr.check_after(t.into()) { Witness::empty() } else if root_has_sig { // If the root terminal has signature, the diff --git a/src/miniscript/types/extra_props.rs b/src/miniscript/types/extra_props.rs index 0cd97f78b..2061dabdf 100644 --- a/src/miniscript/types/extra_props.rs +++ b/src/miniscript/types/extra_props.rs @@ -4,11 +4,10 @@ use core::cmp; use core::iter::once; +use bitcoin::{LockTime, PackedLockTime, Sequence}; + use super::{Error, ErrorKind, Property, ScriptContext}; use crate::miniscript::context::SigType; -use crate::miniscript::limits::{ - LOCKTIME_THRESHOLD, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_TYPE_FLAG, -}; use crate::prelude::*; use crate::{script_num_size, MiniscriptKey, Terminal}; @@ -338,9 +337,9 @@ impl Property for ExtData { unreachable!() } - fn from_after(t: u32) -> Self { + fn from_after(t: LockTime) -> Self { ExtData { - pk_cost: script_num_size(t as usize) + 1, + pk_cost: script_num_size(t.to_consensus_u32() as usize) + 1, has_free_verify: false, ops: OpLimits::new(1, Some(0), None), stack_elem_count_sat: Some(0), @@ -350,8 +349,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), // @@ -359,9 +358,9 @@ impl Property for ExtData { } } - fn from_older(t: u32) -> Self { + fn from_older(t: Sequence) -> Self { ExtData { - pk_cost: script_num_size(t as usize) + 1, + pk_cost: script_num_size(t.to_consensus_u32() as usize) + 1, has_free_verify: false, ops: OpLimits::new(1, Some(0), None), stack_elem_count_sat: Some(0), @@ -369,8 +368,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_height_locked(), + csv_with_time: t.is_time_locked(), cltv_with_height: false, cltv_with_time: false, contains_combination: false, @@ -934,16 +933,16 @@ 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 { + if t == PackedLockTime::ZERO { return Err(Error { fragment: fragment.clone(), error: ErrorKind::InvalidTime, }); } - Ok(Self::from_after(t)) + Ok(Self::from_after(t.into())) } Terminal::Older(t) => { - if t == 0 || (t & SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0 { + if t == Sequence::ZERO || !t.is_relative_lock_time() { return Err(Error { fragment: fragment.clone(), error: ErrorKind::InvalidTime, diff --git a/src/miniscript/types/mod.rs b/src/miniscript/types/mod.rs index 161185c4f..3802a08e5 100644 --- a/src/miniscript/types/mod.rs +++ b/src/miniscript/types/mod.rs @@ -24,10 +24,11 @@ use core::fmt; #[cfg(feature = "std")] use std::error; +use bitcoin::{LockTime, PackedLockTime, Sequence}; + 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}; @@ -303,14 +304,14 @@ pub trait Property: Sized { /// Type property of an absolute timelock. Default implementation simply /// passes through to `from_time` - fn from_after(t: u32) -> Self { - Self::from_time(t) + fn from_after(t: LockTime) -> Self { + Self::from_time(t.to_consensus_u32()) } /// Type property of a relative timelock. Default implementation simply /// passes through to `from_time` - fn from_older(t: u32) -> Self { - Self::from_time(t) + fn from_older(t: Sequence) -> Self { + Self::from_time(t.to_consensus_u32()) } /// Cast using the `Alt` wrapper @@ -437,16 +438,16 @@ 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 { + if t == PackedLockTime::ZERO { return Err(Error { fragment: fragment.clone(), error: ErrorKind::InvalidTime, }); } - Ok(Self::from_after(t)) + Ok(Self::from_after(t.into())) } Terminal::Older(t) => { - if t == 0 || (t & SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0 { + if t == Sequence::ZERO || !t.is_relative_lock_time() { return Err(Error { fragment: fragment.clone(), error: ErrorKind::InvalidTime, @@ -624,14 +625,14 @@ impl Property for Type { } } - fn from_after(t: u32) -> Self { + fn from_after(t: LockTime) -> Self { Type { corr: Property::from_after(t), mall: Property::from_after(t), } } - fn from_older(t: u32) -> Self { + fn from_older(t: Sequence) -> Self { Type { corr: Property::from_older(t), mall: Property::from_older(t), @@ -820,16 +821,16 @@ 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 { + if t == PackedLockTime::ZERO { return Err(Error { fragment: fragment.clone(), error: ErrorKind::InvalidTime, }); } - Ok(Self::from_after(t)) + Ok(Self::from_after(t.into())) } Terminal::Older(t) => { - if t == 0 || (t & SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0 { + if t == Sequence::ZERO || !t.is_relative_lock_time() { return Err(Error { fragment: fragment.clone(), error: ErrorKind::InvalidTime, diff --git a/src/policy/compiler.rs b/src/policy/compiler.rs index c5cb7cdea..c19e95745 100644 --- a/src/policy/compiler.rs +++ b/src/policy/compiler.rs @@ -1202,10 +1202,10 @@ mod tests { use core::str::FromStr; use bitcoin::blockdata::{opcodes, script}; - use bitcoin::{self, hashes, secp256k1}; + use bitcoin::{self, hashes, secp256k1, Sequence}; use super::*; - use crate::miniscript::{satisfy, Legacy, Segwitv0, Tap}; + use crate::miniscript::{Legacy, Segwitv0, Tap}; use crate::policy::Liftable; use crate::script_num_size; @@ -1255,7 +1255,7 @@ 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), Concrete::after(1000_000_000)]), ]); assert!(pol.compile::().is_err()); @@ -1367,7 +1367,7 @@ mod tests { ( 1, Concrete::And(vec![ - Concrete::Older(10000), + Concrete::Older(Sequence::from_height(10000)), Concrete::Threshold(2, key_pol[5..8].to_owned()), ]), ), @@ -1394,13 +1394,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(10000); + abs = abs.at_age(Sequence::from_height(10000)); assert_eq!(abs.n_keys(), 8); assert_eq!(abs.minimum_n_keys(), Some(2)); - abs = abs.at_age(9999); + abs = abs.at_age(Sequence::from_height(9999)); assert_eq!(abs.n_keys(), 5); assert_eq!(abs.minimum_n_keys(), Some(3)); - abs = abs.at_age(0); + abs = abs.at_age(Sequence::ZERO); assert_eq!(abs.n_keys(), 5); assert_eq!(abs.minimum_n_keys(), Some(3)); @@ -1424,12 +1424,16 @@ 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, Sequence::from_height(10001))) + .is_ok()); //timelock not met - assert!(ms.satisfy((&right_sat, satisfy::Older(9999))).is_err()); + assert!(ms + .satisfy((&right_sat, Sequence::from_height(9999))) + .is_err()); assert_eq!( - ms.satisfy((left_sat, satisfy::Older(9999))).unwrap(), + ms.satisfy((left_sat, Sequence::from_height(9999))).unwrap(), vec![ // sat for left branch vec![], @@ -1440,7 +1444,8 @@ mod tests { ); assert_eq!( - ms.satisfy((right_sat, satisfy::Older(10000))).unwrap(), + ms.satisfy((right_sat, Sequence::from_height(10000))) + .unwrap(), vec![ // sat for right branch vec![], diff --git a/src/policy/concrete.rs b/src/policy/concrete.rs index 6e28f3d11..d29a66251 100644 --- a/src/policy/concrete.rs +++ b/src/policy/concrete.rs @@ -19,6 +19,7 @@ use core::{fmt, str}; #[cfg(feature = "std")] use std::error; +use bitcoin::{LockTime, PackedLockTime, Sequence}; #[cfg(feature = "compiler")] use { crate::descriptor::TapTree, @@ -35,7 +36,6 @@ use { 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; use crate::prelude::*; use crate::{errstr, Error, ForEachKey, MiniscriptKey, Translator}; @@ -52,9 +52,9 @@ pub enum Policy { /// A public key which must sign to satisfy the descriptor Key(Pk), /// An absolute locktime restriction - After(u32), + After(PackedLockTime), /// A relative locktime restriction - Older(u32), + Older(Sequence), /// 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 @@ -72,6 +72,23 @@ pub enum Policy { Threshold(usize, Vec>), } +impl Policy +where + Pk: MiniscriptKey, +{ + /// Construct a `Policy::After` from `n`. Helper function equivalent to + /// `Policy::After(PackedLockTime::from(LockTime::from_consensus(n)))`. + pub fn after(n: u32) -> Policy { + Policy::After(PackedLockTime::from(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 { + Policy::Older(Sequence::from_consensus(n)) + } +} + /// Detailed Error type for Policies #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum PolicyError { @@ -548,13 +565,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: LockTime::from(t).is_block_height(), + cltv_with_time: LockTime::from(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_height_locked(), + csv_with_time: t.is_time_locked(), cltv_with_height: false, cltv_with_time: false, contains_combination: false, @@ -614,10 +631,19 @@ impl Policy { Ok(()) } } - Policy::After(n) | Policy::Older(n) => { - if n == 0 { + Policy::After(n) => { + if n == PackedLockTime::ZERO { + Err(PolicyError::ZeroTime) + } else if n.to_u32() > 2u32.pow(31) { + Err(PolicyError::TimeTooFar) + } else { + Ok(()) + } + } + Policy::Older(n) => { + if n == Sequence::ZERO { Err(PolicyError::ZeroTime) - } else if n > 2u32.pow(31) { + } else if n.to_consensus_u32() > 2u32.pow(31) { Err(PolicyError::TimeTooFar) } else { Ok(()) @@ -824,7 +850,7 @@ impl_block_str!( } else if num == 0 { return Err(Error::PolicyError(PolicyError::ZeroTime)); } - Ok(Policy::After(num)) + Ok(Policy::after(num)) } ("older", 1) => { let num = expression::terminal(&top.args[0], expression::parse_num)?; @@ -833,7 +859,7 @@ impl_block_str!( } else if num == 0 { return Err(Error::PolicyError(PolicyError::ZeroTime)); } - Ok(Policy::Older(num)) + Ok(Policy::older(num)) } ("sha256", 1) => expression::terminal(&top.args[0], |x| { ::from_str(x).map(Policy::Sha256) diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 9e91dc7fe..a6add6694 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -230,7 +230,7 @@ impl Liftable for Concrete { mod tests { use core::str::FromStr; - use bitcoin; + use bitcoin::Sequence; #[cfg(feature = "compiler")] use sync::Arc; @@ -361,7 +361,7 @@ mod tests { 2, vec![ Semantic::KeyHash(key_a.pubkey_hash().as_hash()), - Semantic::Older(42) + Semantic::Older(Sequence::from_height(42)) ] ), Semantic::KeyHash(key_b.pubkey_hash().as_hash()) diff --git a/src/policy/semantic.rs b/src/policy/semantic.rs index 1bf430cb9..7d0748ba5 100644 --- a/src/policy/semantic.rs +++ b/src/policy/semantic.rs @@ -17,10 +17,12 @@ use core::str::FromStr; use core::{fmt, str}; +use bitcoin::{LockTime, PackedLockTime, Sequence}; + use super::concrete::PolicyError; use super::ENTAILMENT_MAX_TERMINALS; use crate::prelude::*; -use crate::{errstr, expression, timelock, Error, ForEachKey, MiniscriptKey, Translator}; +use crate::{errstr, expression, Error, ForEachKey, MiniscriptKey, Translator}; /// Abstract policy which corresponds to the semantics of a Miniscript /// and which allows complex forms of analysis, e.g. filtering and @@ -37,9 +39,9 @@ pub enum Policy { /// Signature and public key matching a given hash is required KeyHash(Pk::RawPkHash), /// An absolute locktime restriction - After(u32), + After(PackedLockTime), /// A relative locktime restriction - Older(u32), + Older(Sequence), /// 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 @@ -52,6 +54,23 @@ pub enum Policy { Threshold(usize, Vec>), } +impl Policy +where + Pk: MiniscriptKey, +{ + /// Construct a `Policy::After` from `n`. Helper function equivalent to + /// `Policy::After(PackedLockTime::from(LockTime::from_consensus(n)))`. + pub fn after(n: u32) -> Policy { + Policy::After(PackedLockTime::from(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 { + Policy::Older(Sequence::from_consensus(n)) + } +} + impl ForEachKey for Policy { fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, mut pred: F) -> bool where @@ -345,10 +364,10 @@ impl_from_tree!( Pk::RawPkHash::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)) }), ("older", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x).map(Policy::Older) + expression::parse_num(x).map(|x| Policy::older(x)) }), ("sha256", 1) => expression::terminal(&top.args[0], |x| { Pk::Sha256::from_str(x).map(Policy::Sha256) @@ -503,7 +522,7 @@ impl Policy { | Policy::Ripemd160(..) | Policy::Hash160(..) => vec![], Policy::After(..) => vec![], - Policy::Older(t) => vec![t], + Policy::Older(t) => vec![t.to_consensus_u32()], Policy::Threshold(_, ref subs) => subs.iter().fold(vec![], |mut acc, x| { acc.extend(x.real_relative_timelocks()); acc @@ -531,7 +550,7 @@ impl Policy { | Policy::Ripemd160(..) | Policy::Hash160(..) => vec![], Policy::Older(..) => vec![], - Policy::After(t) => vec![t], + Policy::After(t) => vec![t.0], Policy::Threshold(_, ref subs) => subs.iter().fold(vec![], |mut acc, x| { acc.extend(x.real_absolute_timelocks()); acc @@ -550,10 +569,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, age: u32) -> Policy { + pub fn at_age(mut self, age: Sequence) -> Policy { self = match self { Policy::Older(t) => { - if t > age { + if t.is_height_locked() && age.is_time_locked() + || t.is_time_locked() && age.is_height_locked() + { + Policy::Unsatisfiable + } else if t.to_consensus_u32() > age.to_consensus_u32() { Policy::Unsatisfiable } else { Policy::Older(t) @@ -569,15 +592,21 @@ impl Policy { /// Filter a policy by eliminating absolute timelock constraints /// that are not satisfied at the given `n` (`n OP_CHECKLOCKTIMEVERIFY`). - pub fn at_lock_time(mut self, n: u32) -> Policy { + pub fn at_lock_time(mut self, n: LockTime) -> Policy { + use LockTime::*; + self = match self { Policy::After(t) => { - if !timelock::absolute_timelocks_are_same_unit(t, n) { - Policy::Unsatisfiable - } else if t > n { + let t = 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 { Policy::Unsatisfiable } else { - Policy::After(t) + Policy::After(t.into()) } } Policy::Threshold(k, subs) => { @@ -684,19 +713,31 @@ mod tests { assert_eq!(policy, Policy::KeyHash("".to_owned())); assert_eq!(policy.relative_timelocks(), vec![]); assert_eq!(policy.absolute_timelocks(), vec![]); - assert_eq!(policy.clone().at_age(0), policy.clone()); - assert_eq!(policy.clone().at_age(10000), policy.clone()); + assert_eq!(policy.clone().at_age(Sequence::ZERO), policy.clone()); + assert_eq!( + policy.clone().at_age(Sequence::from_height(10000)), + policy.clone() + ); 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(1000)); + assert_eq!(policy, Policy::Older(Sequence::from_height(1000))); assert_eq!(policy.absolute_timelocks(), vec![]); assert_eq!(policy.relative_timelocks(), vec![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()); - assert_eq!(policy.clone().at_age(10000), policy.clone()); + 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.clone() + ); + assert_eq!( + policy.clone().at_age(Sequence::from_height(10000)), + policy.clone() + ); assert_eq!(policy.n_keys(), 0); assert_eq!(policy.minimum_n_keys(), Some(0)); @@ -705,15 +746,30 @@ mod tests { policy, Policy::Threshold( 1, - vec![Policy::KeyHash("".to_owned()), Policy::Older(1000),] + vec![ + Policy::KeyHash("".to_owned()), + Policy::Older(Sequence::from_height(1000)), + ] ) ); assert_eq!(policy.relative_timelocks(), vec![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())); - assert_eq!(policy.clone().at_age(1000), policy.clone().normalized()); - assert_eq!(policy.clone().at_age(10000), policy.clone().normalized()); + assert_eq!( + policy.clone().at_age(Sequence::ZERO), + Policy::KeyHash("".to_owned()) + ); + assert_eq!( + policy.clone().at_age(Sequence::from_height(999)), + Policy::KeyHash("".to_owned()) + ); + assert_eq!( + policy.clone().at_age(Sequence::from_height(1000)), + policy.clone().normalized() + ); + assert_eq!( + policy.clone().at_age(Sequence::from_height(10000)), + policy.clone().normalized() + ); assert_eq!(policy.n_keys(), 1); assert_eq!(policy.minimum_n_keys(), Some(0)); @@ -754,11 +810,11 @@ mod tests { Policy::Threshold( 2, vec![ - Policy::Older(1000), - Policy::Older(10000), - Policy::Older(1000), - Policy::Older(2000), - Policy::Older(2000), + Policy::Older(Sequence::from_height(1000)), + Policy::Older(Sequence::from_height(10000)), + Policy::Older(Sequence::from_height(1000)), + Policy::Older(Sequence::from_height(2000)), + Policy::Older(Sequence::from_height(2000)), ] ) ); @@ -778,9 +834,9 @@ mod tests { Policy::Threshold( 2, vec![ - Policy::Older(1000), - Policy::Older(10000), - Policy::Older(1000), + Policy::Older(Sequence::from_height(1000)), + Policy::Older(Sequence::from_height(10000)), + Policy::Older(Sequence::from_height(1000)), Policy::Unsatisfiable, Policy::Unsatisfiable, ] @@ -795,16 +851,36 @@ mod tests { // Block height 1000. let policy = StringPolicy::from_str("after(1000)").unwrap(); - assert_eq!(policy, Policy::After(1000)); + assert_eq!(policy, Policy::after(1000)); assert_eq!(policy.absolute_timelocks(), vec![1000]); assert_eq!(policy.relative_timelocks(), vec![]); - assert_eq!(policy.clone().at_lock_time(0), Policy::Unsatisfiable); - assert_eq!(policy.clone().at_lock_time(999), Policy::Unsatisfiable); - assert_eq!(policy.clone().at_lock_time(1000), policy.clone()); - assert_eq!(policy.clone().at_lock_time(10000), policy.clone()); + assert_eq!( + policy.clone().at_lock_time(LockTime::ZERO), + Policy::Unsatisfiable + ); + assert_eq!( + policy + .clone() + .at_lock_time(LockTime::from_height(999).expect("valid block height")), + Policy::Unsatisfiable + ); + assert_eq!( + policy + .clone() + .at_lock_time(LockTime::from_height(1000).expect("valid block height")), + policy.clone() + ); + assert_eq!( + policy + .clone() + .at_lock_time(LockTime::from_height(10000).expect("valid block height")), + policy.clone() + ); // Pass a UNIX timestamp to at_lock_time while policy uses a block height. assert_eq!( - policy.clone().at_lock_time(500_000_001), + policy + .clone() + .at_lock_time(LockTime::from_time(500_000_001).expect("valid timestamp")), Policy::Unsatisfiable ); assert_eq!(policy.n_keys(), 0); @@ -812,25 +888,57 @@ 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(500_000_010)); 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. - assert_eq!(policy.clone().at_lock_time(0), Policy::Unsatisfiable); - assert_eq!(policy.clone().at_lock_time(999), Policy::Unsatisfiable); - assert_eq!(policy.clone().at_lock_time(1000), Policy::Unsatisfiable); - assert_eq!(policy.clone().at_lock_time(10000), Policy::Unsatisfiable); + assert_eq!( + policy.clone().at_lock_time(LockTime::ZERO), + Policy::Unsatisfiable + ); + assert_eq!( + policy + .clone() + .at_lock_time(LockTime::from_height(999).expect("valid block height")), + Policy::Unsatisfiable + ); + assert_eq!( + policy + .clone() + .at_lock_time(LockTime::from_height(1000).expect("valid block height")), + Policy::Unsatisfiable + ); + assert_eq!( + policy + .clone() + .at_lock_time(LockTime::from_height(10000).expect("valid block height")), + Policy::Unsatisfiable + ); // And now pass a UNIX timestamp to at_lock_time while policy also uses a timestamp. assert_eq!( - policy.clone().at_lock_time(500_000_000), + policy + .clone() + .at_lock_time(LockTime::from_time(500_000_000).expect("valid timestamp")), Policy::Unsatisfiable ); assert_eq!( - policy.clone().at_lock_time(500_000_001), + policy + .clone() + .at_lock_time(LockTime::from_time(500_000_001).expect("valid timestamp")), Policy::Unsatisfiable ); - assert_eq!(policy.clone().at_lock_time(500_000_010), policy.clone()); - assert_eq!(policy.clone().at_lock_time(500_000_012), policy.clone()); + assert_eq!( + policy + .clone() + .at_lock_time(LockTime::from_time(500_000_010).expect("valid timestamp")), + policy.clone() + ); + assert_eq!( + policy + .clone() + .at_lock_time(LockTime::from_time(500_000_012).expect("valid timestamp")), + policy.clone() + ); assert_eq!(policy.n_keys(), 0); assert_eq!(policy.minimum_n_keys(), Some(0)); } @@ -851,7 +959,7 @@ mod tests { let backup_policy = StringPolicy::from_str("thresh(2,pkh(A),pkh(B),pkh(C))").unwrap(); assert!(!backup_policy .clone() - .entails(liquid_pol.clone().at_age(4095)) + .entails(liquid_pol.clone().at_age(Sequence::from_height(4095))) .unwrap()); // Finally test both spending paths diff --git a/src/psbt/finalizer.rs b/src/psbt/finalizer.rs index cd52cadfb..7bc507f5f 100644 --- a/src/psbt/finalizer.rs +++ b/src/psbt/finalizer.rs @@ -295,7 +295,7 @@ fn interpreter_inp_check>( let cltv = psbt.unsigned_tx.lock_time; let csv = psbt.unsigned_tx.input[index].sequence; let interpreter = - interpreter::Interpreter::from_txdata(spk, script_sig, witness, csv, cltv) + interpreter::Interpreter::from_txdata(spk, script_sig, witness, csv, cltv.into()) .map_err(|e| Error::InputError(InputError::Interpreter(e), index))?; let iter = interpreter.iter(secp, &psbt.unsigned_tx, index, utxos); if let Some(error) = iter.filter_map(Result::err).next() { diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index b0047bd78..c113c447c 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -29,11 +29,9 @@ use bitcoin::secp256k1::{self, Secp256k1, VerifyOnly}; 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 bitcoin::{self, EcdsaSighashType, LockTime, SchnorrSighashType, Script, Sequence}; use crate::miniscript::iter::PkPkh; -use crate::miniscript::limits::SEQUENCE_LOCKTIME_DISABLE_FLAG; -use crate::miniscript::satisfy::{After, Older}; use crate::prelude::*; use crate::{ descriptor, interpreter, DefiniteDescriptorKey, Descriptor, MiniscriptKey, PkTranslator, @@ -334,31 +332,30 @@ 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; - let seq = self.psbt.unsigned_tx.input[self.index].sequence; - - // https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki - // fail if TxIn is finalized - if seq == 0xffffffff { - false - } else { - >::check_after(&After(locktime), n) + fn check_after(&self, n: LockTime) -> bool { + if !self.psbt.unsigned_tx.input[self.index].enables_lock_time() { + return false; } + + let lock_time = LockTime::from(self.psbt.unsigned_tx.lock_time); + + >::check_after(&lock_time, n) } - fn check_older(&self, n: u32) -> bool { + fn check_older(&self, n: Sequence) -> 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 & SEQUENCE_LOCKTIME_DISABLE_FLAG != 0 { - true - } else if self.psbt.unsigned_tx.version < 2 || (seq & SEQUENCE_LOCKTIME_DISABLE_FLAG != 0) { - // transaction version and sequence check - false - } else { - >::check_older(&Older(seq), n) + // Disable flag set => return true. + if !n.is_relative_lock_time() { + return true; } + + if self.psbt.unsigned_tx.version < 2 || !seq.is_relative_lock_time() { + return false; + } + + >::check_older(&seq, n) } fn lookup_hash160(&self, h: &Pk::Hash160) -> Option { @@ -1255,7 +1252,7 @@ mod tests { use bitcoin::hashes::hex::FromHex; use bitcoin::secp256k1::PublicKey; use bitcoin::util::bip32::{DerivationPath, ExtendedPubKey}; - use bitcoin::{OutPoint, TxIn, TxOut, XOnlyPublicKey}; + use bitcoin::{OutPoint, PackedLockTime, TxIn, TxOut, XOnlyPublicKey}; use super::*; use crate::Miniscript; @@ -1442,7 +1439,7 @@ mod tests { let mut non_witness_utxo = bitcoin::Transaction { version: 1, - lock_time: 0, + lock_time: PackedLockTime::ZERO, input: vec![], output: vec![TxOut { value: 1_000, @@ -1455,7 +1452,7 @@ mod tests { let tx = bitcoin::Transaction { version: 1, - lock_time: 0, + lock_time: PackedLockTime::ZERO, input: vec![TxIn { previous_output: OutPoint { txid: non_witness_utxo.txid(), diff --git a/src/test_utils.rs b/src/test_utils.rs index 64a3202f4..ccd5b55eb 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -171,8 +171,9 @@ impl StrXOnlyKeyTranslator { let pks: Vec<_> = sks .iter() .map(|sk| { - let keypair = secp256k1::KeyPair::from_secret_key(&secp, *sk); - bitcoin::XOnlyPublicKey::from_keypair(&keypair) + let keypair = secp256k1::KeyPair::from_secret_key(&secp, sk); + let (pk, _parity) = bitcoin::XOnlyPublicKey::from_keypair(&keypair); + pk }) .collect(); let mut pk_map = HashMap::new(); diff --git a/src/timelock.rs b/src/timelock.rs deleted file mode 100644 index a28e211e8..000000000 --- a/src/timelock.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! Various functions for manipulating Bitcoin timelocks. - -use crate::miniscript::limits::LOCKTIME_THRESHOLD; - -/// Returns true if `a` and `b` are the same unit i.e., both are block heights or both are UNIX -/// timestamps. `a` and `b` are nLockTime values. -pub fn absolute_timelocks_are_same_unit(a: u32, b: u32) -> bool { - n_lock_time_is_block_height(a) == n_lock_time_is_block_height(b) -} - -// https://github.com/bitcoin/bitcoin/blob/9ccaee1d5e2e4b79b0a7c29aadb41b97e4741332/src/script/script.h#L39 - -/// Returns true if nLockTime `n` is to be interpreted as a block height. -pub fn n_lock_time_is_block_height(n: u32) -> bool { - n < LOCKTIME_THRESHOLD -} - -/// Returns true if nLockTime `n` is to be interpreted as a UNIX timestamp. -pub fn n_lock_time_is_timestamp(n: u32) -> bool { - n >= LOCKTIME_THRESHOLD -} diff --git a/tests/setup/test_util.rs b/tests/setup/test_util.rs index 18bf2ddb5..f5043c7f5 100644 --- a/tests/setup/test_util.rs +++ b/tests/setup/test_util.rs @@ -85,8 +85,8 @@ fn setup_keys( let mut x_only_pks = vec![]; for i in 0..n { - let keypair = bitcoin::KeyPair::from_secret_key(&secp_sign, sks[i]); - let xpk = bitcoin::XOnlyPublicKey::from_keypair(&keypair); + let keypair = bitcoin::KeyPair::from_secret_key(&secp_sign, &sks[i]); + let (xpk, _parity) = bitcoin::XOnlyPublicKey::from_keypair(&keypair); x_only_keypairs.push(keypair); x_only_pks.push(xpk); } diff --git a/tests/test_cpp.rs b/tests/test_cpp.rs index a6e7fd1a3..af45a12b3 100644 --- a/tests/test_cpp.rs +++ b/tests/test_cpp.rs @@ -13,7 +13,7 @@ use bitcoin::hashes::{sha256d, Hash}; use bitcoin::secp256k1::{self, Secp256k1}; use bitcoin::util::psbt; use bitcoin::util::psbt::PartiallySignedTransaction as Psbt; -use bitcoin::{self, Amount, OutPoint, Transaction, TxIn, TxOut, Txid}; +use bitcoin::{self, Amount, LockTime, OutPoint, Sequence, Transaction, TxIn, TxOut, Txid}; use bitcoind::bitcoincore_rpc::{json, Client, RpcApi}; use miniscript::miniscript::iter; use miniscript::psbt::PsbtExt; @@ -110,7 +110,9 @@ pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) { let mut psbt = Psbt { unsigned_tx: Transaction { version: 2, - lock_time: 1_603_866_330, // time at 10/28/2020 @ 6:25am (UTC) + lock_time: LockTime::from_time(1_603_866_330) + .expect("valid timestamp") + .into(), // 10/28/2020 @ 6:25am (UTC) input: vec![], output: vec![], }, @@ -122,13 +124,13 @@ pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) { outputs: vec![], }; // figure out the outpoint from the txid - let (outpoint, witness_utxo) = get_vout(&cl, txid, btc(1.0).as_sat()); + let (outpoint, witness_utxo) = get_vout(&cl, txid, btc(1.0).to_sat()); let mut txin = TxIn::default(); txin.previous_output = outpoint; // set the sequence to a non-final number for the locktime transactions to be // processed correctly. // We waited 50 blocks, keep 49 for safety - txin.sequence = 49; + txin.sequence = Sequence::from_height(49); psbt.unsigned_tx.input.push(txin); // Get a new script pubkey from the node so that // the node wallet tracks the receiving transaction @@ -169,7 +171,7 @@ pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) { }) .collect(); // Get the required sighash message - let amt = btc(1).as_sat(); + let amt = btc(1).to_sat(); let mut sighash_cache = bitcoin::util::sighash::SighashCache::new(&psbts[i].unsigned_tx); let sighash_ty = bitcoin::EcdsaSighashType::All; let sighash = sighash_cache diff --git a/tests/test_desc.rs b/tests/test_desc.rs index f94264c72..f9a4c13d7 100644 --- a/tests/test_desc.rs +++ b/tests/test_desc.rs @@ -15,7 +15,8 @@ use bitcoin::util::sighash::SighashCache; use bitcoin::util::taproot::{LeafVersion, TapLeafHash}; use bitcoin::util::{psbt, sighash}; use bitcoin::{ - self, secp256k1, Amount, OutPoint, SchnorrSig, Script, Transaction, TxIn, TxOut, Txid, + self, secp256k1, Amount, LockTime, OutPoint, SchnorrSig, Script, Sequence, Transaction, TxIn, + TxOut, Txid, }; use bitcoind::bitcoincore_rpc::{json, Client, RpcApi}; use miniscript::miniscript::iter; @@ -105,7 +106,9 @@ pub fn test_desc_satisfy( let mut psbt = Psbt { unsigned_tx: Transaction { version: 2, - lock_time: 1_603_866_330, // time at 10/28/2020 @ 6:25am (UTC) + lock_time: LockTime::from_time(1_603_866_330) + .expect("valid timestamp") + .into(), // 10/28/2020 @ 6:25am (UTC) input: vec![], output: vec![], }, @@ -118,13 +121,13 @@ pub fn test_desc_satisfy( }; // figure out the outpoint from the txid let (outpoint, witness_utxo) = - get_vout(&cl, txid, btc(1.0).as_sat(), derived_desc.script_pubkey()); + get_vout(&cl, txid, btc(1.0).to_sat(), derived_desc.script_pubkey()); let mut txin = TxIn::default(); txin.previous_output = outpoint; // set the sequence to a non-final number for the locktime transactions to be // processed correctly. // We waited 2 blocks, keep 1 for safety - txin.sequence = 1; + txin.sequence = Sequence::from_height(1); psbt.unsigned_tx.input.push(txin); // Get a new script pubkey from the node so that // the node wallet tracks the receiving transaction @@ -164,10 +167,10 @@ pub fn test_desc_satisfy( let prevouts = [witness_utxo]; let prevouts = sighash::Prevouts::All(&prevouts); - if let Some(mut internal_keypair) = internal_keypair { + if let Some(internal_keypair) = internal_keypair { // ---------------------- Tr key spend -------------------- - internal_keypair - .tweak_add_assign(&secp, tr.spend_info().tap_tweak().as_ref()) + let internal_keypair = internal_keypair + .add_xonly_tweak(&secp, &tr.spend_info().tap_tweak().to_scalar()) .expect("Tweaking failed"); let sighash_msg = sighash_cache .taproot_key_spend_signature_hash(0, &prevouts, hash_ty) @@ -214,7 +217,7 @@ pub fn test_desc_satisfy( // FIXME: uncomment when == is supported for secp256k1::KeyPair. (next major release) // let x_only_pk = pks[xonly_keypairs.iter().position(|&x| x == keypair).unwrap()]; // Just recalc public key - let x_only_pk = secp256k1::XOnlyPublicKey::from_keypair(&keypair); + let (x_only_pk, _parity) = secp256k1::XOnlyPublicKey::from_keypair(&keypair); psbt.inputs[0].tap_script_sigs.insert( (x_only_pk, leaf_hash), bitcoin::SchnorrSig {