Skip to main content

blvm_consensus/
locktime.rs

1//! Shared locktime validation logic for BIP65 (CLTV) and BIP112 (CSV)
2//!
3//! Provides common functions for locktime type detection, value encoding/decoding,
4//! and validation that are shared between CLTV and CSV implementations.
5
6use crate::constants::LOCKTIME_THRESHOLD;
7use crate::types::*;
8use blvm_spec_lock::spec_locked;
9
10/// Locktime type (block height vs timestamp)
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum LocktimeType {
13    /// Block height locktime (< LOCKTIME_THRESHOLD)
14    BlockHeight,
15    /// Unix timestamp locktime (>= LOCKTIME_THRESHOLD)
16    Timestamp,
17}
18
19/// Determine locktime type from value
20///
21/// BIP65/BIP68: If locktime < 500000000, it's block height; otherwise it's Unix timestamp.
22#[spec_locked("5.4.7")]
23#[inline]
24pub fn get_locktime_type(locktime: u32) -> LocktimeType {
25    if locktime < LOCKTIME_THRESHOLD {
26        LocktimeType::BlockHeight
27    } else {
28        LocktimeType::Timestamp
29    }
30}
31
32/// BIP65 CLTV core check: validates that transaction locktime satisfies the script requirement.
33/// Returns true if valid: tx_locktime != 0, types match, and tx_locktime >= stack_locktime.
34#[inline]
35#[spec_locked("5.4.7")]
36pub fn check_bip65(tx_locktime: u32, stack_locktime: u32) -> bool {
37    tx_locktime != 0
38        && locktime_types_match(tx_locktime, stack_locktime)
39        && tx_locktime >= stack_locktime
40}
41
42/// Check if two locktime values have matching types
43///
44/// Used by both BIP65 (CLTV) and BIP112 (CSV) to ensure type consistency.
45#[inline]
46#[spec_locked("5.4.7")]
47pub fn locktime_types_match(locktime1: u32, locktime2: u32) -> bool {
48    get_locktime_type(locktime1) == get_locktime_type(locktime2)
49}
50
51/// Decode locktime value from minimal-encoding byte string
52///
53/// Decodes a little-endian, minimal-encoding locktime value from script stack.
54/// Used by both BIP65 (CLTV) and BIP112 (CSV) for stack value decoding.
55///
56/// # Arguments
57/// * `bytes` - Byte string from stack (max 5 bytes)
58///
59/// # Returns
60/// Decoded u32 value, or None if invalid encoding
61#[spec_locked("5.4.7")]
62pub fn decode_locktime_value(bytes: &[u8]) -> Option<u32> {
63    if bytes.len() > 5 {
64        return None; // Invalid encoding (too large)
65    }
66
67    // Runtime assertion: Byte string length must be <= 5
68    debug_assert!(
69        bytes.len() <= 5,
70        "Locktime byte string length ({}) must be <= 5",
71        bytes.len()
72    );
73
74    let mut value: u32 = 0;
75    for (i, &byte) in bytes.iter().enumerate() {
76        if i >= 4 {
77            break; // Only use first 4 bytes
78        }
79
80        // Runtime assertion: Index must be < 4
81        debug_assert!(i < 4, "Byte index ({i}) must be < 4 for locktime decoding");
82
83        // Runtime assertion: Shift amount must be valid (0-24, multiples of 8)
84        let shift_amount = i * 8;
85        debug_assert!(
86            shift_amount < 32,
87            "Shift amount ({shift_amount}) must be < 32 (i: {i})"
88        );
89
90        value |= (byte as u32) << shift_amount;
91    }
92
93    // value is u32, so it always fits in u32 - no assertion needed
94
95    Some(value)
96}
97
98/// Encode locktime value to minimal-encoding byte string
99///
100/// Encodes a u32 locktime value to minimal little-endian encoding for script stack.
101/// Used for script construction and testing.
102#[spec_locked("5.4.7")]
103pub fn encode_locktime_value(value: u32) -> ByteString {
104    let mut bytes = Vec::new();
105
106    // Minimal encoding: only include bytes up to the highest non-zero byte
107    let mut temp = value;
108    while temp > 0 {
109        bytes.push((temp & 0xff) as u8);
110        temp >>= 8;
111
112        // Runtime assertion: Encoding loop must terminate (temp decreases each iteration)
113        // This is guaranteed by right shift, but documents the invariant
114        debug_assert!(
115            temp < value || bytes.len() <= 4,
116            "Locktime encoding loop must terminate (temp: {}, value: {}, bytes: {})",
117            temp,
118            value,
119            bytes.len()
120        );
121    }
122
123    // If value is 0, return single zero byte
124    if bytes.is_empty() {
125        bytes.push(0);
126    }
127
128    // Runtime assertion: Encoded length must be between 1 and 4 bytes (u32 max)
129    let len = bytes.len();
130    debug_assert!(
131        !bytes.is_empty() && len <= 4,
132        "Encoded locktime length ({len}) must be between 1 and 4 bytes"
133    );
134
135    bytes
136}
137
138/// BIP68: Extract relative locktime type flag from sequence number
139///
140/// Bit 22 (0x00400000) indicates locktime type:
141/// - 0 = block-based relative locktime
142/// - 1 = time-based relative locktime
143#[inline]
144#[spec_locked("5.5")]
145pub fn extract_sequence_type_flag(sequence: u32) -> bool {
146    (sequence & 0x00400000) != 0
147}
148
149/// BIP68: Extract relative locktime value from sequence number
150///
151/// Masks out flags (bits 31, 22) and returns only the locktime value (bits 0-15).
152#[inline]
153#[spec_locked("5.5")]
154pub fn extract_sequence_locktime_value(sequence: u32) -> u16 {
155    (sequence & 0x0000ffff) as u16
156}
157
158/// BIP68: Check if sequence number has disabled bit set
159///
160/// Bit 31 (0x80000000) disables relative locktime when set.
161#[inline]
162#[spec_locked("5.5")]
163pub fn is_sequence_disabled(sequence: u32) -> bool {
164    (sequence & 0x80000000) != 0
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_get_locktime_type_block_height() {
173        assert_eq!(get_locktime_type(100), LocktimeType::BlockHeight);
174        assert_eq!(
175            get_locktime_type(LOCKTIME_THRESHOLD - 1),
176            LocktimeType::BlockHeight
177        );
178    }
179
180    #[test]
181    fn test_get_locktime_type_timestamp() {
182        assert_eq!(
183            get_locktime_type(LOCKTIME_THRESHOLD),
184            LocktimeType::Timestamp
185        );
186        assert_eq!(get_locktime_type(1_000_000_000), LocktimeType::Timestamp);
187    }
188
189    #[test]
190    fn test_locktime_types_match() {
191        assert!(locktime_types_match(100, 200));
192        assert!(locktime_types_match(
193            LOCKTIME_THRESHOLD,
194            LOCKTIME_THRESHOLD + 1000
195        ));
196        assert!(!locktime_types_match(100, LOCKTIME_THRESHOLD));
197    }
198
199    #[test]
200    fn test_decode_locktime_value() {
201        assert_eq!(decode_locktime_value(&vec![100, 0, 0, 0]), Some(100));
202        assert_eq!(decode_locktime_value(&vec![0]), Some(0));
203        assert_eq!(
204            decode_locktime_value(&vec![0xff, 0xff, 0xff, 0xff]),
205            Some(0xffffffff)
206        );
207        assert_eq!(decode_locktime_value(&vec![0; 6]), None); // Too large
208    }
209
210    #[test]
211    fn test_encode_locktime_value() {
212        // Minimal encoding: only include bytes up to highest non-zero byte
213        assert_eq!(encode_locktime_value(100), vec![100]); // 0x64 fits in one byte
214        assert_eq!(encode_locktime_value(0), vec![0]);
215        assert_eq!(
216            encode_locktime_value(0x12345678),
217            vec![0x78, 0x56, 0x34, 0x12]
218        );
219        // Test multi-byte values
220        assert_eq!(encode_locktime_value(0x00001234), vec![0x34, 0x12]);
221        assert_eq!(
222            encode_locktime_value(0x12345600),
223            vec![0x00, 0x56, 0x34, 0x12]
224        );
225    }
226
227    #[test]
228    fn test_extract_sequence_type_flag() {
229        assert!(extract_sequence_type_flag(0x00400000));
230        assert!(!extract_sequence_type_flag(0x00000000));
231        assert!(extract_sequence_type_flag(0x00410000));
232    }
233
234    #[test]
235    fn test_extract_sequence_locktime_value() {
236        assert_eq!(extract_sequence_locktime_value(0x00001234), 0x1234);
237        assert_eq!(extract_sequence_locktime_value(0x00401234), 0x1234); // Flags don't affect value
238    }
239
240    #[test]
241    fn test_is_sequence_disabled() {
242        assert!(is_sequence_disabled(0x80000000));
243        assert!(!is_sequence_disabled(0x00000000));
244        assert!(is_sequence_disabled(0x80010000));
245    }
246}