Skip to content

Commit

Permalink
Add timelock module
Browse files Browse the repository at this point in the history
Bitcoin supports various timelocks based on the nLockTime and nSequence
numbers. Absolute timelocks are defined in BIP-65 and relative timelocks
are defined in BIP-112 and BIP-68.

Add a timelock module to support the various Bitcoin timelocks.
  • Loading branch information
tcharding committed May 16, 2022
1 parent 491da3e commit e766192
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/blockdata/constants.rs
Expand Up @@ -64,6 +64,25 @@ pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520;
/// How may blocks between halvings.
pub const SUBSIDY_HALVING_INTERVAL: u32 = 210_000;

/// 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. Applies 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;

/// Granularity for time-based relative lock-time is fixed at 512 seconds, equivalent to 2^9.
/// https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki
pub const SEQUENCE_LOCKTIME_GRANULARITY: u32 = 9;

/// In Bitcoind this is insanely described as ~((u256)0 >> 32)
pub fn max_target(_: Network) -> Uint256 {
Uint256::from_u64(0xFFFF).unwrap() << 208
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Expand Up @@ -135,6 +135,7 @@ pub use util::amount::Denomination;
pub use util::amount::SignedAmount;
pub use util::merkleblock::MerkleBlock;
pub use util::sighash::SchnorrSighashType;
pub use util::timelock;

pub use util::ecdsa::{self, EcdsaSig, EcdsaSigError};
pub use util::schnorr::{self, SchnorrSig, SchnorrSigError};
Expand Down
1 change: 1 addition & 0 deletions src/util/mod.rs
Expand Up @@ -33,6 +33,7 @@ pub mod taproot;
pub mod uint;
pub mod bip158;
pub mod sighash;
pub mod timelock;

pub(crate) mod endian;

Expand Down
236 changes: 236 additions & 0 deletions src/util/timelock.rs
@@ -0,0 +1,236 @@
// Rust Bitcoin Library
// Written in 2022 by
// Tobin C. Harding <me@tobin.cc>
// 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 <http://creativecommons.org/publicdomain/zero/1.0/>.
//

//! Bitcoin timelocks.
//!
//! Relative and absolute timelocks based on `nSequence` and `nLockTime`.
//!

#[cfg(feature = "std")]
use std::error;

use core::fmt;

use blockdata::constants::{
LOCKTIME_THRESHOLD, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_GRANULARITY,
SEQUENCE_LOCKTIME_TYPE_FLAG,
};

/// An absolute time lock, expires after either height or time.
///
/// This is nLockTime as defined in BIP-65.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Abs(u32);

impl Abs {
/// Returns true if this absolute lock is lock-by-blockheight.
pub fn is_block_time(self) -> bool {
self.0 < LOCKTIME_THRESHOLD
}

/// Returns true if this absolute lock is lock-by-blocktime.
pub fn is_block_height(self) -> bool {
self.0 >= LOCKTIME_THRESHOLD
}

/// Returns true if this timelock is the same type as `other` timelock.
pub fn is_same_type(&self, other: Self) -> bool {
self.is_block_time() && other.is_block_time()
|| self.is_block_height() && other.is_block_height()
}

/// Returns true if the timelock is locked at `age`.
pub fn is_locked(&self, age: u32) -> Result<bool, Error> {
self.is_expired(age).map(|b| !b)
}

/// Returns true if the timelock has expired at `age`.
pub fn is_expired(&self, age: u32) -> Result<bool, Error> {
let age = Abs::from(age);
if !self.is_same_type(age) {
return Err(Error::InvalidComparison);
}
Ok(age.0 >= self.0)
}

/// Returns true if this timelock value is zero.
pub fn is_zero(&self) -> bool {
self.0 == 0
}

/// Returns the nLockTime value. This is the inverse of `From<u32>`.
///
/// # Examples
/// ```
/// # use miniscript::miniscript::timelock::Abs;
/// let n = 0xdeadbeef;
/// let lock = Abs::from(n);
/// assert_eq!(lock.n_lock_time(), n);
///```
pub fn n_lock_time(&self) -> u32 {
self.to_u32()
}

/// Returns the time lock value. This is the inverse of `From<u32>`.
// FIXME: Should we provide this method as well as n_lock_time() or just one of them?
pub fn to_u32(&self) -> u32 {
self.0
}
}

impl From<u32> for Abs {
/// Creates an absolute timelock from nLockTime.
fn from(n_lock_time: u32) -> Self {
Abs(n_lock_time)
}
}

impl fmt::Display for Abs {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}

/// Relative time lock, either after n blocks or after duration (time) as defined by BIP-112.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Rel(u32);

const SEQUENCE_LOCKTIME_MASK: u32 = 0x0000ffff;

impl Rel {
/// Returns true if this timelock is blocks-based.
pub fn is_block_based(&self) -> bool {
!self.is_time_based()
}

/// Returns true if this timelock is time-based. This means the relative
/// timelock has units of 512 seconds.
pub fn is_time_based(&self) -> bool {
self.0 & SEQUENCE_LOCKTIME_TYPE_FLAG != 0
}

/// Returns true if this timelock is the same type as `other` timelock.
pub fn is_same_type(&self, other: Self) -> bool {
self.is_time_based() && other.is_time_based()
|| self.is_block_based() && other.is_block_based()
}

/// Returns true if nSequence is NOT interpreted as a relative timelock.
pub fn is_disabled(&self) -> bool {
self.0 & SEQUENCE_LOCKTIME_DISABLE_FLAG != 0
}

/// Returns true if the timelock is locked at `age`.
pub fn is_locked(&self, age: u32) -> Result<bool, Error> {
self.is_expired(age).map(|b| !b)
}

/// Returns true if the timelock has expired at `age`.
pub fn is_expired(&self, age: u32) -> Result<bool, Error> {
let age = Rel::from(age);

// FIXME: Do we need to do this check or should call be required to do this?
if self.is_disabled() || age.is_disabled() {
return Err(Error::Disabled);
}

if !self.is_same_type(age) {
return Err(Error::InvalidComparison);
}

Ok(age.value() >= self.value())
}

/// Returns true if this timelock value is zero.
pub fn is_zero(&self) -> bool {
self.value() == 0
}

/// Returns the value of the relative time lock. Note, this is not the
/// original nSequence number but rather the masked 16 bit value.
pub fn value(&self) -> u16 {
(self.0 & SEQUENCE_LOCKTIME_MASK) as u16
}

/// Converts a relative time-based timelock to seconds.
pub fn seconds(&self) -> Result<u32, Error> {
if !self.is_time_based() {
return Err(Error::NotTimeBased);
}
// 2^9 because units encode 512 second granularity (see BIP-68).
Ok((self.value() as u32) << SEQUENCE_LOCKTIME_GRANULARITY)
}

/// Returns the nSequence value. This is the inverse of `From<u32>`.
///
/// # Examples
/// ```
/// # use miniscript::miniscript::timelock::Rel;
/// let n = 0xdeadbeef;
/// let lock = Rel::from(n);
/// assert_eq!(lock.n_sequence(), n);
///```
pub fn n_sequence(&self) -> u32 {
self.0
}

/// Returns the time lock value. This is the inverse of `From<u32>`.
// FIXME: Should we provide this method as well as n_sequence() or just one of them?
pub fn to_u32(&self) -> u32 {
self.0
}
}

impl From<u32> for Rel {
/// Creates a relative timelock from nSequence.
fn from(n_sequence: u32) -> Self {
Rel(n_sequence)
}
}

impl fmt::Display for Rel {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}

/// Timelock related errors.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Error {
/// Cannot compare different type locks (time vs height/block).
InvalidComparison,
/// Timelock is not a time based lock.
NotTimeBased,
/// Timelock is disabled.
Disabled,
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;

match self {
InvalidComparison => f.write_str("cannot compare different type locks (time vs height/block)"),
NotTimeBased => f.write_str("timelock is not a time based lock"),
Disabled => f.write_str("timelock is disabled"),
}
}
}

#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl error::Error for Error {
fn cause(&self) -> Option<&dyn error::Error> {
None
}
}

0 comments on commit e766192

Please sign in to comment.