Software APIs
hardened_memory.c
1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
6
10
11// NOTE: The three hardened_mem* functions have similar contents, but the parts
12// that are shared between them are commented only in `memcpy()`.
13status_t hardened_memcpy(uint32_t *restrict dest, const uint32_t *restrict src,
14 size_t word_len) {
15 random_order_t order;
16 random_order_init(&order, word_len);
17
18 size_t count = 0;
19
20 // Immediately convert `src` and `dest` to addresses, which erases their
21 // provenance and causes their addresses to be exposed (in the provenance
22 // sense).
23 uintptr_t src_addr = (uintptr_t)src;
24 uintptr_t dest_addr = (uintptr_t)dest;
25
26 // We need to launder `count`, so that the SW.LOOP-COMPLETION check is not
27 // deleted by the compiler.
28 for (; launderw(count) < word_len; count = launderw(count) + 1) {
29 // The order values themselves are in units of words, but we need `byte_idx`
30 // to be in units of bytes.
31 //
32 // The value obtained from `advance()` is laundered, to prevent
33 // implementation details from leaking across procedures.
34 size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t);
35
36 // Prevent the compiler from reordering the loop; this ensures a
37 // happens-before among indices consistent with `order`.
38 barrierw(byte_idx);
39
40 // Calculate pointers.
41 void *src_word = (void *)launderw(src_addr + byte_idx);
42 void *dest_word = (void *)launderw(dest_addr + byte_idx);
43
44 // Perform the copy, without performing a typed dereference operation.
45 write_32(read_32(src_word), dest_word);
46 }
48 HARDENED_CHECK_EQ(count, word_len);
49
50 return (status_t){.value = (int32_t)launder32((uint32_t)OTCRYPTO_OK.value)};
51}
52
53status_t hardened_memshred(uint32_t *dest, size_t word_len) {
54 random_order_t order;
55 random_order_init(&order, word_len);
56
57 size_t count = 0;
58
59 uintptr_t data_addr = (uintptr_t)dest;
60
61 for (; count < word_len; count = launderw(count) + 1) {
62 size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t);
63 barrierw(byte_idx);
64
65 // Calculate pointer.
66 void *data = (void *)launderw(data_addr + byte_idx);
67
68 // Write a freshly-generated random word to `*data`.
69 write_32(hardened_memshred_random_word(), data);
70 }
72
73 HARDENED_CHECK_EQ(count, word_len);
74
75 return (status_t){.value = (int32_t)launder32((uint32_t)OTCRYPTO_OK.value)};
76}
77
78hardened_bool_t hardened_memeq(const uint32_t *lhs, const uint32_t *rhs,
79 size_t word_len) {
80 random_order_t order;
81 random_order_init(&order, word_len);
82
83 size_t count = 0;
84
85 uintptr_t lhs_addr = (uintptr_t)lhs;
86 uintptr_t rhs_addr = (uintptr_t)rhs;
87
88 uint32_t zeros = 0;
89 uint32_t ones = UINT32_MAX;
90
91 // The loop is almost token-for-token the one above, but the copy is
92 // replaced with something else.
93 for (; count < word_len; count = launderw(count) + 1) {
94 size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t);
95 barrierw(byte_idx);
96
97 // Calculate pointers.
98 void *av = (void *)launderw(lhs_addr + byte_idx);
99 void *bv = (void *)launderw(rhs_addr + byte_idx);
100
101 uint32_t a = read_32(av);
102 uint32_t b = read_32(bv);
103
104 // Launder one of the operands, so that the compiler cannot cache the result
105 // of the xor for use in the next operation.
106 //
107 // We launder `zeroes` so that compiler cannot learn that `zeroes` has
108 // strictly more bits set at the end of the loop.
109 zeros = launder32(zeros) | (launder32(a) ^ b);
110
111 // Same as above. The compiler can cache the value of `a[offset]`, but it
112 // has no chance to strength-reduce this operation.
113 ones = launder32(ones) & (launder32(a) ^ ~b);
114 }
116
117 HARDENED_CHECK_EQ(count, word_len);
118 HARDENED_CHECK_NE(lhs_addr, rhs_addr);
119 if (launder32(zeros) == 0) {
120 HARDENED_CHECK_EQ(ones, UINT32_MAX);
121 return kHardenedBoolTrue;
122 }
123
124 HARDENED_CHECK_NE(ones, UINT32_MAX);
125 return kHardenedBoolFalse;
126}
127
128hardened_bool_t consttime_memeq_byte(const void *lhs, const void *rhs,
129 size_t len) {
130 uint32_t zeros = 0;
131 uint32_t ones = UINT32_MAX;
132
133 random_order_t order;
134 random_order_init(&order, len);
135
136 size_t count = 0;
137
138 uintptr_t lhs_addr = (uintptr_t)lhs;
139 uintptr_t rhs_addr = (uintptr_t)rhs;
140
141 for (; launderw(count) < len; count = launderw(count) + 1) {
142 size_t byte_idx = launderw(random_order_advance(&order));
143 barrierw(byte_idx);
144
145 uint8_t *a = (uint8_t *)launderw(lhs_addr + byte_idx);
146 uint8_t *b = (uint8_t *)launderw(rhs_addr + byte_idx);
147
148 // Launder one of the operands, so that the compiler cannot cache the result
149 // of the xor for use in the next operation.
150 //
151 // We launder `zeroes` so that compiler cannot learn that `zeroes` has
152 // strictly more bits set at the end of the loop.
153 zeros = launder32(zeros) | (launder32((uint32_t)*a) ^ *b);
154
155 // Same as above. The compiler can cache the value of `a[offset]`, but it
156 // has no chance to strength-reduce this operation.
157 ones = launder32(ones) & (launder32((uint32_t)*a) ^ ~(uint32_t)*b);
158 }
159
160 HARDENED_CHECK_EQ(count, len);
161
162 if (launder32(zeros) == 0) {
163 HARDENED_CHECK_EQ(ones, UINT32_MAX);
164 return kHardenedBoolTrue;
165 }
166
167 HARDENED_CHECK_NE(ones, UINT32_MAX);
168 return kHardenedBoolFalse;
169}
170
171status_t hardened_xor(const uint32_t *restrict x, const uint32_t *restrict y,
172 size_t word_len, uint32_t *restrict dest) {
173 // Randomize the content of the output buffer before writing to it.
174 hardened_memshred(dest, word_len);
175
176 // Create a random variable rand.
177 uint32_t rand[word_len];
178 hardened_memshred(rand, word_len);
179
180 // Cast pointers to `uintptr_t` to erase their provenance.
181 uintptr_t x_addr = (uintptr_t)x;
182 uintptr_t y_addr = (uintptr_t)y;
183 uintptr_t dest_addr = (uintptr_t)dest;
184 uintptr_t rand_addr = (uintptr_t)&rand;
185
186 // Generate a random ordering.
187 random_order_t order;
188 random_order_init(&order, word_len);
189 size_t count = 0;
190
191 // XOR the mask with the first share. This loop is modelled off the one in
192 // `hardened_memcpy`; see the comments there for more details.
193 for (; launderw(count) < word_len; count = launderw(count) + 1) {
194 size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t);
195
196 // Prevent the compiler from re-ordering the loop.
197 barrierw(byte_idx);
198
199 // Calculate pointers.
200 uintptr_t xp = x_addr + byte_idx;
201 uintptr_t yp = y_addr + byte_idx;
202 uintptr_t destp = dest_addr + byte_idx;
203 uintptr_t randp = rand_addr + byte_idx;
204
205 // Set the pointers.
206 void *xv = (void *)launderw(xp);
207 void *yv = (void *)launderw(yp);
208 void *destv = (void *)launderw(destp);
209 void *randv = (void *)launderw(randp);
210
211 // Perform the XORs: dest = ((x ^ rand) ^ y) ^ rand
212 write_32(read_32(xv) ^ read_32(randv), destv);
213 write_32(read_32(destv) ^ read_32(yv), destv);
214 write_32(read_32(destv) ^ read_32(randv), destv);
215 }
217 HARDENED_CHECK_EQ(count, word_len);
218
219 return (status_t){.value = (int32_t)launder32((uint32_t)OTCRYPTO_OK.value)};
220}
221
222status_t hardened_xor_in_place(uint32_t *restrict x, const uint32_t *restrict y,
223 size_t word_len) {
224 // Generate a random ordering.
225 random_order_t order;
226 random_order_init(&order, word_len);
227 size_t count = 0;
228
229 // Cast pointers to `uintptr_t` to erase their provenance.
230 uintptr_t x_addr = (uintptr_t)x;
231 uintptr_t y_addr = (uintptr_t)y;
232
233 // XOR the mask with the first share. This loop is modelled off the one in
234 // `hardened_memcpy`; see the comments there for more details.
235 for (; launderw(count) < word_len; count = launderw(count) + 1) {
236 size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t);
237
238 // Prevent the compiler from re-ordering the loop.
239 barrierw(byte_idx);
240
241 // Calculate pointers.
242 void *xv = (void *)launderw(x_addr + byte_idx);
243 void *yv = (void *)launderw(y_addr + byte_idx);
244
245 // Perform an XOR in the array.
246 write_32(read_32(xv) ^ read_32(yv), xv);
247 }
249 HARDENED_CHECK_EQ(count, word_len);
250
251 return (status_t){.value = (int32_t)launder32((uint32_t)OTCRYPTO_OK.value)};
252}
253
254status_t randomized_bytecopy(void *restrict dest, const void *restrict src,
255 size_t byte_len) {
256 random_order_t order;
257 random_order_init(&order, byte_len);
258
259 size_t count = 0;
260
261 uintptr_t src_addr = (uintptr_t)src;
262 uintptr_t dest_addr = (uintptr_t)dest;
263
264 for (; launderw(count) < byte_len; count = launderw(count) + 1) {
265 size_t byte_idx = launderw(random_order_advance(&order));
266 barrierw(byte_idx);
267
268 uint8_t *src_byte_idx = (uint8_t *)launderw(src_addr + byte_idx);
269 uint8_t *dest_byte_idx = (uint8_t *)launderw(dest_addr + byte_idx);
270
271 *(dest_byte_idx) = *(src_byte_idx);
272 }
274 HARDENED_CHECK_EQ(count, byte_len);
275
276 return (status_t){.value = (int32_t)launder32((uint32_t)OTCRYPTO_OK.value)};
277}
278
279status_t randomized_bytexor_in_place(void *restrict x, const void *restrict y,
280 size_t byte_len) {
281 random_order_t order;
282 random_order_init(&order, byte_len);
283
284 size_t count = 0;
285
286 uintptr_t x_addr = (uintptr_t)x;
287 uintptr_t y_addr = (uintptr_t)y;
288
289 for (; launderw(count) < byte_len; count = launderw(count) + 1) {
290 size_t byte_idx = launderw(random_order_advance(&order));
291 barrierw(byte_idx);
292
293 // TODO(#8815) byte writes vs. word-wise integrity
294 uint8_t *x_byte_idx = (uint8_t *)launderw(x_addr + byte_idx);
295 uint8_t *y_byte_idx = (uint8_t *)launderw(y_addr + byte_idx);
296
297 *(x_byte_idx) = *(x_byte_idx) ^ *(y_byte_idx);
298 }
300 HARDENED_CHECK_EQ(count, byte_len);
301
302 return (status_t){.value = (int32_t)launder32((uint32_t)OTCRYPTO_OK.value)};
303}
304
305#ifdef OT_PLATFORM_RV32
306/**
307 * Call the RISC-V addition with carry.
308 *
309 * @param x First input of the addition.
310 * @param y Second input of the addition.
311 * @param carry The carry-in, updates to the carry-out.
312 * @return The addition of x, y, and carry.
313 */
314static inline uint32_t rv32_addc(uint32_t x, uint32_t y, uint32_t *carry) {
315 uint32_t res, next_carry, c1, c2;
316 __asm__ __volatile__(
317 "add %[res], %[x], %[c_in]\n\t"
318 "sltu %[c1], %[res], %[c_in]\n\t"
319 "add %[res], %[res], %[y]\n\t"
320 "sltu %[c2], %[res], %[y]\n\t"
321 "or %[next_c], %[c1], %[c2]"
322 : [res] "=&r"(res), [next_c] "=&r"(next_carry), [c1] "=&r"(c1),
323 [c2] "=&r"(c2)
324 : [x] "r"(x), [y] "r"(y), [c_in] "r"(*carry));
325 *carry = next_carry;
326 return res;
327}
328
329/**
330 * Call the RISC-V subtraction with borrow.
331 *
332 * @param x First input of the subtraction.
333 * @param y Second input of the subtraction.
334 * @param borrow The borrow-in, updates to the borrow-out.
335 * @return The subtraction of x, y, and borrow.
336 */
337static inline uint32_t rv32_subc(uint32_t x, uint32_t y, uint32_t *borrow) {
338 uint32_t res, next_borrow, b1, b2;
339 __asm__ __volatile__(
340 "sltu %[b1], %[x], %[b_in]\n\t"
341 "sub %[res], %[x], %[b_in]\n\t"
342 "sltu %[b2], %[res], %[y]\n\t"
343 "sub %[res], %[res], %[y]\n\t"
344 "or %[next_b], %[b1], %[b2]"
345 : [res] "=&r"(res), [next_b] "=&r"(next_borrow), [b1] "=&r"(b1),
346 [b2] "=&r"(b2)
347 : [x] "r"(x), [y] "r"(y), [b_in] "r"(*borrow));
348 *borrow = next_borrow;
349 return res;
350}
351
352/**
353 * Call the RISC-V select.
354 *
355 *
356 * @param a First input of the select.
357 * @param b Second input of the select.
358 * @param cond The condition to select.
359 * @return `a` if `cond == 1`, or `b` if `cond == 0`.
360 */
361static inline uint32_t rv32_sel(uint32_t cond, uint32_t a, uint32_t b) {
362 uint32_t res, mask, tmp;
363 __asm__ __volatile__(
364 "neg %[mask], %[cond]\n\t" // mask = 0 - cond (0xFFFFFFFF if 1,
365 // 0x00000000 if 0)
366 "xor %[tmp], %[a], %[b]\n\t" // tmp = a ^ b
367 "and %[tmp], %[tmp], %[mask]\n\t" // tmp = (a ^ b) & mask
368 "xor %[res], %[b], %[tmp]" // out = b ^ ((a ^ b) & mask)
369 : [res] "=r"(res), [mask] "=&r"(mask), [tmp] "=&r"(tmp)
370 : [cond] "r"(cond), [a] "r"(a), [b] "r"(b));
371 return res;
372}
373#endif
374
375status_t hardened_add(const uint32_t *restrict x, const uint32_t *restrict y,
376 size_t word_len, uint32_t *restrict dest) {
377 // Randomize the content of the output buffer before writing to it.
378 hardened_memshred(dest, word_len);
379
380 uint32_t carry = 0;
381 size_t count = 0;
382
383 for (; launderw(count) < word_len; count = launderw(count) + 1) {
384#ifdef OT_PLATFORM_RV32
385 dest[count] = rv32_addc(x[count], y[count], &carry);
386#else
387 uint32_t x_val = x[count];
388 uint32_t y_val = y[count];
389
390 uint32_t res = x_val + carry;
391 uint32_t next_carry = (res < carry);
392
393 res += y_val;
394 next_carry += (res < y_val);
395
396 dest[count] = res;
397 carry = next_carry;
398#endif
399 }
400 HARDENED_CHECK_EQ(count, word_len);
401
402 return (status_t){.value = (int32_t)launder32((uint32_t)OTCRYPTO_OK.value)};
403}
404
405status_t hardened_sub(const uint32_t *restrict x, const uint32_t *restrict y,
406 size_t word_len, uint32_t *restrict dest) {
407 // Randomize the content of the output buffer before writing to it.
408 hardened_memshred(dest, word_len);
409
410 uint32_t borrow = 0;
411 size_t count = 0;
412
413 for (; launderw(count) < word_len; count = launderw(count) + 1) {
414#ifdef OT_PLATFORM_RV32
415 dest[count] = rv32_subc(x[count], y[count], &borrow);
416#else
417 uint32_t x_val = x[count];
418 uint32_t y_val = y[count];
419
420 uint32_t res = x_val - borrow;
421
422 uint32_t next_borrow = (x_val < borrow);
423
424 next_borrow += (res < y_val);
425 res -= y_val;
426
427 dest[count] = res;
428 borrow = next_borrow;
429#endif
430 }
431 HARDENED_CHECK_EQ(count, word_len);
432
433 return (status_t){.value = (int32_t)launder32((uint32_t)OTCRYPTO_OK.value)};
434}
435
436status_t hardened_sub_mod(const uint32_t *restrict x,
437 const uint32_t *restrict y,
438 const uint32_t *restrict n, size_t word_len,
439 uint32_t *restrict dest) {
440 // Randomize the content of the output buffer before writing to it.
441 hardened_memshred(dest, word_len);
442
443 // temp_sub = x - y
444 uint32_t temp_sub[word_len];
445 uint32_t borrow = 0;
446 size_t count = 0;
447 for (; launderw(count) < word_len; count = launderw(count) + 1) {
448#ifdef OT_PLATFORM_RV32
449 temp_sub[count] = rv32_subc(x[count], y[count], &borrow);
450#else
451 uint32_t x_val = x[count];
452 uint32_t y_val = y[count];
453 uint32_t res = x_val - borrow;
454 uint32_t next_borrow = (x_val < borrow);
455 next_borrow += (res < y_val);
456 res -= y_val;
457 temp_sub[count] = res;
458 borrow = next_borrow;
459#endif
460 }
461 HARDENED_CHECK_EQ(count, word_len);
462
463 // temp_add = temp_sub + n
464 uint32_t temp_add[word_len];
465 uint32_t carry = 0;
466 count = 0;
467 for (; launderw(count) < word_len; count = launderw(count) + 1) {
468#ifdef OT_PLATFORM_RV32
469 temp_add[count] = rv32_addc(temp_sub[count], n[count], &carry);
470#else
471 uint32_t x_val = temp_sub[count];
472 uint32_t y_val = n[count];
473 uint32_t res = x_val + carry;
474 uint32_t next_carry = (res < carry);
475 res += y_val;
476 next_carry += (res < y_val);
477 temp_add[count] = res;
478 carry = next_carry;
479#endif
480 }
481 HARDENED_CHECK_EQ(count, word_len);
482
483 // If borrow is 1, choose temp_add, otherwise choose temp_sub.
484 uint32_t is_borrow = launder32(borrow);
485
486 count = 0;
487 for (; launderw(count) < word_len; count = launderw(count) + 1) {
488#ifdef OT_PLATFORM_RV32
489 dest[count] = rv32_sel(is_borrow, temp_add[count], temp_sub[count]);
490#else
491 // The mask is all 1s if borrow is 1, and all 0s if borrow is 0.
492 uint32_t mask = ~(is_borrow - 1);
493 // Prevent optimizations of mask.
494 mask = launder32(mask);
495 dest[count] = (temp_add[count] & launder32(mask)) |
496 (temp_sub[count] & launder32(~mask));
497#endif
498 }
499 HARDENED_CHECK_EQ(count, word_len);
500
501 return (status_t){.value = (int32_t)launder32((uint32_t)OTCRYPTO_OK.value)};
502}
503
504status_t hardened_add_mod(const uint32_t *restrict x,
505 const uint32_t *restrict y,
506 const uint32_t *restrict n, size_t word_len,
507 uint32_t *restrict dest) {
508 // Randomize the content of the output buffer before writing to it.
509 hardened_memshred(dest, word_len);
510
511 // temp_add = x + y
512 uint32_t temp_add[word_len];
513 uint32_t carry = 0;
514 size_t count = 0;
515 for (; launderw(count) < word_len; count = launderw(count) + 1) {
516#ifdef OT_PLATFORM_RV32
517 temp_add[count] = rv32_addc(x[count], y[count], &carry);
518#else
519 uint32_t x_val = x[count];
520 uint32_t y_val = y[count];
521 uint32_t res = x_val + carry;
522 uint32_t next_carry = (res < carry);
523 res += y_val;
524 next_carry += (res < y_val);
525 temp_add[count] = res;
526 carry = next_carry;
527#endif
528 }
529 HARDENED_CHECK_EQ(count, word_len);
530
531 // temp_sub = temp_add - n
532 uint32_t temp_sub[word_len];
533 uint32_t borrow = 0;
534 count = 0;
535 for (; launderw(count) < word_len; count = launderw(count) + 1) {
536#ifdef OT_PLATFORM_RV32
537 temp_sub[count] = rv32_subc(temp_add[count], n[count], &borrow);
538#else
539 uint32_t x_val = temp_add[count];
540 uint32_t y_val = n[count];
541 uint32_t res = x_val - borrow;
542 uint32_t next_borrow = (x_val < borrow);
543 next_borrow += (res < y_val);
544 res -= y_val;
545 temp_sub[count] = res;
546 borrow = next_borrow;
547#endif
548 }
549 HARDENED_CHECK_EQ(count, word_len);
550
551 uint32_t is_ge = launder32(carry) | (1 - launder32(borrow));
552
553 count = 0;
554 for (; launderw(count) < word_len; count = launderw(count) + 1) {
555#ifdef OT_PLATFORM_RV32
556 dest[count] = rv32_sel(is_ge, temp_sub[count], temp_add[count]);
557#else
558 uint32_t mask = ~(is_ge - 1);
559 // Prevent optimizations of mask
560 mask = launder32(mask);
561 dest[count] = (temp_sub[count] & launder32(mask)) |
562 (temp_add[count] & launder32(~mask));
563#endif
564 }
565 HARDENED_CHECK_EQ(count, word_len);
566
567 return (status_t){.value = (int32_t)launder32((uint32_t)OTCRYPTO_OK.value)};
568}
569
570status_t hardened_range_check(const uint32_t *value, const uint32_t *N,
571 size_t word_len) {
572 uint32_t borrow = 0;
573 uint32_t is_zero_acc = 0;
574 size_t count = 0;
575
576 for (; launderw(count) < word_len; count = launderw(count) + 1) {
577 uint32_t val_word = value[count];
578 uint32_t n_word = N[count];
579
580 // Accumulate bits to check if value is zero.
581 is_zero_acc = launder32(is_zero_acc) | launder32(val_word);
582
583#ifdef OT_PLATFORM_RV32
584 (void)rv32_subc(val_word, n_word, &borrow);
585#else
586 // Compute borrow to check if value < N.
587 uint32_t res = val_word - borrow;
588 uint32_t next_borrow = (val_word < borrow);
589 next_borrow += (res < n_word);
590
591 borrow = next_borrow;
592#endif
593 }
594 HARDENED_CHECK_EQ(count, word_len);
595
596 uint32_t is_zero = (launder32(is_zero_acc) == 0);
597 uint32_t is_greater_or_eq = (launder32(borrow) == 0);
598 uint32_t is_bad = launder32(is_zero) | launder32(is_greater_or_eq);
599
600 if (launder32(is_bad) != 0) {
601 return (status_t){
602 .value = (int32_t)launder32((uint32_t)OTCRYPTO_BAD_ARGS.value)};
603 }
604 HARDENED_CHECK_EQ(is_bad, 0);
605
606 return (status_t){.value = (int32_t)launder32((uint32_t)OTCRYPTO_OK.value)};
607}
608
609status_t hardened_mod_reduce(const uint32_t *value, const uint32_t *n,
610 size_t word_len, uint32_t *result) {
611 // This function computes modular reduction (value % n).
612 // It implements a constant-time shift-and-subtract division. It iterates
613 // through the bits of the dividend (`value`) from MSB to LSB, shifting them
614 // into a remainder `r`, and subtracting the divisor `n` in constant time.
615
616 // Remainder, twice the size of the modulus to handle the left shift.
617 uint32_t r[2 * word_len];
618 // Intermediate storing of (r - n).
619 uint32_t r_sub[2 * word_len];
620
621 size_t i = 0;
622
623 // Initialize remainder arrays to zero.
624 for (; launderw(i) < 2 * word_len; i = launderw(i) + 1) {
625 r[i] = 0;
626 r_sub[i] = 0;
627 }
628 HARDENED_CHECK_EQ(i, 2 * word_len);
629
630 // Process each bit of `value` from Most Significant Bit (MSB) down to LSB.
631 i = word_len * 32;
632 for (; launderw(i) > 0; i = launderw(i) - 1) {
633 size_t bit_idx = i - 1;
634 size_t word_idx = bit_idx >> 5;
635 size_t bit_in_word = bit_idx % 32;
636
637 // Shift the current remainder `r` left by 1 bit.
638 uint32_t carry = 0;
639 size_t j = 0;
640 for (; launderw(j) < 2 * word_len; j = launderw(j) + 1) {
641 uint32_t next_carry = (r[j] >> 31);
642 r[j] = (r[j] << 1) | carry;
643 carry = next_carry;
644 }
645 HARDENED_CHECK_EQ(j, 2 * word_len);
646
647 // Inject the current top bit of `value` into the LSB of `r`.
648 uint32_t bit = (value[word_idx] >> bit_in_word) & 1;
649 r[0] = r[0] ^ ((r[0] ^ bit) & 1);
650
651 // Compute `r_sub = r - n`.
652 uint32_t borrow = 0;
653 j = 0;
654 for (; launderw(j) < word_len; j = launderw(j) + 1) {
655#ifdef OT_PLATFORM_RV32
656 r_sub[j] = rv32_subc(r[j], n[j], &borrow);
657#else
658 uint32_t r_word = r[j];
659 uint32_t n_word = n[j];
660 uint32_t res = r_word - borrow;
661 uint32_t next_borrow = (r_word < borrow);
662 next_borrow |= (res < n_word);
663 res -= n_word;
664 r_sub[j] = res;
665 borrow = next_borrow;
666#endif
667 }
668 HARDENED_CHECK_EQ(j, word_len);
669
670 // Propagate the borrow through the upper half of r
671 j = word_len;
672 for (; launderw(j) < 2 * word_len; j = launderw(j) + 1) {
673#ifdef OT_PLATFORM_RV32
674 r_sub[j] = rv32_subc(r[j], 0, &borrow);
675#else
676 uint32_t res = r[j] - borrow;
677 borrow = (r[j] < borrow);
678 r_sub[j] = res;
679#endif
680 }
681 HARDENED_CHECK_EQ(j, 2 * word_len);
682
683 // Conditional swap.
684 // If r < n, the final borrow is 1. If r >= n, the final borrow is 0.
685#ifdef OT_PLATFORM_RV32
686 uint32_t cond = launder32(1 - launder32(borrow));
687#else
688 uint32_t mask = borrow - 1;
689 // Prevent compiler optimizations of the mask.
690 mask = launder32(mask);
691#endif
692
693 j = 0;
694 for (; launderw(j) < 2 * word_len; j = launderw(j) + 1) {
695#ifdef OT_PLATFORM_RV32
696 r[j] = rv32_sel(cond, r_sub[j], r[j]);
697#else
698 r[j] = (r_sub[j] & launder32(mask)) | (r[j] & launder32(~mask));
699#endif
700 }
701 HARDENED_CHECK_EQ(j, 2 * word_len);
702 }
703 HARDENED_CHECK_EQ(i, 0);
704
705 // Copy the lower word_len elements of the final remainder into the result
706 // array.
707 TRY(hardened_memcpy(result, r, word_len));
708
709 return (status_t){.value = (int32_t)launder32((uint32_t)OTCRYPTO_OK.value)};
710}