Skip to main content

blvm_primitives/crypto/
int_ops.rs

1//! Optimized integer arithmetic operations for hot paths
2//!
3//! Provides fast-path implementations for common arithmetic operations
4//! that are used frequently in validation code. The fast path uses
5//! pre-validation to avoid checked arithmetic overhead when values are
6//! known to be safe.
7
8use crate::constants::MAX_MONEY;
9use crate::error::{ConsensusError, Result};
10// Cold error construction helper - this path is rarely taken
11#[cold]
12fn make_arithmetic_overflow_error() -> ConsensusError {
13    ConsensusError::TransactionValidation("Arithmetic overflow".into())
14}
15
16/// Safe maximum value for fast-path arithmetic
17///
18/// Values below this threshold are guaranteed to not overflow when
19/// added together (even with many additions). This is set conservatively
20/// to ensure safety.
21#[allow(dead_code)] // Used in tests
22const MAX_SAFE_VALUE: i64 = MAX_MONEY / 2;
23
24/// Fast-path addition with overflow checking
25///
26/// Uses manual overflow detection for common cases (both positive values)
27/// which is faster than `checked_add` for the hot path. Falls back to
28/// `checked_add` for edge cases.
29///
30/// # Safety
31///
32/// This function maintains the same safety guarantees as `checked_add`,
33/// but with better performance for common cases.
34#[inline(always)]
35#[cfg(feature = "production")]
36pub fn safe_add(a: i64, b: i64) -> Result<i64> {
37    // Fast path: both values are positive (common case in Bitcoin)
38    // Manual overflow check: a + b > i64::MAX is equivalent to a > i64::MAX - b
39    if a >= 0 && b >= 0 {
40        if a > i64::MAX - b {
41            return Err(make_arithmetic_overflow_error());
42        }
43        Ok(a + b)
44    } else if a < 0 && b < 0 {
45        // Both negative: check for underflow (a + b < i64::MIN)
46        // Equivalent to a < i64::MIN - b
47        if a < i64::MIN - b {
48            return Err(ConsensusError::TransactionValidation(
49                "Arithmetic underflow".into(),
50            ));
51        }
52        Ok(a + b)
53    } else {
54        // Mixed signs: use checked arithmetic (overflow not possible, but safer)
55        a.checked_add(b).ok_or_else(make_arithmetic_overflow_error)
56    }
57}
58
59#[cfg(not(feature = "production"))]
60#[inline]
61pub fn safe_add(a: i64, b: i64) -> Result<i64> {
62    // Always use checked arithmetic in non-production builds
63    a.checked_add(b).ok_or_else(make_arithmetic_overflow_error)
64}
65
66/// Fast-path subtraction with overflow checking
67///
68/// Uses manual overflow detection for common cases which is faster than
69/// `checked_sub` for the hot path. Falls back to `checked_sub` for edge cases.
70///
71/// # Safety
72///
73/// This function maintains the same safety guarantees as `checked_sub`,
74/// but with better performance for common cases.
75#[inline(always)]
76#[cfg(feature = "production")]
77pub fn safe_sub(a: i64, b: i64) -> Result<i64> {
78    // Fast path: a >= 0, b >= 0 (common case: subtracting output from input)
79    // Manual underflow check: a - b < i64::MIN is equivalent to a < i64::MIN + b
80    // But since a >= 0 and b >= 0, underflow only happens if result < 0, which is fine for i64
81    // Actually, for a >= 0 and b >= 0, a - b can underflow if b > a, but that's fine (negative result)
82    // The real issue is if a < i64::MIN + b (which can't happen if both are >= 0)
83    if a >= 0 && b >= 0 {
84        // No underflow possible (both positive)
85        Ok(a - b)
86    } else if a < 0 && b < 0 {
87        // Both negative: check for overflow (a - b > i64::MAX)
88        // Equivalent to a > i64::MAX + b, but since both are negative, this can't happen
89        Ok(a - b)
90    } else {
91        // Mixed signs: use checked arithmetic
92        a.checked_sub(b)
93            .ok_or_else(|| ConsensusError::TransactionValidation("Arithmetic underflow".into()))
94    }
95}
96
97#[cfg(not(feature = "production"))]
98#[inline]
99pub fn safe_sub(a: i64, b: i64) -> Result<i64> {
100    // Always use checked arithmetic in non-production builds
101    a.checked_sub(b)
102        .ok_or_else(|| ConsensusError::TransactionValidation("Arithmetic underflow".into()))
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_safe_add_positive() {
111        assert_eq!(safe_add(100, 200).unwrap(), 300);
112        assert_eq!(safe_add(MAX_SAFE_VALUE, 0).unwrap(), MAX_SAFE_VALUE);
113    }
114
115    #[test]
116    fn test_safe_add_overflow() {
117        assert!(safe_add(i64::MAX, 1).is_err());
118    }
119
120    #[test]
121    fn test_safe_sub_positive() {
122        assert_eq!(safe_sub(300, 200).unwrap(), 100);
123        assert_eq!(safe_sub(MAX_SAFE_VALUE, 0).unwrap(), MAX_SAFE_VALUE);
124    }
125
126    #[test]
127    fn test_safe_sub_underflow() {
128        assert!(safe_sub(i64::MIN, 1).is_err());
129    }
130}