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}