Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
4 changed files
with
257 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |