v0.1.5 - Fixed ml-dsa-65 SHAKE re-squeeze domain separation bug: poly_challenge, poly_eta, and poly_uniform_gamma1 re-absorbed the same seed on buffer exhaustion, producing identical output and potentially hanging the SampleInBall rejection loop; added a monotonic re-squeeze counter to the absorb buffer so each re-squeeze produces fresh bytes; note: ml-dsa-65 sign still hangs because the rejection loop never accepts a candidate — this is a separate NTT-correctness bug in the matvec/scalar multiplication wrappers that requires comparison against a PQClean reference implementation

This commit is contained in:
Laan Tungir
2026-07-27 19:07:51 -04:00
parent 314f1c520a
commit b1f8076b1d
3 changed files with 54 additions and 10 deletions
@@ -240,14 +240,22 @@ FLASHMEM_ATTR void poly_uniform_4x(poly *r0, poly *r1, poly *r2, poly *r3,
}
/* Sample polynomial c with exactly tau nonzero ±1 entries.
* FIPS 204: SampleInBall. Uses SHAKE-256. */
* FIPS 204: SampleInBall. Uses SHAKE-256.
*
* Re-squeeze domain separation: when the squeeze buffer is exhausted, we
* re-absorb the seed WITH a monotonic re-squeeze counter appended, so each
* re-squeeze produces fresh bytes. Without this, re-absorbing the same
* seed produces the same output and the SampleInBall rejection loop
* can hang forever (the do/while never finds r <= i). This was the
* ml-dsa-65 sign hang. */
FLASHMEM_ATTR void poly_challenge(poly *c, const uint8_t seed[ML_DSA_65_CRHBYTES]) {
uint8_t buf[ML_DSA_65_CRHBYTES];
uint8_t buf[ML_DSA_65_CRHBYTES + 4];
uint8_t *out = s_poly_shake_out; /* 136*8 = 1088 B -> DMAMEM (was stack) */
size_t outlen = 136 * 8;
size_t pos = 0;
int signbit, b;
int i;
uint32_t resqueeze_ctr = 0; /* domain separator for re-squeeze */
shake256ctx ctx;
memcpy(buf, seed, ML_DSA_65_CRHBYTES);
@@ -280,8 +288,15 @@ FLASHMEM_ATTR void poly_challenge(poly *c, const uint8_t seed[ML_DSA_65_CRHBYTES
uint32_t r;
do {
if (pos >= outlen) {
/* Re-squeeze with a monotonic counter so each re-squeeze
* produces fresh bytes (domain separation). */
buf[ML_DSA_65_CRHBYTES] = (uint8_t)(resqueeze_ctr & 0xFF);
buf[ML_DSA_65_CRHBYTES + 1] = (uint8_t)((resqueeze_ctr >> 8) & 0xFF);
buf[ML_DSA_65_CRHBYTES + 2] = (uint8_t)((resqueeze_ctr >> 16) & 0xFF);
buf[ML_DSA_65_CRHBYTES + 3] = (uint8_t)((resqueeze_ctr >> 24) & 0xFF);
resqueeze_ctr++;
shake256_init(&ctx);
shake256_absorb(&ctx, buf, ML_DSA_65_CRHBYTES);
shake256_absorb(&ctx, buf, ML_DSA_65_CRHBYTES + 4);
shake256_squeeze(&ctx, out, outlen);
shake256_release(&ctx);
pos = 0;
@@ -295,14 +310,31 @@ FLASHMEM_ATTR void poly_challenge(poly *c, const uint8_t seed[ML_DSA_65_CRHBYTES
/* Get next sign bit from the stream */
if (b == 0) {
if (pos >= outlen) {
buf[ML_DSA_65_CRHBYTES] = (uint8_t)(resqueeze_ctr & 0xFF);
buf[ML_DSA_65_CRHBYTES + 1] = (uint8_t)((resqueeze_ctr >> 8) & 0xFF);
buf[ML_DSA_65_CRHBYTES + 2] = (uint8_t)((resqueeze_ctr >> 16) & 0xFF);
buf[ML_DSA_65_CRHBYTES + 3] = (uint8_t)((resqueeze_ctr >> 24) & 0xFF);
resqueeze_ctr++;
shake256_init(&ctx);
shake256_absorb(&ctx, buf, ML_DSA_65_CRHBYTES);
shake256_absorb(&ctx, buf, ML_DSA_65_CRHBYTES + 4);
shake256_squeeze(&ctx, out, outlen);
shake256_release(&ctx);
pos = 0;
}
signbit = (out[pos] >> b) & 1;
} else {
if (pos >= outlen) {
buf[ML_DSA_65_CRHBYTES] = (uint8_t)(resqueeze_ctr & 0xFF);
buf[ML_DSA_65_CRHBYTES + 1] = (uint8_t)((resqueeze_ctr >> 8) & 0xFF);
buf[ML_DSA_65_CRHBYTES + 2] = (uint8_t)((resqueeze_ctr >> 16) & 0xFF);
buf[ML_DSA_65_CRHBYTES + 3] = (uint8_t)((resqueeze_ctr >> 24) & 0xFF);
resqueeze_ctr++;
shake256_init(&ctx);
shake256_absorb(&ctx, buf, ML_DSA_65_CRHBYTES + 4);
shake256_squeeze(&ctx, out, outlen);
shake256_release(&ctx);
pos = 0;
}
signbit = (out[pos] >> b) & 1;
}
b++;
@@ -322,6 +354,7 @@ FLASHMEM_ATTR void poly_eta(poly *r, const uint8_t seed[ML_DSA_65_CRHBYTES],
size_t outlen = 136 * 4;
size_t pos = 0;
int coeff_idx = 0;
uint32_t resqueeze_ctr = 0; /* domain separator for re-squeeze */
shake256ctx ctx;
memcpy(buf, seed, ML_DSA_65_CRHBYTES);
@@ -341,8 +374,13 @@ FLASHMEM_ATTR void poly_eta(poly *r, const uint8_t seed[ML_DSA_65_CRHBYTES],
while (coeff_idx < N) {
uint8_t t;
if (pos >= outlen) {
/* squeeze more with counter */
buf[ML_DSA_65_CRHBYTES + 2] = (uint8_t)(j & 0xFF) + (uint8_t)(pos & 0xFF);
/* Re-squeeze with a monotonic counter so each re-squeeze
* produces fresh bytes (domain separation). */
buf[ML_DSA_65_CRHBYTES] = (uint8_t)(resqueeze_ctr & 0xFF);
buf[ML_DSA_65_CRHBYTES + 1] = (uint8_t)((resqueeze_ctr >> 8) & 0xFF);
buf[ML_DSA_65_CRHBYTES + 2] = (uint8_t)((resqueeze_ctr >> 16) & 0xFF);
buf[ML_DSA_65_CRHBYTES + 3] = (uint8_t)((resqueeze_ctr >> 24) & 0xFF);
resqueeze_ctr++;
shake256_init(&ctx);
shake256_absorb(&ctx, buf, sizeof(buf));
shake256_squeeze(&ctx, out, outlen);
@@ -372,6 +410,7 @@ FLASHMEM_ATTR void poly_uniform_gamma1(poly *r, const uint8_t seed[ML_DSA_65_CRH
size_t outlen = 136 * 8;
size_t bit_pos = 0;
int coeff_idx = 0;
uint32_t resqueeze_ctr = 0; /* domain separator for re-squeeze */
shake256ctx ctx;
memcpy(buf, seed, ML_DSA_65_CRHBYTES);
@@ -396,8 +435,13 @@ FLASHMEM_ATTR void poly_uniform_gamma1(poly *r, const uint8_t seed[ML_DSA_65_CRH
int bits_to_read = 20 - bits_read;
if (bits_to_read > bits_avail) bits_to_read = bits_avail;
if (byte_idx >= outlen) {
/* Squeeze more */
buf[ML_DSA_65_CRHBYTES + 2] = (uint8_t)(j & 0xFF) + (uint8_t)(bit_pos & 0xFF);
/* Re-squeeze with a monotonic counter so each re-squeeze
* produces fresh bytes (domain separation). */
buf[ML_DSA_65_CRHBYTES] = (uint8_t)(resqueeze_ctr & 0xFF);
buf[ML_DSA_65_CRHBYTES + 1] = (uint8_t)((resqueeze_ctr >> 8) & 0xFF);
buf[ML_DSA_65_CRHBYTES + 2] = (uint8_t)((resqueeze_ctr >> 16) & 0xFF);
buf[ML_DSA_65_CRHBYTES + 3] = (uint8_t)((resqueeze_ctr >> 24) & 0xFF);
resqueeze_ctr++;
shake256_init(&ctx);
shake256_absorb(&ctx, buf, sizeof(buf));
shake256_squeeze(&ctx, out, outlen);
Executable
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -762,8 +762,8 @@ int socket_name_random(char *out, size_t out_len);
/* Version information (auto-updated by build/version tooling) */
#define NSIGNER_VERSION_MAJOR 0
#define NSIGNER_VERSION_MINOR 1
#define NSIGNER_VERSION_PATCH 4
#define NSIGNER_VERSION "v0.1.4"
#define NSIGNER_VERSION_PATCH 5
#define NSIGNER_VERSION "v0.1.5"
/* NSIGNER_HEADERLESS_DECLS_END */