UltrafastSecp256k1 3.50.0
Ultra high-performance secp256k1 elliptic curve cryptography library
Loading...
Searching...
No Matches
ops.hpp
Go to the documentation of this file.
1#ifndef SECP256K1_CT_OPS_HPP
2#define SECP256K1_CT_OPS_HPP
3
4// ============================================================================
5// Constant-Time Primitives
6// ============================================================================
7// Low-level building blocks for side-channel resistant code.
8// Every function in this header has a data-independent execution trace:
9// - No secret-dependent branches
10// - No secret-dependent memory access patterns
11// - Fixed instruction count regardless of input
12//
13// These primitives use volatile barriers to prevent the compiler from
14// converting branchless code into branches via if-conversion.
15//
16// Usage:
17// #include <secp256k1/ct/ops.hpp>
18// uint64_t mask = secp256k1::ct::is_zero_mask(x); // 0 or 0xFFFF...
19// secp256k1::ct::cmov64(&dst, &src, flag); // if(flag) dst=src
20// ============================================================================
21
22#include <cstdint>
23#include <cstddef>
24
25// --- Declassify / Classify Markers -------------------------------------------
26// For constant-time verification with Valgrind (memcheck) or MSAN.
27//
28// SECP256K1_CLASSIFY(ptr, len) -- Mark memory as secret (undefined).
29// Call on inputs before CT operations.
30// SECP256K1_DECLASSIFY(ptr, len) -- Mark memory as public (defined).
31// Call on outputs after CT operations.
32//
33// Under normal compilation these are no-ops. When compiled with:
34// -DSECP256K1_CT_VALGRIND=1 and Valgrind headers available:
35// maps to VALGRIND_MAKE_MEM_{UNDEFINED,DEFINED}
36//
37// Usage in tests:
38// Scalar k;
39// SECP256K1_CLASSIFY(&k, sizeof(k)); // treat k as secret
40// Point R = ct::scalar_mul(G, k); // CT operation under test
41// SECP256K1_DECLASSIFY(&R, sizeof(R)); // declassify result for comparison
42//
43// If valgrind reports a "conditional jump depends on uninitialised value"
44// error BETWEEN classify and declassify, the code has a CT violation.
45// -----------------------------------------------------------------------------
46
47#if defined(SECP256K1_CT_VALGRIND) && SECP256K1_CT_VALGRIND
48 #include <valgrind/memcheck.h>
49 #define SECP256K1_CLASSIFY(ptr, len) VALGRIND_MAKE_MEM_UNDEFINED((ptr), (len))
50 #define SECP256K1_DECLASSIFY(ptr, len) VALGRIND_MAKE_MEM_DEFINED((ptr), (len))
51#else
52 #define SECP256K1_CLASSIFY(ptr, len) ((void)(ptr), (void)(len))
53 #define SECP256K1_DECLASSIFY(ptr, len) ((void)(ptr), (void)(len))
54#endif
55
56#if defined(__GNUC__) || defined(__clang__)
57 #define SECP256K1_CT_NO_STACK_PROTECTOR __attribute__((no_stack_protector))
58#else
59 #define SECP256K1_CT_NO_STACK_PROTECTOR
60#endif
61
62namespace secp256k1::ct {
63
64// --- Compiler barrier --------------------------------------------------------
65// Prevents compiler from optimizing away branchless patterns.
66// Uses inline asm (GCC/Clang) or volatile (MSVC) to create optimization barrier.
67
68#if defined(__GNUC__) || defined(__clang__)
69#if defined(__riscv)
70 // RISC-V in-order cores (U74): register-only barrier.
71 // The "memory" clobber is intentionally omitted: it forces
72 // store-to-load-forwarding sequences that have data-dependent
73 // retirement latency on U74's store buffer (zero-coalescing),
74 // creating false-positive dudect leaks in field_add, field_is_zero,
75 // and scalar_add. Register-only is sufficient on in-order cores
76 // because the pipeline cannot reorder past the asm volatile.
77 inline void value_barrier(std::uint64_t& v) noexcept {
78 asm volatile("" : "+r"(v));
79 }
80 inline void value_barrier(std::uint32_t& v) noexcept {
81 asm volatile("" : "+r"(v));
82 }
83#else
84 // x86/ARM OOO cores: memory clobber is cheap; keep full fence.
85 inline void value_barrier(std::uint64_t& v) noexcept {
86 asm volatile("" : "+r"(v) : : "memory");
87 }
88 inline void value_barrier(std::uint32_t& v) noexcept {
89 asm volatile("" : "+r"(v) : : "memory");
90 }
91#endif
92#else
93 // MSVC: volatile prevents optimization of subsequent operations
94 inline void value_barrier(std::uint64_t& v) noexcept {
95 volatile std::uint64_t sink = v;
96 v = sink;
97 }
98 inline void value_barrier(std::uint32_t& v) noexcept {
99 volatile std::uint32_t sink = v;
100 v = sink;
101 }
102#endif
103
104// --- Mask generation ---------------------------------------------------------
105
106// Returns 0xFFFFFFFFFFFFFFFF if v == 0, else 0x0000000000000000
107inline SECP256K1_CT_NO_STACK_PROTECTOR std::uint64_t is_zero_mask(std::uint64_t v) noexcept {
108#if defined(__riscv) && (__riscv_xlen == 64)
109 // RISC-V: seqz + neg produces fully branchless is-zero mask.
110 // seqz tmp, v -> tmp = (v == 0) ? 1 : 0
111 // neg tmp, tmp -> tmp = 0 - tmp (all-ones if was 1, zero if was 0)
112 // asm volatile prevents the compiler from reasoning about the output,
113 // so downstream code stays branchless.
114 std::uint64_t mask;
115 asm volatile(
116 "seqz %0, %1\n\t"
117 "neg %0, %0"
118 : "=r"(mask) : "r"(v));
119 return mask;
120#else
121 // ~(v | (0-v)) has MSB set iff v == 0
122 value_barrier(v); // prevent compiler from recognising v's value range
123 std::uint64_t nv = 0ULL - v;
124 value_barrier(nv); // prevent compiler from knowing nv == (0-v)
125 auto mask = static_cast<std::uint64_t>(
126 -static_cast<std::int64_t>((~(v | nv)) >> 63));
127 value_barrier(mask); // prevent compiler from converting result into branch
128 return mask;
129#endif
130}
131
132// Returns 0xFFFFFFFFFFFFFFFF if v != 0, else 0x0000000000000000
133inline std::uint64_t is_nonzero_mask(std::uint64_t v) noexcept {
134 return ~is_zero_mask(v);
135}
136
137// Returns 0xFFFFFFFFFFFFFFFF if a == b, else 0x0000000000000000
138inline std::uint64_t eq_mask(std::uint64_t a, std::uint64_t b) noexcept {
139 return is_zero_mask(a ^ b);
140}
141
142// Returns 0xFFFFFFFFFFFFFFFF if flag is true (nonzero), else 0
143inline SECP256K1_CT_NO_STACK_PROTECTOR std::uint64_t bool_to_mask(bool flag) noexcept {
144 auto v = static_cast<std::uint64_t>(flag);
145 value_barrier(v);
146 std::uint64_t mask = 0ULL - v;
147 value_barrier(mask); // prevent converting to branch
148 return mask;
149}
150
151// Returns all-ones mask when a is strictly less than b (unsigned), else zero
152// Uses the borrow bit from subtraction
153inline std::uint64_t lt_mask(std::uint64_t a, std::uint64_t b) noexcept {
154 // If a < b, then (a - b) borrows, so bit 64 of the extended result is 1
155 // Subtraction borrows when a < b; detects wrap via XOR-OR-shift pattern
156 std::uint64_t const diff = a - b;
157 // Borrow occurred iff a < b. The borrow is in the "carry out" position.
158 // For unsigned: a < b iff the subtract wraps, iff (a ^ ((a ^ b) | (diff ^ a))) has MSB set
159 std::uint64_t borrow = (a ^ ((a ^ b) | (diff ^ a))) >> 63;
160 value_barrier(borrow);
161 return 0ULL - borrow;
162}
163
164// --- Conditional move (CT) ---------------------------------------------------
165// if (flag) *dst = *src; -- constant time, no branch
166
167inline void cmov64(std::uint64_t* dst, const std::uint64_t* src,
168 std::uint64_t mask) noexcept {
169 // dst = (src & mask) | (dst & ~mask)
170 *dst ^= (*dst ^ *src) & mask;
171}
172
173// Conditional move for 4-limb (256-bit) values
174inline void cmov256(std::uint64_t dst[4], const std::uint64_t src[4],
175 std::uint64_t mask) noexcept {
176 dst[0] ^= (dst[0] ^ src[0]) & mask;
177 dst[1] ^= (dst[1] ^ src[1]) & mask;
178 dst[2] ^= (dst[2] ^ src[2]) & mask;
179 dst[3] ^= (dst[3] ^ src[3]) & mask;
180}
181
182// --- Conditional swap (CT) ---------------------------------------------------
183// if (flag) swap(*a, *b); -- constant time
184
185inline void cswap256(std::uint64_t a[4], std::uint64_t b[4],
186 std::uint64_t mask) noexcept {
187 std::uint64_t const diff0 = (a[0] ^ b[0]) & mask;
188 a[0] ^= diff0;
189 b[0] ^= diff0;
190
191 std::uint64_t const diff1 = (a[1] ^ b[1]) & mask;
192 a[1] ^= diff1;
193 b[1] ^= diff1;
194
195 std::uint64_t const diff2 = (a[2] ^ b[2]) & mask;
196 a[2] ^= diff2;
197 b[2] ^= diff2;
198
199 std::uint64_t const diff3 = (a[3] ^ b[3]) & mask;
200 a[3] ^= diff3;
201 b[3] ^= diff3;
202}
203
204// --- Constant-time select ----------------------------------------------------
205// Returns a if mask == 0xFFF...F, else b (mask must be all-zeros or all-ones)
206
207inline std::uint64_t ct_select(std::uint64_t a, std::uint64_t b,
208 std::uint64_t mask) noexcept {
209 return (a & mask) | (b & ~mask);
210}
211
212// --- Constant-time table lookup ----------------------------------------------
213// Always reads ALL entries. No secret-dependent memory access pattern.
214// table: pointer to N entries of 'stride' bytes each
215// index: which entry to select (0-based)
216// out: output buffer (stride bytes)
217
218inline void ct_lookup(const void* table, std::size_t count,
219 std::size_t stride, std::size_t index,
220 void* out) noexcept {
221 // Zero the output
222 auto* dst = static_cast<std::uint8_t*>(out);
223 for (std::size_t b = 0; b < stride; ++b) dst[b] = 0;
224
225 const auto* src = static_cast<const std::uint8_t*>(table);
226 for (std::size_t i = 0; i < count; ++i) {
227 std::uint64_t const mask = eq_mask(static_cast<std::uint64_t>(i),
228 static_cast<std::uint64_t>(index));
229 auto mask8 = static_cast<std::uint8_t>(mask & 0xFF);
230 for (std::size_t b = 0; b < stride; ++b) {
231 dst[b] |= src[i * stride + b] & mask8;
232 }
233 }
234}
235
236// Specialized: CT lookup for 4-limb (256-bit) entries
237inline void ct_lookup_256(const std::uint64_t table[][4], std::size_t count,
238 std::size_t index, std::uint64_t out[4]) noexcept {
239 out[0] = out[1] = out[2] = out[3] = 0;
240 for (std::size_t i = 0; i < count; ++i) {
241 std::uint64_t const mask = eq_mask(static_cast<std::uint64_t>(i),
242 static_cast<std::uint64_t>(index));
243 out[0] |= table[i][0] & mask;
244 out[1] |= table[i][1] & mask;
245 out[2] |= table[i][2] & mask;
246 out[3] |= table[i][3] & mask;
247 }
248}
249
250} // namespace secp256k1::ct
251
252#undef SECP256K1_CT_NO_STACK_PROTECTOR
253
254#endif // SECP256K1_CT_OPS_HPP
std::uint64_t lt_mask(std::uint64_t a, std::uint64_t b) noexcept
Definition ops.hpp:153
void cswap256(std::uint64_t a[4], std::uint64_t b[4], std::uint64_t mask) noexcept
Definition ops.hpp:185
void cmov64(std::uint64_t *dst, const std::uint64_t *src, std::uint64_t mask) noexcept
Definition ops.hpp:167
std::uint64_t ct_select(std::uint64_t a, std::uint64_t b, std::uint64_t mask) noexcept
Definition ops.hpp:207
std::uint64_t is_nonzero_mask(std::uint64_t v) noexcept
Definition ops.hpp:133
void cmov256(std::uint64_t dst[4], const std::uint64_t src[4], std::uint64_t mask) noexcept
Definition ops.hpp:174
void value_barrier(std::uint64_t &v) noexcept
Definition ops.hpp:94
SECP256K1_CT_NO_STACK_PROTECTOR std::uint64_t bool_to_mask(bool flag) noexcept
Definition ops.hpp:143
std::uint64_t eq_mask(std::uint64_t a, std::uint64_t b) noexcept
Definition ops.hpp:138
SECP256K1_CT_NO_STACK_PROTECTOR std::uint64_t is_zero_mask(std::uint64_t v) noexcept
Definition ops.hpp:107
void ct_lookup(const void *table, std::size_t count, std::size_t stride, std::size_t index, void *out) noexcept
Definition ops.hpp:218
void ct_lookup_256(const std::uint64_t table[][4], std::size_t count, std::size_t index, std::uint64_t out[4]) noexcept
Definition ops.hpp:237
#define SECP256K1_CT_NO_STACK_PROTECTOR
Definition ops.hpp:59