Files
zeekay cc84032371 hqc: wire real PQClean cgo backend, NIST KATs pass byte-for-byte
Replaces the stub backend (ErrBackendNotWired) with a working byte-
for-byte NIST-compatible HQC implementation. Build tag hqc_pqclean
activates the cgo path; default builds remain stub for portability.

Vendored:
  - PQClean HQC-128/192/256 clean reference C (2023-04-30 NIST
    submission), public domain
  - PQClean shared fips202 (SHA-3 / SHAKE)
  - Per-mode/per-source shim TUs (36 total) to keep PQClean's
    static helpers isolated and avoid duplicate-symbol collisions
  - randombytes_shim.c wiring PQClean's randombytes() to a Go-side
    io.Reader callback, mutex-protected for concurrent dispatch

Tests:
  - TestKAT_HQC128/192/256 — all three NIST KAT vectors verified
    byte-for-byte against META.yml nistkat-sha256
  - TestRoundTrip_AllModes — keygen → encap → decap roundtrip,
    shared secrets byte-equal in all 3 modes
  - TestDeterminism_AllModes — same seed produces identical ct/ss
    (load-bearing for the precompile's on-chain determinism)
  - -race clean

Correction: ciphertext sizes updated from 4481/9026/14469 (older
HQC round-3 listing) to PQClean's 4433/8978/14421 (2023-04-30 NIST
submission); the prior numbers would have failed round-trip with
a 48-byte mismatch.
2026-05-15 17:43:48 -07:00

199 lines
4.9 KiB
C

#include "gf2x.h"
#include "parameters.h"
#include <stddef.h>
#include <stdint.h>
/**
* @file gf2x.c
* @brief Implementation of multiplication of two polynomials
*/
/**
* @brief Caryless multiplication of two words of 64 bits
*
* Implemntation of the algorithm mul1 in https://hal.inria.fr/inria-00188261v4/document.
* With w = 64 and s = 4
*
* @param[out] c The result c = a * b
* @param[in] a The first value a
* @param[in] b The second value b
*/
static void base_mul(uint64_t *c, uint64_t a, uint64_t b) {
uint64_t h = 0;
uint64_t l = 0;
uint64_t g;
uint64_t u[16] = {0};
uint64_t mask_tab[4] = {0};
uint64_t tmp1, tmp2;
// Step 1
u[0] = 0;
u[1] = b & (((uint64_t)1 << (64 - 4)) - 1);
u[2] = u[1] << 1;
u[3] = u[2] ^ u[1];
u[4] = u[2] << 1;
u[5] = u[4] ^ u[1];
u[6] = u[3] << 1;
u[7] = u[6] ^ u[1];
u[8] = u[4] << 1;
u[9] = u[8] ^ u[1];
u[10] = u[5] << 1;
u[11] = u[10] ^ u[1];
u[12] = u[6] << 1;
u[13] = u[12] ^ u[1];
u[14] = u[7] << 1;
u[15] = u[14] ^ u[1];
g = 0;
tmp1 = a & 0x0f;
for (size_t i = 0; i < 16; ++i) {
tmp2 = tmp1 - i;
g ^= (u[i] & (uint64_t)(0 - (1 - ((uint64_t)(tmp2 | (0 - tmp2)) >> 63))));
}
l = g;
h = 0;
// Step 2
for (size_t i = 4; i < 64; i += 4) {
g = 0;
tmp1 = (a >> i) & 0x0f;
for (size_t j = 0; j < 16; ++j) {
tmp2 = tmp1 - j;
g ^= (u[j] & (uint64_t)(0 - (1 - ((uint64_t)(tmp2 | (0 - tmp2)) >> 63))));
}
l ^= g << i;
h ^= g >> (64 - i);
}
// Step 3
mask_tab [0] = 0 - ((b >> 60) & 1);
mask_tab [1] = 0 - ((b >> 61) & 1);
mask_tab [2] = 0 - ((b >> 62) & 1);
mask_tab [3] = 0 - ((b >> 63) & 1);
l ^= ((a << 60) & mask_tab[0]);
h ^= ((a >> 4) & mask_tab[0]);
l ^= ((a << 61) & mask_tab[1]);
h ^= ((a >> 3) & mask_tab[1]);
l ^= ((a << 62) & mask_tab[2]);
h ^= ((a >> 2) & mask_tab[2]);
l ^= ((a << 63) & mask_tab[3]);
h ^= ((a >> 1) & mask_tab[3]);
c[0] = l;
c[1] = h;
}
static void karatsuba_add1(uint64_t *alh, uint64_t *blh, const uint64_t *a, const uint64_t *b, size_t size_l, size_t size_h) {
for (size_t i = 0; i < size_h; ++i) {
alh[i] = a[i] ^ a[i + size_l];
blh[i] = b[i] ^ b[i + size_l];
}
if (size_h < size_l) {
alh[size_h] = a[size_h];
blh[size_h] = b[size_h];
}
}
static void karatsuba_add2(uint64_t *o, uint64_t *tmp1, const uint64_t *tmp2, size_t size_l, size_t size_h) {
for (size_t i = 0; i < (2 * size_l); ++i) {
tmp1[i] = tmp1[i] ^ o[i];
}
for (size_t i = 0; i < ( 2 * size_h); ++i) {
tmp1[i] = tmp1[i] ^ tmp2[i];
}
for (size_t i = 0; i < (2 * size_l); ++i) {
o[i + size_l] = o[i + size_l] ^ tmp1[i];
}
}
/**
* Karatsuba multiplication of a and b, Implementation inspired from the NTL library.
*
* @param[out] o Polynomial
* @param[in] a Polynomial
* @param[in] b Polynomial
* @param[in] size Length of polynomial
* @param[in] stack Length of polynomial
*/
static void karatsuba(uint64_t *o, const uint64_t *a, const uint64_t *b, size_t size, uint64_t *stack) {
size_t size_l, size_h;
const uint64_t *ah, *bh;
if (size == 1) {
base_mul(o, a[0], b[0]);
return;
}
size_h = size / 2;
size_l = (size + 1) / 2;
uint64_t *alh = stack;
uint64_t *blh = alh + size_l;
uint64_t *tmp1 = blh + size_l;
uint64_t *tmp2 = o + 2 * size_l;
stack += 4 * size_l;
ah = a + size_l;
bh = b + size_l;
karatsuba(o, a, b, size_l, stack);
karatsuba(tmp2, ah, bh, size_h, stack);
karatsuba_add1(alh, blh, a, b, size_l, size_h);
karatsuba(tmp1, alh, blh, size_l, stack);
karatsuba_add2(o, tmp1, tmp2, size_l, size_h);
}
/**
* @brief Compute o(x) = a(x) mod \f$ X^n - 1\f$
*
* This function computes the modular reduction of the polynomial a(x)
*
* @param[in] a Pointer to the polynomial a(x)
* @param[out] o Pointer to the result
*/
static void reduce(uint64_t *o, const uint64_t *a) {
uint64_t r;
uint64_t carry;
for (size_t i = 0; i < VEC_N_SIZE_64; ++i) {
r = a[i + VEC_N_SIZE_64 - 1] >> (PARAM_N & 0x3F);
carry = a[i + VEC_N_SIZE_64] << (64 - (PARAM_N & 0x3F));
o[i] = a[i] ^ r ^ carry;
}
o[VEC_N_SIZE_64 - 1] &= RED_MASK;
}
/**
* @brief Multiply two polynomials modulo \f$ X^n - 1\f$.
*
* This functions multiplies polynomials <b>v1</b> and <b>v2</b>.
* The multiplication is done modulo \f$ X^n - 1\f$.
*
* @param[out] o Product of <b>v1</b> and <b>v2</b>
* @param[in] v1 Pointer to the first polynomial
* @param[in] v2 Pointer to the second polynomial
*/
void PQCLEAN_HQC192_CLEAN_vect_mul(uint64_t *o, const uint64_t *v1, const uint64_t *v2) {
uint64_t stack[VEC_N_SIZE_64 << 3] = {0};
uint64_t o_karat[VEC_N_SIZE_64 << 1] = {0};
karatsuba(o_karat, v1, v2, VEC_N_SIZE_64, stack);
reduce(o, o_karat);
}