dpp/util/is_non_zero_fibonacci_number.rs
1fn is_perfect_square(number: u64) -> bool {
2 if number < 2 {
3 return true;
4 }
5 // Integer square root via Newton's method
6 let mut x = number;
7 let mut y = x.div_ceil(2);
8 while y < x {
9 x = y;
10 y = (x + number / x) / 2;
11 }
12 x * x == number
13}
14
15pub fn is_non_zero_fibonacci_number(number: u64) -> bool {
16 if number == 0 {
17 return false;
18 }
19
20 let square_check_up = 5u64
21 .checked_mul(number)
22 .and_then(|n| n.checked_mul(number))
23 .and_then(|n| n.checked_add(4));
24
25 let square_check_down = 5u64
26 .checked_mul(number)
27 .and_then(|n| n.checked_mul(number))
28 .and_then(|n| n.checked_sub(4));
29
30 match (square_check_up, square_check_down) {
31 (Some(n1), Some(n2)) => is_perfect_square(n1) || is_perfect_square(n2),
32 (Some(n1), None) => is_perfect_square(n1),
33 (None, Some(n2)) => is_perfect_square(n2),
34 (None, None) => false,
35 }
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41
42 /// AUDIT M6: Floating-point imprecision in is_perfect_square for large values.
43 ///
44 /// `is_perfect_square` casts u64 to f64 and checks `sqrt().fract() == 0.0`.
45 /// f64 has only 52 bits of mantissa, so for values above 2^52, precision is lost.
46 /// This means `is_perfect_square` can return incorrect results for large numbers.
47 ///
48 /// Currently not exploitable for `core_fee_per_byte` (u32), but the function
49 /// accepts u64 and is technically unsound for large inputs.
50 ///
51 /// Location: rs-dpp/src/util/is_non_zero_fibonacci_number.rs:1-3
52 #[test]
53 fn test_is_perfect_square_large_values() {
54 // For small values, is_perfect_square works correctly
55 assert!(is_perfect_square(0));
56 assert!(is_perfect_square(1));
57 assert!(is_perfect_square(4));
58 assert!(is_perfect_square(9));
59 assert!(is_perfect_square(16));
60 assert!(!is_perfect_square(2));
61 assert!(!is_perfect_square(3));
62
63 // Find a value where f64 imprecision causes is_perfect_square to give wrong answer.
64 // The number (2^26 + 1)^2 = 2^52 + 2^27 + 1 = 4503599761588225
65 // When cast to f64, this may lose the +1 and appear as (2^26 + 1)^2 exactly,
66 // or nearby non-squares may appear as perfect squares.
67 //
68 // Strategy: find a non-square near a large perfect square where f64 rounds
69 // the sqrt to an exact integer.
70 let base: u64 = (1u64 << 26) + 1; // 67108865
71 let perfect_square = base * base; // 4503599761588225
72
73 // Verify perfect_square is correctly identified
74 assert!(
75 is_perfect_square(perfect_square),
76 "AUDIT M6: {} should be a perfect square ({}^2)",
77 perfect_square,
78 base
79 );
80
81 // Check non-squares adjacent to large perfect squares.
82 // Due to f64 imprecision, some of these may incorrectly return true.
83 let false_positives: Vec<u64> = (1..=10u64)
84 .filter(|&offset| {
85 let candidate = perfect_square + offset;
86 // This should NOT be a perfect square, but f64 may say it is
87 is_perfect_square(candidate)
88 })
89 .collect();
90
91 // If is_perfect_square is sound, there should be no false positives.
92 // With f64 imprecision, some non-squares will be falsely identified as perfect squares.
93 assert!(
94 false_positives.is_empty(),
95 "AUDIT M6: is_perfect_square returned true for non-square values near {}^2: \
96 offsets {:?}. This is caused by f64 imprecision — (number as f64).sqrt() \
97 rounds to an exact integer for these non-square values. \
98 Fix: use integer square root instead of floating-point.",
99 base,
100 false_positives
101 );
102 }
103
104 /// AUDIT M6 (supplementary): Verify is_non_zero_fibonacci_number works for known values
105 #[test]
106 fn test_known_non_zero_fibonacci_numbers() {
107 // Known non-zero Fibonacci numbers
108 let fibs = [
109 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597,
110 ];
111 for &n in &fibs {
112 assert!(
113 is_non_zero_fibonacci_number(n),
114 "{} should be recognized as a Fibonacci number",
115 n
116 );
117 }
118
119 // Known non-Fibonacci numbers
120 let non_fibs = [0, 4, 6, 7, 9, 10, 11, 12, 14, 15, 16, 22, 100];
121 for &n in &non_fibs {
122 assert!(
123 !is_non_zero_fibonacci_number(n),
124 "{} should NOT be recognized as a Fibonacci number",
125 n
126 );
127 }
128 }
129}