Compact the existing STM32 field arithmetic and ESP-NOW tail-frame storage without removing features or lowering memory reserves. Refresh power-saving fixtures and pin official-app tests to a published archive.
161 lines
5.3 KiB
C
161 lines
5.3 KiB
C
/*
|
|
* Fixed-loop form of the ref10 field arithmetic in fe.c. The existing STM32
|
|
* size recipes opt in; other builds keep the unrolled code.
|
|
* The radix alternates 26 and 25 bits. Odd-by-odd products therefore have
|
|
* weight 2, and terms beyond bit 254 fold back with weight 19, exactly as in
|
|
* ref10. Product accumulation, decoding, encoding and carry orders are unchanged.
|
|
*
|
|
* All loops, branches and array addresses depend only on public limb indices
|
|
* or the public operation (multiply, square, double-square), not input values.
|
|
* Under ref10's 1.65*2^26 / 1.65*2^25 input bounds, even doubled sums fit in
|
|
* signed 64 bits. Outputs are written only after reading every input, so h
|
|
* may alias f or g. No additional persistent RAM or heap is needed. Compared
|
|
* with the unrolled forms, public-index loop overhead trades CPU time for
|
|
* flash; the fixed number of field operations and secret-independent control
|
|
* flow are retained for every opted-in Companion and Mini Repeater profile.
|
|
*/
|
|
|
|
void fe_add(fe h, const fe f, const fe g) {
|
|
int i;
|
|
for (i = 0; i < 10; ++i) h[i] = f[i] + g[i];
|
|
}
|
|
|
|
void fe_sub(fe h, const fe f, const fe g) {
|
|
int i;
|
|
for (i = 0; i < 10; ++i) h[i] = f[i] - g[i];
|
|
}
|
|
|
|
void fe_neg(fe h, const fe f) {
|
|
int i;
|
|
for (i = 0; i < 10; ++i) h[i] = -f[i];
|
|
}
|
|
|
|
void fe_copy(fe h, const fe f) {
|
|
int i;
|
|
for (i = 0; i < 10; ++i) h[i] = f[i];
|
|
}
|
|
|
|
void fe_cmov(fe f, const fe g, unsigned int b) {
|
|
const int32_t mask = -(int32_t)b;
|
|
int i;
|
|
for (i = 0; i < 10; ++i) f[i] ^= mask & (f[i] ^ g[i]);
|
|
}
|
|
|
|
void fe_cswap(fe f, fe g, unsigned int b) {
|
|
const int32_t mask = -(int32_t)b;
|
|
int i;
|
|
for (i = 0; i < 10; ++i) {
|
|
const int32_t swap = mask & (f[i] ^ g[i]);
|
|
f[i] ^= swap;
|
|
g[i] ^= swap;
|
|
}
|
|
}
|
|
|
|
static void fe_compact_carry(fe h, int64_t limbs[10],
|
|
const unsigned char *order, int count) {
|
|
int i;
|
|
for (i = 0; i < count; ++i) {
|
|
const int index = order[i];
|
|
const int bits = 26 - (index & 1);
|
|
const int64_t radix = (int64_t)1 << bits;
|
|
const int64_t carry = (limbs[index] + radix / 2) >> bits;
|
|
if (index == 9) limbs[0] += carry * 19;
|
|
else limbs[index + 1] += carry;
|
|
/* A carry may be negative; do not left-shift that signed value. */
|
|
limbs[index] -= carry * radix;
|
|
}
|
|
for (i = 0; i < 10; ++i) h[i] = (int32_t)limbs[i];
|
|
}
|
|
|
|
static const unsigned char fe_decode_carries[] = {9, 1, 3, 5, 7, 0, 2, 4, 6, 8};
|
|
|
|
void fe_frombytes(fe h, const unsigned char *s) {
|
|
static const unsigned char offsets[] = {0, 4, 7, 10, 13, 16, 20, 23, 26, 29};
|
|
static const unsigned char shifts[] = {0, 6, 5, 3, 2, 0, 7, 5, 4, 2};
|
|
int64_t limbs[10];
|
|
int i, j;
|
|
for (i = 0; i < 10; ++i) {
|
|
uint32_t word = 0;
|
|
const int count = i == 0 || i == 5 ? 4 : 3;
|
|
for (j = 0; j < count; ++j)
|
|
word |= (uint32_t)s[offsets[i] + j] << (j * 8);
|
|
if (i == 9) word &= 8388607; /* Ignore the encoded sign bit. */
|
|
limbs[i] = (int64_t)word << shifts[i];
|
|
}
|
|
fe_compact_carry(h, limbs, fe_decode_carries, sizeof(fe_decode_carries));
|
|
}
|
|
|
|
void fe_mul121666(fe h, fe f) {
|
|
int64_t limbs[10];
|
|
int i;
|
|
for (i = 0; i < 10; ++i) limbs[i] = (int64_t)f[i] * 121666;
|
|
fe_compact_carry(h, limbs, fe_decode_carries, sizeof(fe_decode_carries));
|
|
}
|
|
|
|
void fe_tobytes(unsigned char *s, const fe h) {
|
|
int32_t limbs[10], q = (19 * h[9] + (1 << 24)) >> 25;
|
|
int i, index = 0, offset = 0;
|
|
for (i = 0; i < 10; ++i) {
|
|
limbs[i] = h[i];
|
|
q = (limbs[i] + q) >> (26 - (i & 1));
|
|
}
|
|
limbs[0] += 19 * q;
|
|
for (i = 0; i < 10; ++i) {
|
|
const int bits = 26 - (i & 1);
|
|
const int32_t carry = limbs[i] >> bits;
|
|
if (i < 9) limbs[i + 1] += carry;
|
|
limbs[i] -= carry * ((int32_t)1 << bits);
|
|
}
|
|
/* Each byte crosses at most one public radix boundary. Normalized limbs
|
|
* are nonnegative, and unsigned shifts avoid overflowing signed int32. */
|
|
for (i = 0; i < 32; ++i) {
|
|
uint32_t byte = (uint32_t)limbs[index] >> offset;
|
|
offset += 8;
|
|
if (offset >= 26 - (index & 1)) {
|
|
offset -= 26 - (index & 1);
|
|
++index;
|
|
if (offset && index < 10)
|
|
byte |= (uint32_t)limbs[index] << (8 - offset);
|
|
}
|
|
s[i] = (unsigned char)byte;
|
|
}
|
|
}
|
|
|
|
int fe_isnonzero(const fe f) {
|
|
unsigned char bytes[32], value = 0;
|
|
int i;
|
|
fe_tobytes(bytes, f);
|
|
for (i = 0; i < 32; ++i) value |= bytes[i];
|
|
return value != 0;
|
|
}
|
|
|
|
static void fe_compact_product(fe h, const fe f, const fe g, int doubled) {
|
|
static const unsigned char carry_order[] = {0, 4, 1, 5, 2, 6, 3, 7, 4, 8, 9, 0};
|
|
int64_t limbs[10] = {0};
|
|
int i, j;
|
|
for (i = 0; i < 10; ++i) {
|
|
for (j = 0; j < 10; ++j) {
|
|
const int sum = i + j;
|
|
const int index = sum < 10 ? sum : sum - 10;
|
|
const int weight = (sum < 10 ? 1 : 19) * (1 + (i & j & 1));
|
|
limbs[index] += (int64_t)f[i] * g[j] * weight;
|
|
}
|
|
}
|
|
if (doubled) {
|
|
for (i = 0; i < 10; ++i) limbs[i] += limbs[i];
|
|
}
|
|
fe_compact_carry(h, limbs, carry_order, sizeof(carry_order));
|
|
}
|
|
|
|
void fe_mul(fe h, const fe f, const fe g) {
|
|
fe_compact_product(h, f, g, 0);
|
|
}
|
|
|
|
void fe_sq(fe h, const fe f) {
|
|
fe_compact_product(h, f, f, 0);
|
|
}
|
|
|
|
void fe_sq2(fe h, const fe f) {
|
|
fe_compact_product(h, f, f, 1);
|
|
}
|