diff --git a/firmware/teensy41/signer/src/pqclean/crypto_sign/ml-dsa-65/mldsa65_poly.c b/firmware/teensy41/signer/src/pqclean/crypto_sign/ml-dsa-65/mldsa65_poly.c index 0143343..f3bd53e 100644 --- a/firmware/teensy41/signer/src/pqclean/crypto_sign/ml-dsa-65/mldsa65_poly.c +++ b/firmware/teensy41/signer/src/pqclean/crypto_sign/ml-dsa-65/mldsa65_poly.c @@ -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); diff --git a/host_test_ntt b/host_test_ntt new file mode 100755 index 0000000..075b57f Binary files /dev/null and b/host_test_ntt differ diff --git a/src/main.c b/src/main.c index a94c36b..15fd974 100644 --- a/src/main.c +++ b/src/main.c @@ -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 */