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