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}