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