diff --git a/examples/ecdsa-psbt.rs b/examples/ecdsa-psbt.rs index c685037681..4522406e60 100644 --- a/examples/ecdsa-psbt.rs +++ b/examples/ecdsa-psbt.rs @@ -43,8 +43,8 @@ use bitcoin::util::bip32::{ }; use bitcoin::util::psbt::{self, Input, Psbt, PsbtSighashType}; use bitcoin::{ - Address, Amount, Network, OutPoint, PrivateKey, PublicKey, Script, Sequence, Transaction, TxIn, - TxOut, Txid, Witness, + Address, Amount, Network, OutPoint, PackedLockTime, PrivateKey, PublicKey, Script, Sequence, + Transaction, TxIn, TxOut, Txid, Witness, }; use self::psbt_sign::*; @@ -207,7 +207,7 @@ impl WatchOnly { let tx = Transaction { version: 2, - lock_time: 0, + lock_time: PackedLockTime::ZERO, input: vec![TxIn { previous_output: OutPoint { txid: Txid::from_hex(INPUT_UTXO_TXID)?, diff --git a/src/blockdata/constants.rs b/src/blockdata/constants.rs index 0e03ae5afd..3b6219f010 100644 --- a/src/blockdata/constants.rs +++ b/src/blockdata/constants.rs @@ -16,6 +16,7 @@ use crate::hashes::hex::{self, HexIterator}; use crate::hashes::{Hash, sha256d}; use crate::blockdata::opcodes; use crate::blockdata::script; +use crate::blockdata::locktime::PackedLockTime; use crate::blockdata::transaction::{OutPoint, Transaction, TxOut, TxIn, Sequence}; use crate::blockdata::block::{Block, BlockHeader}; use crate::blockdata::witness::Witness; @@ -53,6 +54,8 @@ pub const SCRIPT_ADDRESS_PREFIX_TEST: u8 = 196; // 0xc4 pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; /// How may blocks between halvings. pub const SUBSIDY_HALVING_INTERVAL: u32 = 210_000; +/// Maximum allowed value for an integer in Script. +pub const MAX_SCRIPTNUM_VALUE: u32 = 0x80000000; // 2^31 /// In Bitcoind this is insanely described as ~((u256)0 >> 32) pub fn max_target(_: Network) -> Uint256 { @@ -71,7 +74,7 @@ fn bitcoin_genesis_tx() -> Transaction { // Base let mut ret = Transaction { version: 1, - lock_time: 0, + lock_time: PackedLockTime::ZERO, input: vec![], output: vec![], }; @@ -199,6 +202,7 @@ mod test { use crate::hashes::hex::{ToHex, FromHex}; use crate::network::constants::Network; use crate::consensus::encode::serialize; + use crate::blockdata::locktime::PackedLockTime; #[test] fn bitcoin_genesis_first_transaction() { @@ -216,7 +220,7 @@ mod test { assert_eq!(serialize(&gen.output[0].script_pubkey), Vec::from_hex("434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac").unwrap()); assert_eq!(gen.output[0].value, 50 * COIN_VALUE); - assert_eq!(gen.lock_time, 0); + assert_eq!(gen.lock_time, PackedLockTime::ZERO); assert_eq!(gen.wtxid().to_hex(), "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"); } diff --git a/src/blockdata/locktime.rs b/src/blockdata/locktime.rs new file mode 100644 index 0000000000..440006b109 --- /dev/null +++ b/src/blockdata/locktime.rs @@ -0,0 +1,762 @@ +// Rust Bitcoin Library +// Written in 2022 by +// Tobin C. Harding +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! Provides type [`LockTime`] that implements the logic around nLockTime/OP_CHECKLOCKTIMEVERIFY. +//! +//! There are two types of lock time: lock-by-blockheight and lock-by-blocktime, distinguished by +//! whether `LockTime < LOCKTIME_THRESHOLD`. +//! + +use core::{mem, fmt}; +use core::cmp::{PartialOrd, Ordering}; +use core::convert::TryFrom; +use core::str::FromStr; +use core::num::ParseIntError; + +use crate::consensus::encode::{self, Decodable, Encodable}; +use crate::io::{self, Read, Write}; +use crate::prelude::*; +use crate::internal_macros::write_err; + +/// The Threshold for deciding whether a lock time value is a height or a time (see [Bitcoin Core]). +/// +/// `LockTime` values _below_ the threshold are interpreted as block heights, values _above_ (or +/// equal to) the threshold are interpreted as block times (UNIX timestamp, seconds since epoch). +/// +/// Bitcoin is able to safely use this value because a block height greater than 500,000,000 would +/// never occur because it would represent a height in approximately 9500 years. Conversely, block +/// times under 500,000,000 will never happen because they would represent times before 1986 which +/// are, for obvious reasons, not useful within the Bitcoin network. +/// +/// [Bitcoin Core]: https://github.com/bitcoin/bitcoin/blob/9ccaee1d5e2e4b79b0a7c29aadb41b97e4741332/src/script/script.h#L39 +pub const LOCK_TIME_THRESHOLD: u32 = 500_000_000; + +/// Packed lock time wraps a [`LockTime`] consensus value i.e., the raw `u32` used by the network. +/// +/// This struct may be preferred in performance-critical applications because it's slightly smaller +/// than [`LockTime`] and has a bit more performant (de)serialization. In particular, this may be +/// relevant when the value is not processed, just passed around. Note however that the difference +/// is super-small, so unless you do something extreme you shouldn't worry about it. +/// +/// This type implements a naive ordering based on the `u32`, this is _not_ a semantically correct +/// ordering for a lock time, hence [`LockTime`] does not implement `Ord`. This type is useful if +/// you want to use a lock time as part of a struct and wish to derive `Ord`. For all other uses, +/// consider using [`LockTime`] directly. +/// +/// # Examples +/// ``` +/// # use bitcoin::{Amount, PackedLockTime, LockTime}; +/// #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +/// struct S { +/// lock_time: PackedLockTime, +/// amount: Amount, +/// } +/// +/// let _ = S { +/// lock_time: LockTime::from_consensus(741521).into(), +/// amount: Amount::from_sat(10_000_000), +/// }; +/// ``` +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +pub struct PackedLockTime(pub u32); + +impl PackedLockTime { + /// If [`crate::Transaction::lock_time`] is set to zero it is ignored, in other words a + /// transaction with nLocktime==0 is able to be included immediately in any block. + pub const ZERO: PackedLockTime = PackedLockTime(0); + + /// Returns the inner `u32`. + #[inline] + pub fn to_u32(&self) -> u32 { + self.0 + } +} + +impl fmt::Display for PackedLockTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl Encodable for PackedLockTime { + #[inline] + fn consensus_encode(&self, w: &mut W) -> Result { + self.0.consensus_encode(w) + } +} + +impl Decodable for PackedLockTime { + #[inline] + fn consensus_decode(r: &mut R) -> Result { + u32::consensus_decode(r).map(PackedLockTime) + } +} + +impl From for PackedLockTime { + fn from(n: LockTime) -> Self { + PackedLockTime(n.to_consensus_u32()) + } +} + +impl From for LockTime { + fn from(n: PackedLockTime) -> Self { + LockTime::from_consensus(n.0) + } +} + +impl From<&LockTime> for PackedLockTime { + fn from(n: &LockTime) -> Self { + PackedLockTime(n.to_consensus_u32()) + } +} + +impl From<&PackedLockTime> for LockTime { + fn from(n: &PackedLockTime) -> Self { + LockTime::from_consensus(n.0) + } +} + +impl From for u32 { + fn from(p: PackedLockTime) -> Self { + p.0 + } +} + +impl FromStr for PackedLockTime { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + s.parse().map(PackedLockTime) + } +} + +impl TryFrom<&str> for PackedLockTime { + type Error = ParseIntError; + + fn try_from(s: &str) -> Result { + PackedLockTime::from_str(s) + } +} + +impl TryFrom for PackedLockTime { + type Error = ParseIntError; + + fn try_from(s: String) -> Result { + PackedLockTime::from_str(&s) + } +} + +impl fmt::LowerHex for PackedLockTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}", self.0) + } +} + +impl fmt::UpperHex for PackedLockTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:X}", self.0) + } +} + +/// A lock time value, representing either a block height or a UNIX timestamp (seconds since epoch). +/// +/// Used for transaction lock time (`nLockTime` in Bitcoin Core and [`crate::Transaction::lock_time`] +/// in this library) and also for the argument to opcode 'OP_CHECKLOCKTIMEVERIFY`. +/// +/// ### Relevant BIPs +/// +/// * [BIP-65 OP_CHECKLOCKTIMEVERIFY](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki) +/// * [BIP-113 Median time-past as endpoint for lock-time calculations](https://github.com/bitcoin/bips/blob/master/bip-0113.mediawiki) +/// +/// # Examples +/// ``` +/// # use bitcoin::{LockTime, LockTime::*}; +/// # let n = LockTime::from_consensus(100); // n OP_CHECKLOCKTIMEVERIFY +/// # let lock_time = LockTime::from_consensus(100); // nLockTime +/// // To compare lock times there are various `is_satisfied_*` methods, you may also use: +/// let is_satisfied = match (n, lock_time) { +/// (Blocks(n), Blocks(lock_time)) => n <= lock_time, +/// (Seconds(n), Seconds(lock_time)) => n <= lock_time, +/// _ => panic!("handle invalid comparison error"), +/// }; +/// ``` +#[allow(clippy::derive_ord_xor_partial_ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +pub enum LockTime { + /// A block height lock time value. + /// + /// # Examples + /// ```rust + /// use bitcoin::LockTime; + /// + /// let block: u32 = 741521; + /// let n = LockTime::from_height(block).expect("valid height"); + /// assert!(n.is_block_height()); + /// assert_eq!(n.to_consensus_u32(), block); + /// ``` + Blocks(Height), + /// A UNIX timestamp lock time value. + /// + /// # Examples + /// ```rust + /// use bitcoin::LockTime; + /// + /// let seconds: u32 = 1653195600; // May 22nd, 5am UTC. + /// let n = LockTime::from_time(seconds).expect("valid time"); + /// assert!(n.is_block_time()); + /// assert_eq!(n.to_consensus_u32(), seconds); + /// ``` + Seconds(Time), +} + +impl LockTime { + /// If [`crate::Transaction::lock_time`] is set to zero it is ignored, in other words a + /// transaction with nLocktime==0 is able to be included immediately in any block. + pub const ZERO: LockTime = LockTime::Blocks(Height(0)); + + /// Constructs a `LockTime` from an nLockTime value or the argument to OP_CHEKCLOCKTIMEVERIFY. + /// + /// # Examples + /// + /// ```rust + /// # use bitcoin::LockTime; + /// # let n = LockTime::from_consensus(741521); // n OP_CHECKLOCKTIMEVERIFY + /// + /// // `from_consensus` roundtrips as expected with `to_consensus_u32`. + /// let n_lock_time: u32 = 741521; + /// let lock_time = LockTime::from_consensus(n_lock_time); + /// assert_eq!(lock_time.to_consensus_u32(), n_lock_time); + #[inline] + pub fn from_consensus(n: u32) -> Self { + if is_block_height(n) { + Self::Blocks(Height::from_consensus(n).expect("n is valid")) + } else { + Self::Seconds(Time::from_consensus(n).expect("n is valid")) + } + } + + /// Constructs a `LockTime` from `n`, expecting `n` to be a valid block height. + /// + /// See [`LOCK_TIME_THRESHOLD`] for definition of a valid height value. + /// + /// # Examples + /// ```rust + /// # use bitcoin::LockTime; + /// assert!(LockTime::from_height(741521).is_ok()); + /// assert!(LockTime::from_height(1653195600).is_err()); + /// ``` + #[inline] + pub fn from_height(n: u32) -> Result { + let height = Height::from_consensus(n)?; + Ok(LockTime::Blocks(height)) + } + + /// Constructs a `LockTime` from `n`, expecting `n` to be a valid block time. + /// + /// See [`LOCK_TIME_THRESHOLD`] for definition of a valid time value. + /// + /// # Examples + /// ```rust + /// # use bitcoin::LockTime; + /// assert!(LockTime::from_time(1653195600).is_ok()); + /// assert!(LockTime::from_time(741521).is_err()); + /// ``` + #[inline] + pub fn from_time(n: u32) -> Result { + let time = Time::from_consensus(n)?; + Ok(LockTime::Seconds(time)) + } + + /// Returns true if both lock times use the same unit i.e., both height based or both time based. + #[inline] + pub fn is_same_unit(&self, other: LockTime) -> bool { + mem::discriminant(self) == mem::discriminant(&other) + } + + /// Returns true if this lock time value is a block height. + #[inline] + pub fn is_block_height(&self) -> bool { + match *self { + LockTime::Blocks(_) => true, + LockTime::Seconds(_) => false, + } + } + + /// Returns true if this lock time value is a block time (UNIX timestamp). + #[inline] + pub fn is_block_time(&self) -> bool { + !self.is_block_height() + } + + /// Returns true if this timelock constraint is satisfied by the respective `height`/`time`. + /// + /// If `self` is a blockheight based lock then it is checked against `height` and if `self` is a + /// blocktime based lock it is checked against `time`. + /// + /// A 'timelock constraint' refers to the `n` from `n OP_CHEKCLOCKTIMEVERIFY`, this constraint + /// is satisfied if a transaction with nLockTime ([`crate::Transaction::lock_time`]) set to + /// `height`/`time` is valid. + /// + /// # Examples + /// ```no_run + /// # use bitcoin::blockdata::locktime::{LockTime, Height, Time}; + /// // Can be implemented if block chain data is available. + /// fn get_height() -> Height { todo!("return the current block height") } + /// fn get_time() -> Time { todo!("return the current block time") } + /// + /// let n = LockTime::from_consensus(741521); // `n OP_CHEKCLOCKTIMEVERIFY`. + /// if n.is_satisfied_by(get_height(), get_time()) { + /// // Can create and mine a transaction that satisfies the OP_CLTV timelock constraint. + /// } + /// ```` + #[inline] + pub fn is_satisfied_by(&self, height: Height, time: Time) -> bool { + use LockTime::*; + + match *self { + Blocks(n) => n <= height, + Seconds(n) => n <= time, + } + } + + /// Returns the inner `u32` value. This is the value used when creating this `LockTime` + /// i.e., `n OP_CHECKLOCKTIMEVERIFY` or nLockTime. + /// + /// # Warning + /// + /// Do not compare values return by this method. The whole point of the `LockTime` type is to + /// assist in doing correct comparisons. Either use `is_satisfied_by` or use the pattern below: + /// + /// # Examples + /// + /// ```rust + /// # use bitcoin::{LockTime, LockTime::*}; + /// # let n = LockTime::from_consensus(100); // n OP_CHECKLOCKTIMEVERIFY + /// # let lock_time = LockTime::from_consensus(100); // nLockTime + /// + /// let is_satisfied = match (n, lock_time) { + /// (Blocks(n), Blocks(lock_time)) => n <= lock_time, + /// (Seconds(n), Seconds(lock_time)) => n <= lock_time, + /// _ => panic!("invalid comparison"), + /// }; + /// + /// // Or, if you have Rust 1.53 or greater + /// // let is_satisfied = n.partial_cmp(&lock_time).expect("invalid comparison").is_le(); + /// ``` + #[inline] + pub fn to_consensus_u32(&self) -> u32 { + match *self { + LockTime::Blocks(ref h) => h.to_consensus_u32(), + LockTime::Seconds(ref t) => t.to_consensus_u32(), + } + } +} + +impl FromStr for LockTime { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + s.parse().map(LockTime::from_consensus) + } +} + +impl TryFrom<&str> for LockTime { + type Error = ParseIntError; + + fn try_from(s: &str) -> Result { + LockTime::from_str(s) + } +} + +impl TryFrom for LockTime { + type Error = ParseIntError; + + fn try_from(s: String) -> Result { + LockTime::from_str(&s) + } +} + +impl From for LockTime { + fn from(h: Height) -> Self { + LockTime::Blocks(h) + } +} + +impl From