bc/
timelocks.rs

1// Bitcoin protocol consensus library.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2019-2024 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved.
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21
22use std::cmp::Ordering;
23use std::fmt::{self, Display, Formatter};
24use std::num::ParseIntError;
25use std::str::FromStr;
26
27use chrono::Utc;
28
29use crate::LIB_NAME_BITCOIN;
30
31/// The Threshold for deciding whether a lock time value is a height or a time
32/// (see [Bitcoin Core]).
33///
34/// `LockTime` values _below_ the threshold are interpreted as block heights,
35/// values _above_ (or equal to) the threshold are interpreted as block times
36/// (UNIX timestamp, seconds since epoch).
37///
38/// Bitcoin is able to safely use this value because a block height greater than
39/// 500,000,000 would never occur because it would represent a height in
40/// approximately 9500 years. Conversely, block times under 500,000,000 will
41/// never happen because they would represent times before 1986 which
42/// are, for obvious reasons, not useful within the Bitcoin network.
43///
44/// [Bitcoin Core]: https://github.com/bitcoin/bitcoin/blob/9ccaee1d5e2e4b79b0a7c29aadb41b97e4741332/src/script/script.h#L39
45pub const LOCKTIME_THRESHOLD: u32 = 500_000_000;
46
47pub const SEQ_NO_CSV_DISABLE_MASK: u32 = 0x80000000;
48pub const SEQ_NO_CSV_TYPE_MASK: u32 = 0x00400000;
49
50/// Error constructing timelock from the provided value.
51#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error)]
52#[display("invalid timelock value {0}")]
53pub struct InvalidTimelock(pub u32);
54
55#[derive(Debug, Clone, PartialEq, Eq, From, Display)]
56#[display(doc_comments)]
57pub enum TimelockParseError {
58    /// invalid number in time lock descriptor
59    #[from]
60    InvalidNumber(ParseIntError),
61
62    /// block height `{0}` is too large for time lock
63    InvalidHeight(u32),
64
65    /// timestamp `{0}` is too small for time lock
66    InvalidTimestamp(u32),
67
68    /// time lock descriptor `{0}` is not recognized
69    InvalidDescriptor(String),
70
71    /// use of randomly-generated RBF sequence numbers requires compilation
72    /// with `rand` feature
73    NoRand,
74}
75
76#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
77#[derive(StrictType, StrictEncode, StrictDecode)]
78#[strict_type(lib = LIB_NAME_BITCOIN)]
79#[cfg_attr(
80    feature = "serde",
81    derive(Serialize, Deserialize),
82    serde(crate = "serde_crate", transparent)
83)]
84pub struct LockTime(u32);
85
86impl PartialOrd for LockTime {
87    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
88        if self.is_height_based() != other.is_height_based() {
89            None
90        } else {
91            Some(self.0.cmp(&other.0))
92        }
93    }
94}
95
96impl LockTime {
97    /// Zero time lock
98    pub const ZERO: Self = Self(0);
99
100    /// Create zero time lock
101    #[inline]
102    #[deprecated(since = "0.10.8", note = "use LockTime::ZERO")]
103    pub const fn zero() -> Self { Self(0) }
104
105    /// Creates absolute time lock with the given block height.
106    ///
107    /// Block height must be strictly less than `0x1DCD6500`, otherwise
108    /// `None` is returned.
109    #[inline]
110    pub const fn from_height(height: u32) -> Option<Self> {
111        if height < LOCKTIME_THRESHOLD {
112            Some(Self(height))
113        } else {
114            None
115        }
116    }
117
118    /// Creates absolute time lock with the given UNIX timestamp value.
119    ///
120    /// Timestamp value must be greater or equal to `0x1DCD6500`, otherwise
121    /// `None` is returned.
122    #[inline]
123    pub const fn from_unix_timestamp(timestamp: u32) -> Option<Self> {
124        if timestamp < LOCKTIME_THRESHOLD {
125            None
126        } else {
127            Some(Self(timestamp))
128        }
129    }
130
131    /// Converts into full u32 representation of `nLockTime` value as it is
132    /// serialized in bitcoin transaction.
133    #[inline]
134    pub const fn from_consensus_u32(lock_time: u32) -> Self { LockTime(lock_time) }
135
136    #[inline]
137    pub const fn to_consensus_u32(&self) -> u32 { self.0 }
138
139    #[inline]
140    pub const fn into_consensus_u32(self) -> u32 { self.0 }
141
142    /// Checks if the absolute timelock provided by the `nLockTime` value
143    /// specifies height-based lock
144    #[inline]
145    pub const fn is_height_based(self) -> bool { self.0 < LOCKTIME_THRESHOLD }
146
147    /// Checks if the absolute timelock provided by the `nLockTime` value
148    /// specifies time-based lock
149    #[inline]
150    pub const fn is_time_based(self) -> bool { !self.is_height_based() }
151}
152
153/// Value for a transaction `nTimeLock` field which is guaranteed to represent a
154/// UNIX timestamp which is always either 0 or a greater than or equal to
155/// 500000000.
156#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Hash, Debug, Default)]
157#[derive(StrictType, StrictEncode, StrictDecode)]
158#[strict_type(lib = LIB_NAME_BITCOIN)]
159#[cfg_attr(
160    feature = "serde",
161    derive(Serialize, Deserialize),
162    serde(crate = "serde_crate", transparent)
163)]
164pub struct LockTimestamp(u32);
165
166impl From<LockTimestamp> for u32 {
167    fn from(lock_timestamp: LockTimestamp) -> Self { lock_timestamp.into_consensus_u32() }
168}
169
170impl From<LockTimestamp> for LockTime {
171    fn from(lock: LockTimestamp) -> Self { LockTime::from_consensus_u32(lock.into_consensus_u32()) }
172}
173
174impl TryFrom<u32> for LockTimestamp {
175    type Error = InvalidTimelock;
176
177    fn try_from(value: u32) -> Result<Self, Self::Error> { Self::try_from_consensus_u32(value) }
178}
179
180impl TryFrom<LockTime> for LockTimestamp {
181    type Error = InvalidTimelock;
182
183    fn try_from(lock_time: LockTime) -> Result<Self, Self::Error> {
184        Self::try_from_lock_time(lock_time)
185    }
186}
187
188impl LockTimestamp {
189    /// Create zero time lock
190    #[inline]
191    pub fn anytime() -> Self { Self(0) }
192
193    #[cfg(feature = "chrono")]
194    /// Creates absolute time lock valid since the current timestamp.
195    pub fn since_now() -> Self {
196        let now = Utc::now();
197        LockTimestamp::from_unix_timestamp(now.timestamp() as u32)
198            .expect("we are too far in the future")
199    }
200
201    /// Creates absolute time lock with the given UNIX timestamp value.
202    ///
203    /// Timestamp value must be greater or equal to `0x1DCD6500`, otherwise
204    /// `None` is returned.
205    #[inline]
206    pub fn from_unix_timestamp(timestamp: u32) -> Option<Self> {
207        if timestamp < LOCKTIME_THRESHOLD {
208            None
209        } else {
210            Some(Self(timestamp))
211        }
212    }
213
214    #[inline]
215    pub const fn try_from_lock_time(lock_time: LockTime) -> Result<Self, InvalidTimelock> {
216        Self::try_from_consensus_u32(lock_time.into_consensus_u32())
217    }
218
219    #[inline]
220    pub const fn try_from_consensus_u32(lock_time: u32) -> Result<Self, InvalidTimelock> {
221        if !LockTime::from_consensus_u32(lock_time).is_time_based() {
222            return Err(InvalidTimelock(lock_time));
223        }
224        Ok(Self(lock_time))
225    }
226
227    /// Converts into full u32 representation of `nLockTime` value as it is
228    /// serialized in bitcoin transaction.
229    #[inline]
230    pub const fn to_consensus_u32(&self) -> u32 { self.0 }
231
232    /// Converts into full u32 representation of `nLockTime` value as it is
233    /// serialized in bitcoin transaction.
234    #[inline]
235    pub const fn into_consensus_u32(self) -> u32 { self.0 }
236
237    /// Converts into [`LockTime`] representation.
238    #[inline]
239    pub fn into_lock_time(self) -> LockTime { self.into() }
240
241    /// Converts into [`LockTime`] representation.
242    #[inline]
243    pub fn to_lock_time(self) -> LockTime { self.into_lock_time() }
244}
245
246impl Display for LockTimestamp {
247    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
248        f.write_str("time(")?;
249        Display::fmt(&self.0, f)?;
250        f.write_str(")")
251    }
252}
253
254impl FromStr for LockTimestamp {
255    type Err = TimelockParseError;
256
257    fn from_str(s: &str) -> Result<Self, Self::Err> {
258        let s = s.to_lowercase();
259        if s == "0" || s == "none" {
260            Ok(LockTimestamp::anytime())
261        } else if s.starts_with("time(") && s.ends_with(')') {
262            let no = s[5..].trim_end_matches(')').parse()?;
263            LockTimestamp::try_from(no).map_err(|_| TimelockParseError::InvalidTimestamp(no))
264        } else {
265            Err(TimelockParseError::InvalidDescriptor(s))
266        }
267    }
268}
269
270/// Value for a transaction `nTimeLock` field which is guaranteed to represent a
271/// block height number which is always less than 500000000.
272#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Hash, Debug, Default)]
273#[derive(StrictType, StrictEncode, StrictDecode)]
274#[strict_type(lib = LIB_NAME_BITCOIN)]
275#[cfg_attr(
276    feature = "serde",
277    derive(Serialize, Deserialize),
278    serde(crate = "serde_crate", transparent)
279)]
280pub struct LockHeight(u32);
281
282impl From<LockHeight> for u32 {
283    fn from(lock_height: LockHeight) -> Self { lock_height.into_consensus_u32() }
284}
285
286impl From<LockHeight> for LockTime {
287    fn from(lock: LockHeight) -> Self { LockTime::from_consensus_u32(lock.into_consensus_u32()) }
288}
289
290impl TryFrom<u32> for LockHeight {
291    type Error = InvalidTimelock;
292
293    fn try_from(value: u32) -> Result<Self, Self::Error> { Self::try_from_consensus_u32(value) }
294}
295
296impl TryFrom<LockTime> for LockHeight {
297    type Error = InvalidTimelock;
298
299    fn try_from(lock_time: LockTime) -> Result<Self, Self::Error> {
300        Self::try_from_lock_time(lock_time)
301    }
302}
303
304impl LockHeight {
305    /// Create zero time lock
306    #[inline]
307    pub fn anytime() -> Self { Self(0) }
308
309    /// Creates absolute time lock with the given block height.
310    ///
311    /// Block height must be strictly less than `0x1DCD6500`, otherwise
312    /// `None` is returned.
313    #[inline]
314    pub fn from_height(height: u32) -> Option<Self> {
315        if height < LOCKTIME_THRESHOLD {
316            Some(Self(height))
317        } else {
318            None
319        }
320    }
321
322    #[inline]
323    pub const fn try_from_lock_time(lock_time: LockTime) -> Result<Self, InvalidTimelock> {
324        Self::try_from_consensus_u32(lock_time.into_consensus_u32())
325    }
326
327    #[inline]
328    pub const fn try_from_consensus_u32(lock_time: u32) -> Result<Self, InvalidTimelock> {
329        if !LockTime::from_consensus_u32(lock_time).is_height_based() {
330            return Err(InvalidTimelock(lock_time));
331        }
332        Ok(Self(lock_time))
333    }
334
335    /// Converts into full u32 representation of `nLockTime` value as it is
336    /// serialized in bitcoin transaction.
337    #[inline]
338    pub const fn to_consensus_u32(&self) -> u32 { self.0 }
339
340    /// Converts into full u32 representation of `nLockTime` value as it is
341    /// serialized in bitcoin transaction.
342    #[inline]
343    pub const fn into_consensus_u32(self) -> u32 { self.0 }
344
345    /// Converts into [`LockTime`] representation.
346    #[inline]
347    pub fn to_lock_time(&self) -> LockTime { self.into_lock_time() }
348
349    /// Converts into [`LockTime`] representation.
350    #[inline]
351    pub fn into_lock_time(self) -> LockTime { self.into() }
352}
353
354impl Display for LockHeight {
355    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
356        f.write_str("height(")?;
357        Display::fmt(&self.0, f)?;
358        f.write_str(")")
359    }
360}
361
362impl FromStr for LockHeight {
363    type Err = TimelockParseError;
364
365    fn from_str(s: &str) -> Result<Self, Self::Err> {
366        let s = s.to_lowercase();
367        if s == "0" || s == "none" {
368            Ok(LockHeight::anytime())
369        } else if s.starts_with("height(") && s.ends_with(')') {
370            let no = s[7..].trim_end_matches(')').parse()?;
371            LockHeight::try_from(no).map_err(|_| TimelockParseError::InvalidHeight(no))
372        } else {
373            Err(TimelockParseError::InvalidDescriptor(s))
374        }
375    }
376}
377
378#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
379#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
380#[strict_type(lib = LIB_NAME_BITCOIN)]
381#[cfg_attr(
382    feature = "serde",
383    derive(Serialize, Deserialize),
384    serde(crate = "serde_crate", transparent)
385)]
386pub struct SeqNo(u32);
387
388impl SeqNo {
389    pub const ZERO: SeqNo = SeqNo(0);
390
391    #[inline]
392    pub const fn from_consensus_u32(lock_time: u32) -> Self { SeqNo(lock_time) }
393
394    #[inline]
395    pub const fn to_consensus_u32(&self) -> u32 { self.0 }
396
397    /// Creates relative time lock measured in number of blocks (implies RBF).
398    #[inline]
399    pub const fn from_height(blocks: u16) -> SeqNo { SeqNo(blocks as u32) }
400
401    /// Creates relative time lock measured in number of 512-second intervals
402    /// (implies RBF).
403    #[inline]
404    pub const fn from_intervals(intervals: u16) -> SeqNo {
405        SeqNo(intervals as u32 | SEQ_NO_CSV_TYPE_MASK)
406    }
407
408    /// Gets structured relative time lock information from the `nSeq` value.
409    /// See [`TimeLockInterval`].
410    pub const fn time_lock_interval(self) -> Option<TimeLockInterval> {
411        if self.0 & SEQ_NO_CSV_DISABLE_MASK != 0 {
412            None
413        } else if self.0 & SEQ_NO_CSV_TYPE_MASK != 0 {
414            Some(TimeLockInterval::Time((self.0 & 0xFFFF) as u16))
415        } else {
416            Some(TimeLockInterval::Height((self.0 & 0xFFFF) as u16))
417        }
418    }
419
420    pub const fn is_timelock(self) -> bool { self.0 & SEQ_NO_CSV_DISABLE_MASK > 1 }
421}
422
423/// Time lock interval describing both relative (OP_CHECKSEQUENCEVERIFY) and
424/// absolute (OP_CHECKTIMELOCKVERIFY) timelocks.
425#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
426#[derive(StrictType, StrictEncode, StrictDecode)]
427#[strict_type(lib = LIB_NAME_BITCOIN, tags = order)]
428#[cfg_attr(
429    feature = "serde",
430    derive(Serialize, Deserialize),
431    serde(crate = "serde_crate", rename_all = "camelCase")
432)]
433pub enum TimeLockInterval {
434    /// Describes number of blocks for the timelock
435    #[display("height({0})")]
436    Height(u16),
437
438    /// Describes number of 512-second intervals for the timelock
439    #[display("time({0})")]
440    Time(u16),
441}
442
443impl Default for TimeLockInterval {
444    fn default() -> Self { TimeLockInterval::Height(default!()) }
445}