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(
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 pub const ZERO: Self = Self(0);
99
100 #[inline]
102 #[deprecated(since = "0.10.8", note = "use LockTime::ZERO")]
103 pub const fn zero() -> Self { Self(0) }
104
105 #[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 #[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 #[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 #[inline]
145 pub const fn is_height_based(self) -> bool { self.0 < LOCKTIME_THRESHOLD }
146
147 #[inline]
150 pub const fn is_time_based(self) -> bool { !self.is_height_based() }
151}
152
153#[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 #[inline]
191 pub fn anytime() -> Self { Self(0) }
192
193 #[cfg(feature = "chrono")]
194 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 #[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 #[inline]
230 pub const fn to_consensus_u32(&self) -> u32 { self.0 }
231
232 #[inline]
235 pub const fn into_consensus_u32(self) -> u32 { self.0 }
236
237 #[inline]
239 pub fn into_lock_time(self) -> LockTime { self.into() }
240
241 #[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#[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 #[inline]
307 pub fn anytime() -> Self { Self(0) }
308
309 #[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 #[inline]
338 pub const fn to_consensus_u32(&self) -> u32 { self.0 }
339
340 #[inline]
343 pub const fn into_consensus_u32(self) -> u32 { self.0 }
344
345 #[inline]
347 pub fn to_lock_time(&self) -> LockTime { self.into_lock_time() }
348
349 #[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 #[inline]
399 pub const fn from_height(blocks: u16) -> SeqNo { SeqNo(blocks as u32) }
400
401 #[inline]
404 pub const fn from_intervals(intervals: u16) -> SeqNo {
405 SeqNo(intervals as u32 | SEQ_NO_CSV_TYPE_MASK)
406 }
407
408 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#[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 #[display("height({0})")]
436 Height(u16),
437
438 #[display("time({0})")]
440 Time(u16),
441}
442
443impl Default for TimeLockInterval {
444 fn default() -> Self { TimeLockInterval::Height(default!()) }
445}