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