1use 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
31pub 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#[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    #[from]
60    InvalidNumber(ParseIntError),
61
62    InvalidHeight(u32),
64
65    InvalidTimestamp(u32),
67
68    InvalidDescriptor(String),
70
71    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(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
80pub struct LockTime(u32);
81
82impl PartialOrd for LockTime {
83    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
84        if self.is_height_based() != other.is_height_based() {
85            None
86        } else {
87            Some(self.0.cmp(&other.0))
88        }
89    }
90}
91
92impl LockTime {
93    pub const ZERO: Self = Self(0);
95
96    #[inline]
98    #[deprecated(since = "0.10.8", note = "use LockTime::ZERO")]
99    pub const fn zero() -> Self { Self(0) }
100
101    #[inline]
106    pub const fn from_height(height: u32) -> Option<Self> {
107        if height < LOCKTIME_THRESHOLD {
108            Some(Self(height))
109        } else {
110            None
111        }
112    }
113
114    #[inline]
119    pub const fn from_unix_timestamp(timestamp: u32) -> Option<Self> {
120        if timestamp < LOCKTIME_THRESHOLD {
121            None
122        } else {
123            Some(Self(timestamp))
124        }
125    }
126
127    #[inline]
130    pub const fn from_consensus_u32(lock_time: u32) -> Self { LockTime(lock_time) }
131
132    #[inline]
133    pub const fn to_consensus_u32(&self) -> u32 { self.0 }
134
135    #[inline]
136    pub const fn into_consensus_u32(self) -> u32 { self.0 }
137
138    #[inline]
141    pub const fn is_height_based(self) -> bool { self.0 < LOCKTIME_THRESHOLD }
142
143    #[inline]
146    pub const fn is_time_based(self) -> bool { !self.is_height_based() }
147}
148
149#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Hash, Debug, Default)]
153#[derive(StrictType, StrictEncode, StrictDecode)]
154#[strict_type(lib = LIB_NAME_BITCOIN)]
155#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
156pub struct LockTimestamp(u32);
157
158impl From<LockTimestamp> for u32 {
159    fn from(lock_timestamp: LockTimestamp) -> Self { lock_timestamp.into_consensus_u32() }
160}
161
162impl From<LockTimestamp> for LockTime {
163    fn from(lock: LockTimestamp) -> Self { LockTime::from_consensus_u32(lock.into_consensus_u32()) }
164}
165
166impl TryFrom<u32> for LockTimestamp {
167    type Error = InvalidTimelock;
168
169    fn try_from(value: u32) -> Result<Self, Self::Error> { Self::try_from_consensus_u32(value) }
170}
171
172impl TryFrom<LockTime> for LockTimestamp {
173    type Error = InvalidTimelock;
174
175    fn try_from(lock_time: LockTime) -> Result<Self, Self::Error> {
176        Self::try_from_lock_time(lock_time)
177    }
178}
179
180impl LockTimestamp {
181    #[inline]
183    pub fn anytime() -> Self { Self(0) }
184
185    #[cfg(feature = "chrono")]
186    pub fn since_now() -> Self {
188        let now = Utc::now();
189        LockTimestamp::from_unix_timestamp(now.timestamp() as u32)
190            .expect("we are too far in the future")
191    }
192
193    #[inline]
198    pub fn from_unix_timestamp(timestamp: u32) -> Option<Self> {
199        if timestamp < LOCKTIME_THRESHOLD {
200            None
201        } else {
202            Some(Self(timestamp))
203        }
204    }
205
206    #[inline]
207    pub const fn try_from_lock_time(lock_time: LockTime) -> Result<Self, InvalidTimelock> {
208        Self::try_from_consensus_u32(lock_time.into_consensus_u32())
209    }
210
211    #[inline]
212    pub const fn try_from_consensus_u32(lock_time: u32) -> Result<Self, InvalidTimelock> {
213        if !LockTime::from_consensus_u32(lock_time).is_time_based() {
214            return Err(InvalidTimelock(lock_time));
215        }
216        Ok(Self(lock_time))
217    }
218
219    #[inline]
222    pub const fn to_consensus_u32(&self) -> u32 { self.0 }
223
224    #[inline]
227    pub const fn into_consensus_u32(self) -> u32 { self.0 }
228
229    #[inline]
231    pub fn into_lock_time(self) -> LockTime { self.into() }
232
233    #[inline]
235    pub fn to_lock_time(self) -> LockTime { self.into_lock_time() }
236}
237
238impl Display for LockTimestamp {
239    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
240        f.write_str("time(")?;
241        Display::fmt(&self.0, f)?;
242        f.write_str(")")
243    }
244}
245
246impl FromStr for LockTimestamp {
247    type Err = TimelockParseError;
248
249    fn from_str(s: &str) -> Result<Self, Self::Err> {
250        let s = s.to_lowercase();
251        if s == "0" || s == "none" {
252            Ok(LockTimestamp::anytime())
253        } else if s.starts_with("time(") && s.ends_with(')') {
254            let no = s[5..].trim_end_matches(')').parse()?;
255            LockTimestamp::try_from(no).map_err(|_| TimelockParseError::InvalidTimestamp(no))
256        } else {
257            Err(TimelockParseError::InvalidDescriptor(s))
258        }
259    }
260}
261
262#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Hash, Debug, Default)]
265#[derive(StrictType, StrictEncode, StrictDecode)]
266#[strict_type(lib = LIB_NAME_BITCOIN)]
267#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
268pub struct LockHeight(u32);
269
270impl From<LockHeight> for u32 {
271    fn from(lock_height: LockHeight) -> Self { lock_height.into_consensus_u32() }
272}
273
274impl From<LockHeight> for LockTime {
275    fn from(lock: LockHeight) -> Self { LockTime::from_consensus_u32(lock.into_consensus_u32()) }
276}
277
278impl TryFrom<u32> for LockHeight {
279    type Error = InvalidTimelock;
280
281    fn try_from(value: u32) -> Result<Self, Self::Error> { Self::try_from_consensus_u32(value) }
282}
283
284impl TryFrom<LockTime> for LockHeight {
285    type Error = InvalidTimelock;
286
287    fn try_from(lock_time: LockTime) -> Result<Self, Self::Error> {
288        Self::try_from_lock_time(lock_time)
289    }
290}
291
292impl LockHeight {
293    #[inline]
295    pub fn anytime() -> Self { Self(0) }
296
297    #[inline]
302    pub fn from_height(height: u32) -> Option<Self> {
303        if height < LOCKTIME_THRESHOLD {
304            Some(Self(height))
305        } else {
306            None
307        }
308    }
309
310    #[inline]
311    pub const fn try_from_lock_time(lock_time: LockTime) -> Result<Self, InvalidTimelock> {
312        Self::try_from_consensus_u32(lock_time.into_consensus_u32())
313    }
314
315    #[inline]
316    pub const fn try_from_consensus_u32(lock_time: u32) -> Result<Self, InvalidTimelock> {
317        if !LockTime::from_consensus_u32(lock_time).is_height_based() {
318            return Err(InvalidTimelock(lock_time));
319        }
320        Ok(Self(lock_time))
321    }
322
323    #[inline]
326    pub const fn to_consensus_u32(&self) -> u32 { self.0 }
327
328    #[inline]
331    pub const fn into_consensus_u32(self) -> u32 { self.0 }
332
333    #[inline]
335    pub fn to_lock_time(&self) -> LockTime { self.into_lock_time() }
336
337    #[inline]
339    pub fn into_lock_time(self) -> LockTime { self.into() }
340}
341
342impl Display for LockHeight {
343    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
344        f.write_str("height(")?;
345        Display::fmt(&self.0, f)?;
346        f.write_str(")")
347    }
348}
349
350impl FromStr for LockHeight {
351    type Err = TimelockParseError;
352
353    fn from_str(s: &str) -> Result<Self, Self::Err> {
354        let s = s.to_lowercase();
355        if s == "0" || s == "none" {
356            Ok(LockHeight::anytime())
357        } else if s.starts_with("height(") && s.ends_with(')') {
358            let no = s[7..].trim_end_matches(')').parse()?;
359            LockHeight::try_from(no).map_err(|_| TimelockParseError::InvalidHeight(no))
360        } else {
361            Err(TimelockParseError::InvalidDescriptor(s))
362        }
363    }
364}
365
366#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
367#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
368#[strict_type(lib = LIB_NAME_BITCOIN)]
369#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
370pub struct SeqNo(u32);
371
372impl SeqNo {
373    pub const ZERO: SeqNo = SeqNo(0);
374
375    #[inline]
376    pub const fn from_consensus_u32(lock_time: u32) -> Self { SeqNo(lock_time) }
377
378    #[inline]
379    pub const fn to_consensus_u32(&self) -> u32 { self.0 }
380
381    #[inline]
383    pub const fn from_height(blocks: u16) -> SeqNo { SeqNo(blocks as u32) }
384
385    #[inline]
388    pub const fn from_intervals(intervals: u16) -> SeqNo {
389        SeqNo(intervals as u32 | SEQ_NO_CSV_TYPE_MASK)
390    }
391
392    pub const fn time_lock_interval(self) -> Option<TimeLockInterval> {
395        if self.0 & SEQ_NO_CSV_DISABLE_MASK != 0 {
396            None
397        } else if self.0 & SEQ_NO_CSV_TYPE_MASK != 0 {
398            Some(TimeLockInterval::Time((self.0 & 0xFFFF) as u16))
399        } else {
400            Some(TimeLockInterval::Height((self.0 & 0xFFFF) as u16))
401        }
402    }
403
404    pub const fn is_timelock(self) -> bool { self.0 & SEQ_NO_CSV_DISABLE_MASK > 1 }
405}
406
407#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
410#[derive(StrictType, StrictEncode, StrictDecode)]
411#[strict_type(lib = LIB_NAME_BITCOIN, tags = order)]
412#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
413pub enum TimeLockInterval {
414    #[display("height({0})")]
416    Height(u16),
417
418    #[display("time({0})")]
420    Time(u16),
421}
422
423impl Default for TimeLockInterval {
424    fn default() -> Self { TimeLockInterval::Height(default!()) }
425}