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