UltrafastSecp256k1 3.50.0
Ultra high-performance secp256k1 elliptic curve cryptography library
Loading...
Searching...
No Matches
debug_invariants.hpp
Go to the documentation of this file.
1// ============================================================================
2// Debug Invariant Assertions for Hot Paths
3// Phase V, Task 5.3.3 -- Compile-time gated, zero overhead in release
4// ============================================================================
5// Include this header in source files that need debug-mode invariant checking.
6//
7// In Debug builds (-DNDEBUG not defined):
8// - SECP_ASSERT(expr) evaluates expr and aborts on failure
9// - SECP_ASSERT_NORMALIZED(fe) checks field element is canonical (< p)
10// - SECP_ASSERT_ON_CURVE(pt) checks point lies on secp256k1
11// - SECP_ASSERT_SCALAR_VALID(s) checks scalar is < n and non-zero
12//
13// In Release builds (-DNDEBUG defined):
14// - All macros compile to nothing (zero overhead)
15//
16// Usage:
17// #include "secp256k1/debug_invariants.hpp"
18// ...
19// void scalar_mul_inner(const Scalar& k, const Point& P, Point& out) {
20// SECP_ASSERT_SCALAR_VALID(k);
21// SECP_ASSERT_ON_CURVE(P);
22// // ... hot path ...
23// SECP_ASSERT_ON_CURVE(out);
24// }
25//
26// ============================================================================
27
28#ifndef SECP256K1_DEBUG_INVARIANTS_HPP
29#define SECP256K1_DEBUG_INVARIANTS_HPP
30
31#include <cstdio>
32#include <cstdlib>
33#include <cstdint>
34
35// -- Release builds: zero overhead ----------------------------------------
36
37#if defined(NDEBUG) && !defined(SECP256K1_FORCE_INVARIANTS)
38
39#define SECP_ASSERT(expr) ((void)0)
40#define SECP_ASSERT_MSG(expr, msg) ((void)0)
41#define SECP_ASSERT_NORMALIZED(fe) ((void)0)
42#define SECP_ASSERT_ON_CURVE(pt) ((void)0)
43#define SECP_ASSERT_SCALAR_VALID(s) ((void)0)
44#define SECP_ASSERT_SCALAR_NONZERO(s) ((void)0)
45#define SECP_ASSERT_NOT_INFINITY(pt) ((void)0)
46#define SECP_ASSERT_FE_LESS_THAN_P(fe) ((void)0)
47#define SECP_DEBUG_COUNTER_INC(name) ((void)0)
48#define SECP_DEBUG_COUNTER_REPORT() ((void)0)
49
50// -- Debug builds: full checking ------------------------------------------
51
52#else
53
54#include "secp256k1/field.hpp"
55#include "secp256k1/scalar.hpp"
56#include "secp256k1/point.hpp"
57
59
60// secp256k1 field prime: p = 2^256 - 2^32 - 977
61// Last limb must be < 0xFFFFFFFEFFFFFC2F... simplified:
62// A normalized field element has limbs < p when treated as 256-bit LE integer.
63inline bool is_normalized_field_element(const FieldElement& fe) noexcept {
64 // p = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
65 // In 64-bit limbs (little-endian): [0xFFFFFFFEFFFFFC2F, 0xFFFF..., 0xFFFF..., 0xFFFF...]
66 static constexpr uint64_t P[4] = {
67 0xFFFFFFFEFFFFFC2Full,
68 0xFFFFFFFFFFFFFFFFull,
69 0xFFFFFFFFFFFFFFFFull,
70 0xFFFFFFFFFFFFFFFFull
71 };
72
73 const auto& l = fe.limbs();
74 // Compare from most significant limb
75 for (int i = 3; i >= 0; --i) {
76 if (l[i] < P[i]) return true;
77 if (l[i] > P[i]) return false;
78 }
79 // Equal to p -- not canonical (should be reduced to 0)
80 return false;
81}
82
83// Check if point (x, y) satisfies y^2 = x^3 + 7 (mod p)
84inline bool is_on_curve(const Point& pt) noexcept {
85 if (pt.is_infinity()) return true;
86
87#if defined(SECP256K1_FAST_52BIT)
88 // Direct Jacobian check in native FE52 arithmetic.
89 // Avoids the FE52->FE64 conversion + FE64 inverse/mul chain
90 // that can produce wrong results on some compiler/platform combos.
91 //
92 // For Jacobian (X, Y, Z): y_aff = Y/Z^3, x_aff = X/Z^2
93 // Curve equation y^2 = x^3 + 7 becomes Y^2 = X^3 + 7*Z^6
94 const FieldElement52& X = pt.X52();
95 const FieldElement52& Y = pt.Y52();
96 const FieldElement52& Z = pt.Z52();
97
98 FieldElement52 const Y2 = Y.square();
99 FieldElement52 const Z2 = Z.square();
100 FieldElement52 const Z4 = Z2.square();
101 FieldElement52 const Z6 = Z4 * Z2;
102 FieldElement52 const X2 = X.square();
103 FieldElement52 const X3 = X2 * X;
104
105 // Construct literal 7 in 5x52 representation
106 FieldElement52 seven{};
107 seven.n[0] = 7;
108
109 // rhs = X^3 + 7*Z^6 (lazy add; magnitude = 2, well within headroom)
110 FieldElement52 const rhs = X3 + seven * Z6;
111
112 // FE52 operator== normalizes both sides internally
113 return Y2 == rhs;
114#else
115 // FE64 path for platforms without 5x52 support.
116 FieldElement x = pt.x();
117 FieldElement y = pt.y();
118
119 FieldElement lhs = y.square();
120
121 FieldElement x2 = x.square();
122 FieldElement x3 = x2 * x;
124
125 // Normalize both sides before comparing:
126 // Some optimized *_impl paths may produce results in [p, 2^256)
127 // that are correct mod p but not canonical.
128 // Adding zero forces add_impl's conditional p-subtraction.
129 lhs = lhs + FieldElement::zero();
130 rhs = rhs + FieldElement::zero();
131
132 return lhs == rhs;
133#endif
134}
135
136// Check scalar is in range [1, n-1]
137inline bool is_valid_scalar(const Scalar& s) noexcept {
138 // n = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
139 // If scalar arithmetic is correct, result is always reduced mod n
140 // Just check non-zero for most checks
141 return !s.is_zero();
142}
143
144// Debug counters for hot-path monitoring
146 uint64_t field_mul_count = 0;
147 uint64_t field_sqr_count = 0;
148 uint64_t point_add_count = 0;
149 uint64_t point_dbl_count = 0;
150 uint64_t scalar_mul_count = 0;
152
153 void report() const noexcept {
154 (void)std::fprintf(stderr,
155 "[DEBUG COUNTERS]\n"
156 " field_mul: %llu\n"
157 " field_sqr: %llu\n"
158 " point_add: %llu\n"
159 " point_dbl: %llu\n"
160 " scalar_mul: %llu\n"
161 " invariants: %llu\n",
162 (unsigned long long)field_mul_count,
163 (unsigned long long)field_sqr_count,
164 (unsigned long long)point_add_count,
165 (unsigned long long)point_dbl_count,
166 (unsigned long long)scalar_mul_count,
167 (unsigned long long)invariant_check_count);
168 }
169};
170
171inline DebugCounters& counters() noexcept {
172 static thread_local DebugCounters c;
173 return c;
174}
175
176} // namespace secp256k1::fast::debug
177
178// -- Assertion macros ----------------------------------------------------
179
180#define SECP_ASSERT(expr) do { \
181 if (!(expr)) { \
182 (void)std::fprintf(stderr, \
183 "SECP_ASSERT FAILED: %s\n at %s:%d (%s)\n", \
184 #expr, __FILE__, __LINE__, __func__); \
185 std::abort(); \
186 } \
187} while(0)
188
189#define SECP_ASSERT_MSG(expr, msg) do { \
190 if (!(expr)) { \
191 (void)std::fprintf(stderr, \
192 "SECP_ASSERT FAILED: %s\n %s\n at %s:%d (%s)\n", \
193 #expr, msg, __FILE__, __LINE__, __func__); \
194 std::abort(); \
195 } \
196} while(0)
197
198#define SECP_ASSERT_NORMALIZED(fe) do { \
199 ++secp256k1::fast::debug::counters().invariant_check_count; \
200 if (!secp256k1::fast::debug::is_normalized_field_element(fe)) { \
201 (void)std::fprintf(stderr, \
202 "SECP_ASSERT_NORMALIZED FAILED: field element not canonical\n" \
203 " at %s:%d (%s)\n", __FILE__, __LINE__, __func__); \
204 const auto& _l = (fe).limbs(); \
205 (void)std::fprintf(stderr, " limbs: [%016llx, %016llx, %016llx, %016llx]\n", \
206 (unsigned long long)_l[0], (unsigned long long)_l[1], \
207 (unsigned long long)_l[2], (unsigned long long)_l[3]); \
208 std::abort(); \
209 } \
210} while(0)
211
212#define SECP_ASSERT_ON_CURVE(pt) do { \
213 ++secp256k1::fast::debug::counters().invariant_check_count; \
214 if (!secp256k1::fast::debug::is_on_curve(pt)) { \
215 (void)std::fprintf(stderr, \
216 "SECP_ASSERT_ON_CURVE FAILED: point not on secp256k1\n" \
217 " at %s:%d (%s)\n", __FILE__, __LINE__, __func__); \
218 std::abort(); \
219 } \
220} while(0)
221
222#define SECP_ASSERT_SCALAR_VALID(s) do { \
223 ++secp256k1::fast::debug::counters().invariant_check_count; \
224 if (!secp256k1::fast::debug::is_valid_scalar(s)) { \
225 (void)std::fprintf(stderr, \
226 "SECP_ASSERT_SCALAR_VALID FAILED: scalar is zero\n" \
227 " at %s:%d (%s)\n", __FILE__, __LINE__, __func__); \
228 std::abort(); \
229 } \
230} while(0)
231
232#define SECP_ASSERT_SCALAR_NONZERO(s) SECP_ASSERT_SCALAR_VALID(s)
233
234#define SECP_ASSERT_NOT_INFINITY(pt) do { \
235 if ((pt).is_infinity()) { \
236 (void)std::fprintf(stderr, \
237 "SECP_ASSERT_NOT_INFINITY FAILED\n" \
238 " at %s:%d (%s)\n", __FILE__, __LINE__, __func__); \
239 std::abort(); \
240 } \
241} while(0)
242
243#define SECP_ASSERT_FE_LESS_THAN_P(fe) SECP_ASSERT_NORMALIZED(fe)
244
245#define SECP_DEBUG_COUNTER_INC(name) \
246 (++secp256k1::fast::debug::counters().name ## _count)
247
248#define SECP_DEBUG_COUNTER_REPORT() \
249 secp256k1::fast::debug::counters().report()
250
251#endif // NDEBUG
252
253#endif // SECP256K1_DEBUG_INVARIANTS_HPP
FieldElement square() const
static FieldElement zero()
static FieldElement from_uint64(std::uint64_t value)
bool is_normalized_field_element(const FieldElement &fe) noexcept
bool is_valid_scalar(const Scalar &s) noexcept
DebugCounters & counters() noexcept
bool is_on_curve(const Point &pt) noexcept
bool is_zero() const noexcept
FieldElement52 square() const noexcept