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

171 lines
4.3 KiB
C

#include "gf.h"
#include "parameters.h"
#include <stddef.h>
#include <stdint.h>
/**
* @file gf.c
* @brief Galois field implementation
*/
/**
* @brief Computes the number of trailing zero bits.
*
* @returns The number of trailing zero bits in a.
* @param[in] a An operand
*/
static uint16_t trailing_zero_bits_count(uint16_t a) {
uint16_t tmp = 0;
uint16_t mask = 0xFFFF;
for (size_t i = 0; i < 14; ++i) {
tmp += ((1 - ((a >> i) & 0x0001)) & mask);
mask &= - (1 - ((a >> i) & 0x0001));
}
return tmp;
}
/**
* Reduces polynomial x modulo primitive polynomial GF_POLY.
* @returns x mod GF_POLY
* @param[in] x Polynomial of degree less than 64
* @param[in] deg_x The degree of polynomial x
*/
static uint16_t gf_reduce(uint64_t x, size_t deg_x) {
uint16_t z1, z2, rmdr, dist;
uint64_t mod;
// Deduce the number of steps of reduction
size_t steps = CEIL_DIVIDE(deg_x - (PARAM_M - 1), PARAM_GF_POLY_M2);
// Reduce
for (size_t i = 0; i < steps; ++i) {
mod = x >> PARAM_M;
x &= (1 << PARAM_M) - 1;
x ^= mod;
z1 = 0;
rmdr = PARAM_GF_POLY ^ 1;
for (size_t j = PARAM_GF_POLY_WT - 2; j; --j) {
z2 = trailing_zero_bits_count(rmdr);
dist = z2 - z1;
mod <<= dist;
x ^= mod;
rmdr ^= 1 << z2;
z1 = z2;
}
}
return (uint16_t)x;
}
/**
* Carryless multiplication of two polynomials a and b.
*
* Implementation of the algorithm mul1 in https://hal.inria.fr/inria-00188261v4/document
* with s = 2 and w = 8
*
* @param[out] The polynomial c = a * b
* @param[in] a The first polynomial
* @param[in] b The second polynomial
*/
static void gf_carryless_mul(uint8_t c[2], uint8_t a, uint8_t b) {
uint16_t h = 0, l = 0, g = 0, u[4];
uint32_t tmp1, tmp2;
uint16_t mask;
u[0] = 0;
u[1] = b & 0x7F;
u[2] = u[1] << 1;
u[3] = u[2] ^ u[1];
tmp1 = a & 3;
for (size_t i = 0; i < 4; i++) {
tmp2 = (uint32_t)(tmp1 - i);
g ^= (u[i] & (uint32_t)(0 - (1 - ((uint32_t)(tmp2 | (0 - tmp2)) >> 31))));
}
l = g;
h = 0;
for (size_t i = 2; i < 8; i += 2) {
g = 0;
tmp1 = (a >> i) & 3;
for (size_t j = 0; j < 4; ++j) {
tmp2 = (uint32_t)(tmp1 - j);
g ^= (u[j] & (uint32_t)(0 - (1 - ((uint32_t)(tmp2 | (0 - tmp2)) >> 31))));
}
l ^= g << i;
h ^= g >> (8 - i);
}
mask = (-((b >> 7) & 1));
l ^= ((a << 7) & mask);
h ^= ((a >> 1) & mask);
c[0] = (uint8_t)l;
c[1] = (uint8_t)h;
}
/**
* Multiplies two elements of GF(2^GF_M).
* @returns the product a*b
* @param[in] a Element of GF(2^GF_M)
* @param[in] b Element of GF(2^GF_M)
*/
uint16_t PQCLEAN_HQC192_CLEAN_gf_mul(uint16_t a, uint16_t b) {
uint8_t c[2] = {0};
gf_carryless_mul(c, (uint8_t) a, (uint8_t) b);
uint16_t tmp = c[0] ^ (c[1] << 8);
return gf_reduce(tmp, 2 * (PARAM_M - 1));
}
/**
* @brief Squares an element of GF(2^PARAM_M).
* @returns a^2
* @param[in] a Element of GF(2^PARAM_M)
*/
uint16_t PQCLEAN_HQC192_CLEAN_gf_square(uint16_t a) {
uint32_t b = a;
uint32_t s = b & 1;
for (size_t i = 1; i < PARAM_M; ++i) {
b <<= 1;
s ^= b & (1 << 2 * i);
}
return gf_reduce(s, 2 * (PARAM_M - 1));
}
/**
* @brief Computes the inverse of an element of GF(2^PARAM_M),
* using the addition chain 1 2 3 4 7 11 15 30 60 120 127 254
* @returns the inverse of a if a != 0 or 0 if a = 0
* @param[in] a Element of GF(2^PARAM_M)
*/
uint16_t PQCLEAN_HQC192_CLEAN_gf_inverse(uint16_t a) {
uint16_t inv = a;
uint16_t tmp1, tmp2;
inv = PQCLEAN_HQC192_CLEAN_gf_square(a); /* a^2 */
tmp1 = PQCLEAN_HQC192_CLEAN_gf_mul(inv, a); /* a^3 */
inv = PQCLEAN_HQC192_CLEAN_gf_square(inv); /* a^4 */
tmp2 = PQCLEAN_HQC192_CLEAN_gf_mul(inv, tmp1); /* a^7 */
tmp1 = PQCLEAN_HQC192_CLEAN_gf_mul(inv, tmp2); /* a^11 */
inv = PQCLEAN_HQC192_CLEAN_gf_mul(tmp1, inv); /* a^15 */
inv = PQCLEAN_HQC192_CLEAN_gf_square(inv); /* a^30 */
inv = PQCLEAN_HQC192_CLEAN_gf_square(inv); /* a^60 */
inv = PQCLEAN_HQC192_CLEAN_gf_square(inv); /* a^120 */
inv = PQCLEAN_HQC192_CLEAN_gf_mul(inv, tmp2); /* a^127 */
inv = PQCLEAN_HQC192_CLEAN_gf_square(inv); /* a^254 */
return inv;
}