Embedded SDK
Embedded SDK
Loading...
Searching...
No Matches
cx_mldsa.c
Go to the documentation of this file.
1/*****************************************************************************
2 * (c) 2026 Ledger SAS.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *****************************************************************************/
24/*********************
25 * INCLUDES
26 *********************/
27
28#include <string.h>
29#include "lcx_mldsa.h"
30#include "cx_mldsa_poly.h"
31#include "cx_mldsa_polyvec.h"
32#include "cx_mldsa_polymat.h"
33#include "cx_mldsa_sample.h"
34#include "cx_mldsa_packing.h"
35#include "cx_mldsa_rounding.h"
36#include "cx_mldsa_util.h"
37#include "cx_mldsa_internal.h"
38#include "lcx_sha3.h"
39#include "lcx_hash.h"
40#include "lcx_rng.h"
41#ifdef HAVE_MLDSA_OPTIMIZATION
42#include "cx_mldsa_smallpoly.h"
43#include "cx_mldsa_lowram.h"
44#endif
45
46/*********************
47 * DEFINES
48 *********************/
49
54#define MLDSA_MAX_SIGN_ATTEMPTS 814U
55
56/**********************
57 * TYPEDEFS
58 **********************/
59
71
83
105
116typedef union {
118 struct {
119 uint8_t tr[MLDSA_TRBYTES];
120 } setup_phase;
122 struct {
125 } az_phase;
127
146
147/*********************
148 * GLOBAL VARIABLES
149 *********************/
150
151/*********************
152 * STATIC VARIABLES
153 *********************/
154
156 // MLDSA_PREHASH_SHA256
157 {{0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01}, 32U},
158 // MLDSA_PREHASH_SHA512
159 {{0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03}, 64U},
160 // MLDSA_PREHASH_SHA3_256
161 {{0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08}, 32U},
162 // MLDSA_PREHASH_SHA3_512
163 {{0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0A}, 64U},
164 // MLDSA_PREHASH_SHAKE128
165 {{0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0B}, 32U},
166 // MLDSA_PREHASH_SHAKE256
167 {{0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0C}, 64U},
168};
169
170/*********************
171 * STATIC FUNCTIONS
172 *********************/
173
188 const uint8_t *ctx,
189 size_t ctx_len,
190 const uint8_t *msg,
191 size_t msg_len)
192{
193 if (mprime == NULL) {
194 return CX_INVALID_PARAMETER;
195 }
196 if ((msg == NULL) && (msg_len > 0U)) {
197 return CX_INVALID_PARAMETER;
198 }
199 if ((ctx == NULL) && (ctx_len > 0U)) {
200 return CX_INVALID_PARAMETER;
201 }
202 if (ctx_len > 255U) {
203 return CX_INVALID_PARAMETER_SIZE;
204 }
205
206 mprime->prefix[0] = 0x00U;
207 mprime->prefix[1] = (uint8_t) ctx_len;
208 if (ctx_len > 0U) {
209 memcpy(&mprime->prefix[2], ctx, ctx_len);
210 }
211 mprime->prefix_len = 2U + ctx_len;
212 mprime->payload = msg;
213 mprime->payload_len = msg_len;
214
215 return CX_OK;
216}
217
233 const uint8_t *ctx,
234 size_t ctx_len,
235 MLDSA_prehash_t prehash_alg,
236 const uint8_t *ph,
237 size_t ph_len)
238{
239 if (mprime == NULL) {
240 return CX_INVALID_PARAMETER;
241 }
242 if ((ph == NULL) && (ph_len > 0U)) {
243 return CX_INVALID_PARAMETER;
244 }
245 if ((ctx == NULL) && (ctx_len > 0U)) {
246 return CX_INVALID_PARAMETER;
247 }
248 if (ctx_len > 255U) {
249 return CX_INVALID_PARAMETER_SIZE;
250 }
251
252 if ((unsigned) prehash_alg >= MLDSA_NUM_PREHASH_ALGS) {
253 return CX_INVALID_PARAMETER_VALUE;
254 }
255 if (ph_len != MLDSA_PREHASH_INFO[prehash_alg].hash_len) {
256 return CX_INVALID_PARAMETER_SIZE;
257 }
258
259 mprime->prefix[0] = 0x01U;
260 mprime->prefix[1] = (uint8_t) ctx_len;
261 if (ctx_len > 0U) {
262 memcpy(&mprime->prefix[2], ctx, ctx_len);
263 }
264 memcpy(
265 &mprime->prefix[2U + ctx_len], MLDSA_PREHASH_INFO[prehash_alg].oid, MLDSA_PREHASH_OID_LEN);
266 mprime->prefix_len = 2U + ctx_len + MLDSA_PREHASH_OID_LEN;
267 mprime->payload = ph;
268 mprime->payload_len = ph_len;
269
270 return CX_OK;
271}
272
280static void mldsa_compute_mu(uint8_t mu[MLDSA_CRHBYTES],
281 const uint8_t tr[MLDSA_TRBYTES],
282 const MLDSA_formatted_message_t *mprime)
283{
284 cx_sha3_t sha3_ctx = {0};
285
286 memset(&sha3_ctx, 0, sizeof(sha3_ctx));
287 cx_shake256_init_no_throw(&sha3_ctx, MLDSA_CRHBYTES * 8U);
288 cx_hash_no_throw((cx_hash_t *) &sha3_ctx, 0, tr, MLDSA_TRBYTES, NULL, 0);
289 cx_hash_no_throw((cx_hash_t *) &sha3_ctx, 0, mprime->prefix, mprime->prefix_len, NULL, 0);
290 cx_hash_no_throw(
291 (cx_hash_t *) &sha3_ctx, CX_LAST, mprime->payload, mprime->payload_len, mu, MLDSA_CRHBYTES);
292}
293
294#ifndef HAVE_MLDSA_OPTIMIZATION
295
317cx_err_t MLDSA_internal_sign_core(uint8_t *sig,
318 size_t sig_len,
319 size_t *sig_actual_len,
320 const MLDSA_formatted_message_t *formatted_mprime,
321 const uint8_t *precomputed_mu,
322 uint8_t *rnd,
323 size_t rnd_len,
324 const uint8_t *sk,
325 size_t sk_len,
326 MLDSA_param_t param)
327{
328 MLDSA_sign_stack_workspace_t ws_local = {0};
329 MLDSA_sign_stack_workspace_t *ws = &ws_local;
330 const MLDSA_param_info_t *p = NULL;
331 const uint8_t *rnd_input = rnd;
332 uint8_t zero_rnd[MLDSA_RNDBYTES] = {0};
333 cx_err_t error = CX_INTERNAL_ERROR;
334 uint16_t kappa = 0U;
335 uint32_t attempts = 0U;
336
337 if ((sig == NULL) || (sk == NULL) || (sig_actual_len == NULL)) {
338 error = CX_INVALID_PARAMETER;
339 goto cleanup;
340 }
341 if ((formatted_mprime == NULL) && (precomputed_mu == NULL)) {
342 error = CX_INVALID_PARAMETER;
343 goto cleanup;
344 }
345
346 if ((rnd != NULL) && (rnd_len != MLDSA_RNDBYTES)) {
347 error = CX_INVALID_PARAMETER;
348 goto cleanup;
349 }
350 if ((rnd == NULL) && (rnd_len != 0U)) {
351 error = CX_INVALID_PARAMETER;
352 goto cleanup;
353 }
354
355 if (param >= MLDSA_NUM_PARAM_SETS) {
356 error = CX_INVALID_PARAMETER_VALUE;
357 goto cleanup;
358 }
359
360 p = &MLDSA_PARAM[param];
361
362 if (sk_len < p->sk_bytes) {
363 error = CX_INVALID_PARAMETER_SIZE;
364 goto cleanup;
365 }
366 if (sig_len < p->sig_bytes) {
367 error = CX_INVALID_PARAMETER_SIZE;
368 goto cleanup;
369 }
370
371 if (rnd_input == NULL) {
372 rnd_input = zero_rnd;
373 }
374
375 explicit_bzero(ws, sizeof(*ws));
376
377 // Unpack secret-key header only; large vectors are decoded on the fly.
378 memcpy(ws->rho, sk, MLDSA_SEEDBYTES);
379 memcpy(ws->K, &sk[MLDSA_SEEDBYTES], MLDSA_SEEDBYTES);
380 memcpy(ws->tr, &sk[2U * MLDSA_SEEDBYTES], MLDSA_TRBYTES);
381
382 const uint8_t *sk_s1 = &sk[2U * MLDSA_SEEDBYTES + MLDSA_TRBYTES];
383 const uint8_t *sk_s2 = &sk_s1[(size_t) p->l * p->polyeta_packed_bytes];
384 const uint8_t *sk_t0 = &sk_s2[(size_t) p->k * p->polyeta_packed_bytes];
385
386 // Compute or import mu
387 if (precomputed_mu != NULL) {
388 memcpy(ws->mu, precomputed_mu, MLDSA_CRHBYTES);
389 }
390 else {
391 mldsa_compute_mu(ws->mu, ws->tr, formatted_mprime);
392 }
393
394 // Compute rhoprime = H(K || rnd || mu, 64)
395 {
396 cx_sha3_t sha3_ctx = {0};
397 cx_shake256_init_no_throw(&sha3_ctx, MLDSA_CRHBYTES * 8U);
398 cx_hash_no_throw((cx_hash_t *) &sha3_ctx, 0, ws->K, MLDSA_SEEDBYTES, NULL, 0);
399 cx_hash_no_throw((cx_hash_t *) &sha3_ctx, 0, rnd_input, MLDSA_RNDBYTES, NULL, 0);
400 cx_hash_no_throw(
401 (cx_hash_t *) &sha3_ctx, CX_LAST, ws->mu, MLDSA_CRHBYTES, ws->rhoprime, MLDSA_CRHBYTES);
402 }
403
404 // Rejection sampling loop
405 while (attempts < MLDSA_MAX_SIGN_ATTEMPTS) {
406 uint16_t kappa_base = kappa;
407 attempts++;
408
409 // w = A * NTT(y) streamed one column at a time.
410 // For each l: sample y[l], NTT it (t0 = yhat[l]), then accumulate
411 // A[k][l] * yhat[l] into every row w[k]. Only one yhat poly is alive.
412 for (uint8_t l = 0U; l < p->l; l++) {
413 MLDSA_SAMPLE_gamma1(&ws->t0, ws->rhoprime, (uint16_t) (kappa_base + l), p->gamma1);
414 MLDSA_POLY_ntt(&ws->t0);
415 for (uint8_t k = 0U; k < p->k; k++) {
416 uint16_t nonce = ((uint16_t) k << 8U) | (uint16_t) l;
417 MLDSA_SAMPLE_uniform(&ws->t1, ws->rho, nonce);
418 MLDSA_POLY_pointwise_montgomery(&ws->w.vec[k], &ws->t1, &ws->t0, (l == 0U) ? 1 : 0);
419 }
420 }
421 kappa = (uint16_t) (kappa_base + p->l);
422
423 MLDSA_POLYVEC_reduce_k(&ws->w, p->k);
425 MLDSA_POLYVEC_caddq_k(&ws->w, p->k);
426
427 // Pack w1 = HighBits(w) one row at a time.
428 for (uint8_t k = 0U; k < p->k; k++) {
429 MLDSA_ROUNDING_poly_decompose(&ws->t0, &ws->t1, &ws->w.vec[k], p->gamma2);
431 &ws->w1_packed[(size_t) k * p->polyw1_packed_bytes], &ws->t0, p->gamma2);
432 }
433
434 // Challenge hash ctilde = H(mu || w1_packed)
436 p->ctilde_bytes,
437 ws->mu,
439 ws->w1_packed,
440 (size_t) p->k * p->polyw1_packed_bytes);
441
443 MLDSA_POLY_ntt(&ws->cp);
444
445 // Compute z = y + INTT(c*s1) one row at a time; pack each z[l] into sig.
446 // y[l] is re-sampled (deterministic) instead of being stored.
447 {
448 uint8_t z_reject = 0U;
449 for (uint8_t l = 0U; l < p->l; l++) {
450 MLDSA_SAMPLE_gamma1(&ws->t0, ws->rhoprime, (uint16_t) (kappa_base + l), p->gamma1);
452 &ws->t1, &sk_s1[(size_t) l * p->polyeta_packed_bytes], p->eta);
453 MLDSA_POLY_ntt(&ws->t1);
454 MLDSA_POLY_pointwise_montgomery(&ws->t2, &ws->cp, &ws->t1, 1);
456 MLDSA_POLY_add(&ws->t0, &ws->t2);
457 MLDSA_POLY_reduce(&ws->t0);
458
459 if (MLDSA_POLY_chknorm(&ws->t0, p->gamma1 - (int32_t) p->beta)) {
460 z_reject = 1U;
461 break;
462 }
463
465 sig + p->ctilde_bytes + (size_t) l * p->polyz_packed_bytes, &ws->t0, p->gamma1);
466 }
467 if (z_reject != 0U) {
468 continue;
469 }
470 }
471
472 // For each k: r0 = LowBits(w) - INTT(c*s2); ct0 = INTT(c*t0);
473 // hint = MakeHint(ct0 + r0, HighBits(w)). Hints are written straight
474 // into the signature as a sorted index list.
475 {
476 uint8_t *sig_h = sig + p->ctilde_bytes + (size_t) p->l * p->polyz_packed_bytes;
477 uint32_t n_hints = 0U;
478 unsigned int hints_written = 0U;
479 uint8_t reject = 0U;
480
481 memset(sig_h, 0, p->polyvech_packed_bytes);
482
483 for (uint8_t k = 0U; k < p->k; k++) {
484 // t0 = HighBits(w[k]) = w1[k], t1 = LowBits(w[k]) = w0[k]
485 MLDSA_ROUNDING_poly_decompose(&ws->t0, &ws->t1, &ws->w.vec[k], p->gamma2);
486
487 // r0 = w0 - INTT(c*s2[k]) (stored in t1)
489 &ws->t2, &sk_s2[(size_t) k * p->polyeta_packed_bytes], p->eta);
490 MLDSA_POLY_ntt(&ws->t2);
491 MLDSA_POLY_pointwise_montgomery(&ws->t2, &ws->cp, &ws->t2, 1);
493 MLDSA_POLY_sub(&ws->t1, &ws->t2);
494 MLDSA_POLY_reduce(&ws->t1);
495
496 // Check ||r0||_inf < gamma2 - beta
497 if (MLDSA_POLY_chknorm(&ws->t1, p->gamma2 - (int32_t) p->beta)) {
498 reject = 1U;
499 break;
500 }
501
502 // ct0 = INTT(c*t0[k]) (stored in t2)
503 MLDSA_PACK_unpack_polyt0(&ws->t2, &sk_t0[(size_t) k * MLDSA_POLYT0_PACKEDBYTES]);
504 MLDSA_POLY_ntt(&ws->t2);
505 MLDSA_POLY_pointwise_montgomery(&ws->t2, &ws->cp, &ws->t2, 1);
507 MLDSA_POLY_reduce(&ws->t2);
508
509 // Check ||ct0||_inf < gamma2
510 if (MLDSA_POLY_chknorm(&ws->t2, p->gamma2)) {
511 reject = 1U;
512 break;
513 }
514
515 // t2 = ct0 + r0
516 MLDSA_POLY_add(&ws->t2, &ws->t1);
517
518 // make_hint per coefficient against w1 (t0)
519 for (uint32_t j = 0U; j < MLDSA_N; j++) {
520 uint32_t hbit
521 = MLDSA_ROUNDING_make_hint(ws->t2.coeffs[j], ws->t0.coeffs[j], p->gamma2);
522 if (hbit != 0U) {
523 if (hints_written >= p->omega) {
524 reject = 1U;
525 break;
526 }
527 sig_h[hints_written] = (uint8_t) j;
528 hints_written++;
529 }
530 n_hints += hbit;
531 }
532 if (reject != 0U) {
533 break;
534 }
535 sig_h[p->omega + k] = (uint8_t) hints_written;
536 }
537
538 if ((reject != 0U) || (n_hints > p->omega)) {
539 continue;
540 }
541 }
542
543 // Success: c_tilde completes the signature (z and h already packed).
544 memcpy(sig, ws->ctilde, p->ctilde_bytes);
545 *sig_actual_len = p->sig_bytes;
546 error = CX_OK;
547 goto cleanup;
548 }
549
550 // Exhausted attempts
551 error = CX_INTERNAL_ERROR;
552
553cleanup:
554 explicit_bzero(zero_rnd, sizeof(zero_rnd));
555 explicit_bzero(ws, sizeof(*ws));
556
557 return error;
558}
559
577cx_err_t MLDSA_internal_verify_core(const uint8_t *sig,
578 size_t sig_len,
579 const MLDSA_formatted_message_t *formatted_mprime,
580 const uint8_t *precomputed_mu,
581 const uint8_t *pk,
582 size_t pk_len,
583 MLDSA_param_t param)
584{
585 MLDSA_verify_stack_workspace_t ws_local = {0};
586 MLDSA_verify_stack_workspace_t *ws = &ws_local;
587 const MLDSA_param_info_t *p = NULL;
588 const uint8_t *sig_z = NULL;
589 const uint8_t *sig_h = NULL;
590 uint32_t k_offset = 0U;
591 cx_err_t error = CX_INTERNAL_ERROR;
592
593 if ((sig == NULL) || (pk == NULL)) {
594 error = CX_INVALID_PARAMETER;
595 goto cleanup;
596 }
597 if ((formatted_mprime == NULL) && (precomputed_mu == NULL)) {
598 error = CX_INVALID_PARAMETER;
599 goto cleanup;
600 }
601
602 if (param >= MLDSA_NUM_PARAM_SETS) {
603 error = CX_INVALID_PARAMETER_VALUE;
604 goto cleanup;
605 }
606
607 p = &MLDSA_PARAM[param];
608
609 if (pk_len < p->pk_bytes) {
610 error = CX_INVALID_PARAMETER_SIZE;
611 goto cleanup;
612 }
613 if (sig_len < p->sig_bytes) {
614 error = CX_INVALID_PARAMETER_SIZE;
615 goto cleanup;
616 }
617
618 explicit_bzero(ws, sizeof(*ws));
619
620 memcpy(ws->rho, pk, MLDSA_SEEDBYTES);
621 memcpy(ws->ctilde, sig, p->ctilde_bytes);
622
623 sig_z = &sig[p->ctilde_bytes];
624 sig_h = &sig_z[(size_t) p->l * p->polyz_packed_bytes];
625
626 // Validate hint encoding without materializing the full h vector.
627 for (uint32_t i = 0U; i < p->k; i++) {
628 uint32_t limit = (uint32_t) sig_h[p->omega + i];
629 if ((limit < k_offset) || (limit > p->omega)) {
630 error = CX_INVALID_PARAMETER;
631 goto cleanup;
632 }
633
634 for (uint32_t j = k_offset; j < limit; j++) {
635 if ((j > k_offset) && (sig_h[j] <= sig_h[j - 1U])) {
636 error = CX_INVALID_PARAMETER;
637 goto cleanup;
638 }
639 }
640 k_offset = limit;
641 }
642
643 for (uint32_t j = k_offset; j < p->omega; j++) {
644 if (sig_h[j] != 0U) {
645 error = CX_INVALID_PARAMETER;
646 goto cleanup;
647 }
648 }
649
650 // Stream z and check ||z||_inf < gamma1 - beta.
651 for (uint8_t j = 0U; j < p->l; j++) {
652 MLDSA_PACK_unpack_polyz(&ws->ztmp, &sig_z[(size_t) j * p->polyz_packed_bytes], p->gamma1);
653 if (MLDSA_POLY_chknorm(&ws->ztmp, p->gamma1 - (int32_t) p->beta)) {
654 error = CX_INVALID_PARAMETER;
655 goto cleanup;
656 }
657 }
658
659 // Compute or import mu
660 if (precomputed_mu != NULL) {
661 memcpy(ws->mu, precomputed_mu, MLDSA_CRHBYTES);
662 }
663 else {
665 mldsa_compute_mu(ws->mu, ws->overlay.setup_phase.tr, formatted_mprime);
666 }
667
668 // Sample challenge c from ctilde
670 MLDSA_POLY_ntt(&ws->cp);
671
672 // Fused streaming verify: materialize one z, one t1 and one hint row at a time.
673 k_offset = 0U;
674 for (uint8_t i = 0U; i < p->k; i++) {
675 uint32_t limit = (uint32_t) sig_h[p->omega + i];
676
677 for (uint8_t j = 0U; j < p->l; j++) {
678 uint16_t nonce = ((uint16_t) i << 8U) | (uint16_t) j;
680 &ws->ztmp, &sig_z[(size_t) j * p->polyz_packed_bytes], p->gamma1);
681 MLDSA_POLY_ntt(&ws->ztmp);
682 MLDSA_SAMPLE_uniform(&ws->overlay.az_phase.aij, ws->rho, nonce);
684 &ws->overlay.az_phase.dot, &ws->overlay.az_phase.aij, &ws->ztmp, (j == 0U) ? 1 : 0);
685 }
686
688 &pk[MLDSA_SEEDBYTES + (size_t) i * MLDSA_POLYT1_PACKEDBYTES]);
690 MLDSA_POLY_ntt(&ws->t1tmp);
691 MLDSA_POLY_pointwise_montgomery(&ws->t1tmp, &ws->cp, &ws->t1tmp, 1);
692
697
698 explicit_bzero(&ws->htmp, sizeof(ws->htmp));
699 for (uint32_t j = k_offset; j < limit; j++) {
700 ws->htmp.coeffs[sig_h[j]] = 1;
701 }
702 k_offset = limit;
703
705
707 &ws->w1_packed[(size_t) i * p->polyw1_packed_bytes], &ws->t1tmp, p->gamma2);
708 }
709
711 p->ctilde_bytes,
712 ws->mu,
714 ws->w1_packed,
715 (size_t) p->k * p->polyw1_packed_bytes);
716
717 // Compare c_tilde
718 if (memcmp(ws->ctilde, ws->ctilde2, p->ctilde_bytes) != 0) {
719 error = CX_INVALID_PARAMETER;
720 goto cleanup;
721 }
722
723 error = CX_OK;
724
725cleanup:
726 explicit_bzero(ws, sizeof(*ws));
727 return error;
728}
729
730#else /* HAVE_MLDSA_OPTIMIZATION */
731
732/*===========================================================================
733 * OPTIMIZED LOW-RAM IMPLEMENTATION
734 *
735 * Key techniques applied:
736 * 1. smallpoly (int16_t) for c*s1, c*s2 via small NTT mod 3329
737 * 2. Schoolbook c*t0 / c*t1 directly from packed secret/public key
738 * 3. Compressed challenge (68 bytes instead of 1024 byte poly)
739 * 4. Compressed w buffers (768 bytes/row instead of 1024 bytes)
740 * 5. Fused A expansion (streaming 3 bytes at a time into compressed w)
741 * 6. Streaming gamma1 sampling (5-9 byte buffer)
742 * 7. Hint index list in verify (max 80 bytes vs 1024 byte poly)
743 * 8. Eliminated full y/yhat vectors
744 *===========================================================================*/
745
749typedef struct MLDSA_sign_opt_workspace_s {
750 uint8_t rho[MLDSA_SEEDBYTES];
751 uint8_t K[MLDSA_SEEDBYTES];
752 uint8_t tr[MLDSA_TRBYTES];
753 uint8_t mu[MLDSA_CRHBYTES];
754 uint8_t rhoprime[MLDSA_CRHBYTES];
755 uint8_t ctilde[64U];
756 uint8_t ccomp[MLDSA_CCOMP_BYTES];
757 uint8_t wcomp[MLDSA_MAX_K][MLDSA_WCOMP_BYTES];
758 union {
759 mldsa_poly full;
760 struct {
761 mldsa_smallpoly scp;
762 mldsa_smallpoly stmp;
763 } small;
764 } polybuf;
765 uint8_t w1_packed[MLDSA_MAX_K * 192U];
766} MLDSA_sign_opt_workspace_t;
767
771cx_err_t MLDSA_internal_sign_core(uint8_t *sig,
772 size_t sig_len,
773 size_t *sig_actual_len,
774 const MLDSA_formatted_message_t *formatted_mprime,
775 const uint8_t *precomputed_mu,
776 uint8_t *rnd,
777 size_t rnd_len,
778 const uint8_t *sk,
779 size_t sk_len,
780 MLDSA_param_t param)
781{
782 MLDSA_sign_opt_workspace_t ws_local = {0};
783 MLDSA_sign_opt_workspace_t *ws = &ws_local;
784 const MLDSA_param_info_t *p = NULL;
785 const uint8_t *rnd_input = rnd;
786 uint8_t zero_rnd[MLDSA_RNDBYTES] = {0};
787 cx_err_t error = CX_INTERNAL_ERROR;
788 uint16_t kappa = 0U;
789 uint32_t attempts = 0U;
790
791 if ((sig == NULL) || (sk == NULL) || (sig_actual_len == NULL)) {
792 error = CX_INVALID_PARAMETER;
793 goto cleanup;
794 }
795 if ((formatted_mprime == NULL) && (precomputed_mu == NULL)) {
796 error = CX_INVALID_PARAMETER;
797 goto cleanup;
798 }
799 if ((rnd != NULL) && (rnd_len != MLDSA_RNDBYTES)) {
800 error = CX_INVALID_PARAMETER;
801 goto cleanup;
802 }
803 if ((rnd == NULL) && (rnd_len != 0U)) {
804 error = CX_INVALID_PARAMETER;
805 goto cleanup;
806 }
807 if (param >= MLDSA_NUM_PARAM_SETS) {
808 error = CX_INVALID_PARAMETER_VALUE;
809 goto cleanup;
810 }
811
812 p = &MLDSA_PARAM[param];
813
814 if (sk_len < p->sk_bytes) {
815 error = CX_INVALID_PARAMETER_SIZE;
816 goto cleanup;
817 }
818 if (sig_len < p->sig_bytes) {
819 error = CX_INVALID_PARAMETER_SIZE;
820 goto cleanup;
821 }
822
823 if (rnd_input == NULL) {
824 rnd_input = zero_rnd;
825 }
826
827 explicit_bzero(ws, sizeof(*ws));
828
829 // Unpack secret-key header
830 memcpy(ws->rho, sk, MLDSA_SEEDBYTES);
831 memcpy(ws->K, &sk[MLDSA_SEEDBYTES], MLDSA_SEEDBYTES);
832 memcpy(ws->tr, &sk[2U * MLDSA_SEEDBYTES], MLDSA_TRBYTES);
833
834 const uint8_t *sk_s1 = &sk[2U * MLDSA_SEEDBYTES + MLDSA_TRBYTES];
835 const uint8_t *sk_s2 = &sk_s1[(size_t) p->l * p->polyeta_packed_bytes];
836 const uint8_t *sk_t0 = &sk_s2[(size_t) p->k * p->polyeta_packed_bytes];
837
838 // Compute or import mu
839 if (precomputed_mu != NULL) {
840 memcpy(ws->mu, precomputed_mu, MLDSA_CRHBYTES);
841 }
842 else {
843 mldsa_compute_mu(ws->mu, ws->tr, formatted_mprime);
844 }
845
846 // Compute rhoprime = H(K || rnd || mu, 64)
847 {
848 cx_sha3_t sha3_ctx = {0};
849 cx_shake256_init_no_throw(&sha3_ctx, MLDSA_CRHBYTES * 8U);
850 cx_hash_no_throw((cx_hash_t *) &sha3_ctx, 0, ws->K, MLDSA_SEEDBYTES, NULL, 0);
851 cx_hash_no_throw((cx_hash_t *) &sha3_ctx, 0, rnd_input, MLDSA_RNDBYTES, NULL, 0);
852 cx_hash_no_throw(
853 (cx_hash_t *) &sha3_ctx, CX_LAST, ws->mu, MLDSA_CRHBYTES, ws->rhoprime, MLDSA_CRHBYTES);
854 }
855
856 // Rejection sampling loop
857 while (attempts < MLDSA_MAX_SIGN_ATTEMPTS) {
858 attempts++;
859
860 // Zero compressed w buffers
861 for (uint8_t k_idx = 0U; k_idx < p->k; k_idx++) {
862 memset(ws->wcomp[k_idx], 0, MLDSA_WCOMP_BYTES);
863 }
864
865 // For each y polynomial: sample, NTT, fuse A expansion into wcomp
866 for (uint8_t l_idx = 0U; l_idx < p->l; l_idx++) {
867 MLDSA_LOWRAM_sample_gamma1(
868 &ws->polybuf.full, ws->rhoprime, (uint16_t) (kappa + l_idx), p->gamma1);
869 MLDSA_POLY_ntt(&ws->polybuf.full);
870
871 for (uint8_t k_idx = 0U; k_idx < p->k; k_idx++) {
872 uint16_t nonce = ((uint16_t) k_idx << 8U) | (uint16_t) l_idx;
873 MLDSA_LOWRAM_expand_aij_accum(ws->wcomp[k_idx], &ws->polybuf.full, ws->rho, nonce);
874 }
875 }
876 kappa += (uint16_t) p->l;
877
878 // For each row: unpack wcomp, INTT, repack as reduced, compute highbits, pack w1
879 for (uint8_t k_idx = 0U; k_idx < p->k; k_idx++) {
880 MLDSA_LOWRAM_polyw_unpack(&ws->polybuf.full, ws->wcomp[k_idx]);
881 MLDSA_POLY_invntt_tomont(&ws->polybuf.full);
882 MLDSA_POLY_caddq_all(&ws->polybuf.full);
883 MLDSA_LOWRAM_polyw_pack(ws->wcomp[k_idx], &ws->polybuf.full);
884 MLDSA_LOWRAM_poly_highbits(&ws->polybuf.full, &ws->polybuf.full, p->gamma2);
885 MLDSA_PACK_polyw1(&ws->w1_packed[(size_t) k_idx * p->polyw1_packed_bytes],
886 &ws->polybuf.full,
887 p->gamma2);
888 }
889
890 // Compute challenge hash ctilde
891 MLDSA_UTIL_shake256_two(ws->ctilde,
892 p->ctilde_bytes,
893 ws->mu,
895 ws->w1_packed,
896 (size_t) p->k * p->polyw1_packed_bytes);
897
898 MLDSA_SAMPLE_challenge(&ws->polybuf.full, ws->ctilde, p->ctilde_bytes, p->tau);
899 MLDSA_LOWRAM_challenge_compress(ws->ccomp, &ws->polybuf.full, p->tau);
900
901 // Convert challenge to small NTT form for c*s multiplications
902 MLDSA_SMALLPOLY_ntt_copy(&ws->polybuf.small.scp, &ws->polybuf.full);
903
904 // Compute z = y + c*s1 for each l, check norm, and pack into sig
905 {
906 uint8_t z_reject = 0U;
907 for (uint8_t l_idx = 0U; l_idx < p->l; l_idx++) {
908 // Need challenge in small NTT form: re-decompress
909 if (l_idx != 0U) {
910 MLDSA_LOWRAM_challenge_decompress(&ws->polybuf.full, ws->ccomp, p->tau);
911 MLDSA_SMALLPOLY_ntt_copy(&ws->polybuf.small.scp, &ws->polybuf.full);
912 }
913
914 // Unpack s1[l_idx] into small poly
915 MLDSA_SMALLPOLY_unpack_eta(&ws->polybuf.small.stmp,
916 &sk_s1[(size_t) l_idx * p->polyeta_packed_bytes],
917 p->eta);
918 MLDSA_SMALLPOLY_ntt(ws->polybuf.small.stmp.coeffs);
919
920 // Compute c*s1[l_idx] via small basemul + INTT (full poly)
921 MLDSA_SMALLPOLY_basemul_invntt(
922 &ws->polybuf.full, &ws->polybuf.small.scp, &ws->polybuf.small.stmp);
923
924 // z[l_idx] = y[l_idx] + c*s1[l_idx] (re-sample y and add)
925 MLDSA_LOWRAM_sample_gamma1_add(&ws->polybuf.full,
926 &ws->polybuf.full,
927 ws->rhoprime,
928 (uint16_t) (kappa - (uint16_t) p->l + l_idx),
929 p->gamma1);
930 MLDSA_POLY_reduce(&ws->polybuf.full);
931
932 // Check ||z||_inf < gamma1 - beta
933 if (MLDSA_POLY_chknorm(&ws->polybuf.full, p->gamma1 - (int32_t) p->beta)) {
934 z_reject = 1U;
935 break;
936 }
937
938 // Pack z into signature
939 MLDSA_PACK_polyz(sig + p->ctilde_bytes + (size_t) l_idx * p->polyz_packed_bytes,
940 &ws->polybuf.full,
941 p->gamma1);
942 }
943 if (z_reject != 0U) {
944 continue;
945 }
946 }
947
948 // Compute r0 = w - c*s2, check norm
949 {
950 uint8_t r0_reject = 0U;
951 for (uint8_t k_idx = 0U; k_idx < p->k; k_idx++) {
952 // Re-decompress challenge and prepare small NTT
953 MLDSA_LOWRAM_challenge_decompress(&ws->polybuf.full, ws->ccomp, p->tau);
954 MLDSA_SMALLPOLY_ntt_copy(&ws->polybuf.small.scp, &ws->polybuf.full);
955
956 // Unpack s2[k_idx] into small poly
957 MLDSA_SMALLPOLY_unpack_eta(&ws->polybuf.small.stmp,
958 &sk_s2[(size_t) k_idx * p->polyeta_packed_bytes],
959 p->eta);
960 MLDSA_SMALLPOLY_ntt(ws->polybuf.small.stmp.coeffs);
961
962 // c*s2[k_idx] via small basemul + INTT
963 MLDSA_SMALLPOLY_basemul_invntt(
964 &ws->polybuf.full, &ws->polybuf.small.scp, &ws->polybuf.small.stmp);
965
966 // r0 = wcomp - c*s2: subtract from compressed w
967 MLDSA_LOWRAM_polyw_sub(&ws->polybuf.full, ws->wcomp[k_idx], &ws->polybuf.full);
968 MLDSA_POLY_reduce(&ws->polybuf.full);
969
970 // Store back into wcomp for hint computation
971 MLDSA_LOWRAM_polyw_pack(ws->wcomp[k_idx], &ws->polybuf.full);
972
973 // r0 = LowBits(w) - c*s2: reconstruct
974 // from the stored (w - c*s2) and w1 = HighBits(w).
975 MLDSA_LOWRAM_poly_r0(&ws->polybuf.full,
976 ws->wcomp[k_idx],
977 &ws->w1_packed[(size_t) k_idx * p->polyw1_packed_bytes],
978 p->gamma2);
979
980 if (MLDSA_POLY_chknorm(&ws->polybuf.full, p->gamma2 - (int32_t) p->beta)) {
981 r0_reject = 1U;
982 break;
983 }
984 }
985 if (r0_reject != 0U) {
986 continue;
987 }
988 }
989
990 // Fused ct0 computation, norm check, and hint generation
991 {
992 uint32_t n_hints = 0U;
993 uint8_t reject = 0U;
994 unsigned int hints_written = 0U;
995
996 // Zero hint portion of signature
997 memset(sig + p->ctilde_bytes + (size_t) p->l * p->polyz_packed_bytes,
998 0,
1000
1001 for (uint8_t k_idx = 0U; k_idx < p->k; k_idx++) {
1002 MLDSA_LOWRAM_schoolbook_t0(&ws->polybuf.full,
1003 ws->ccomp,
1004 &sk_t0[(size_t) k_idx * MLDSA_POLYT0_PACKEDBYTES],
1005 p->tau);
1006 MLDSA_POLY_reduce(&ws->polybuf.full);
1007
1008 // Check ||ct0||_inf < gamma2
1009 if (MLDSA_POLY_chknorm(&ws->polybuf.full, p->gamma2)) {
1010 reject = 1U;
1011 break;
1012 }
1013
1014 // make_hint from ct0 and (w - cs2) stored in wcomp
1015 uint32_t row_hints = MLDSA_LOWRAM_make_hint(
1016 &ws->polybuf.full,
1017 &ws->polybuf.full,
1018 ws->wcomp[k_idx],
1019 &ws->w1_packed[(size_t) k_idx * p->polyw1_packed_bytes],
1020 p->gamma2);
1021 n_hints += row_hints;
1022
1023 if (n_hints > p->omega) {
1024 reject = 1U;
1025 break;
1026 }
1027
1028 // Pack hint into signature
1029 {
1030 uint8_t *sig_h = sig + p->ctilde_bytes + (size_t) p->l * p->polyz_packed_bytes;
1031 for (uint32_t j = 0U; j < MLDSA_N; j++) {
1032 if (ws->polybuf.full.coeffs[j] != 0) {
1033 sig_h[hints_written] = (uint8_t) j;
1034 hints_written++;
1035 }
1036 }
1037 sig_h[p->omega + k_idx] = (uint8_t) hints_written;
1038 }
1039 }
1040
1041 if (reject != 0U) {
1042 continue;
1043 }
1044
1045 // Pad remaining hint bytes with zeros
1046 {
1047 uint8_t *sig_h = sig + p->ctilde_bytes + (size_t) p->l * p->polyz_packed_bytes;
1048 while (hints_written < p->omega) {
1049 sig_h[hints_written] = 0U;
1050 hints_written++;
1051 }
1052 }
1053 }
1054
1055 // Success: pack ctilde into signature
1056 memcpy(sig, ws->ctilde, p->ctilde_bytes);
1057 *sig_actual_len = p->sig_bytes;
1058 error = CX_OK;
1059 goto cleanup;
1060 }
1061
1062 // Exhausted attempts
1063 error = CX_INTERNAL_ERROR;
1064
1065cleanup:
1066 explicit_bzero(zero_rnd, sizeof(zero_rnd));
1067 explicit_bzero(ws, sizeof(*ws));
1068 return error;
1069}
1070
1074typedef struct MLDSA_verify_opt_workspace_s {
1075 uint8_t rho[MLDSA_SEEDBYTES];
1076 uint8_t ctilde[64U];
1077 uint8_t mu[MLDSA_CRHBYTES];
1078 uint8_t ccomp[MLDSA_CCOMP_BYTES];
1079 uint8_t wcomp[MLDSA_WCOMP_BYTES];
1080 uint8_t w1_packed[MLDSA_MAX_K * 192U];
1081 uint8_t ctilde2[64U];
1082 uint8_t h_indices[MLDSA_MAX_OMEGA];
1083 mldsa_poly tmp;
1084 mldsa_poly ct1;
1085 union {
1086 uint8_t tr[MLDSA_TRBYTES];
1087 } early;
1088} MLDSA_verify_opt_workspace_t;
1089
1093cx_err_t MLDSA_internal_verify_core(const uint8_t *sig,
1094 size_t sig_len,
1095 const MLDSA_formatted_message_t *formatted_mprime,
1096 const uint8_t *precomputed_mu,
1097 const uint8_t *pk,
1098 size_t pk_len,
1099 MLDSA_param_t param)
1100{
1101 MLDSA_verify_opt_workspace_t ws_local = {0};
1102 MLDSA_verify_opt_workspace_t *ws = &ws_local;
1103 const MLDSA_param_info_t *p = NULL;
1104 const uint8_t *sig_z = NULL;
1105 const uint8_t *sig_h = NULL;
1106 uint32_t k_offset = 0U;
1107 cx_err_t error = CX_INTERNAL_ERROR;
1108
1109 if ((sig == NULL) || (pk == NULL)) {
1110 error = CX_INVALID_PARAMETER;
1111 goto cleanup;
1112 }
1113 if ((formatted_mprime == NULL) && (precomputed_mu == NULL)) {
1114 error = CX_INVALID_PARAMETER;
1115 goto cleanup;
1116 }
1117 if (param >= MLDSA_NUM_PARAM_SETS) {
1118 error = CX_INVALID_PARAMETER_VALUE;
1119 goto cleanup;
1120 }
1121
1122 p = &MLDSA_PARAM[param];
1123
1124 if (pk_len < p->pk_bytes) {
1125 error = CX_INVALID_PARAMETER_SIZE;
1126 goto cleanup;
1127 }
1128 if (sig_len < p->sig_bytes) {
1129 error = CX_INVALID_PARAMETER_SIZE;
1130 goto cleanup;
1131 }
1132
1133 explicit_bzero(ws, sizeof(*ws));
1134
1135 memcpy(ws->rho, pk, MLDSA_SEEDBYTES);
1136 memcpy(ws->ctilde, sig, p->ctilde_bytes);
1137
1138 sig_z = &sig[p->ctilde_bytes];
1139 sig_h = &sig_z[(size_t) p->l * p->polyz_packed_bytes];
1140
1141 // Validate hint encoding
1142 for (uint32_t i = 0U; i < p->k; i++) {
1143 uint32_t limit = (uint32_t) sig_h[p->omega + i];
1144 if ((limit < k_offset) || (limit > p->omega)) {
1145 error = CX_INVALID_PARAMETER;
1146 goto cleanup;
1147 }
1148 for (uint32_t j = k_offset; j < limit; j++) {
1149 if ((j > k_offset) && (sig_h[j] <= sig_h[j - 1U])) {
1150 error = CX_INVALID_PARAMETER;
1151 goto cleanup;
1152 }
1153 }
1154 k_offset = limit;
1155 }
1156 for (uint32_t j = k_offset; j < p->omega; j++) {
1157 if (sig_h[j] != 0U) {
1158 error = CX_INVALID_PARAMETER;
1159 goto cleanup;
1160 }
1161 }
1162
1163 // Stream z and check ||z||_inf < gamma1 - beta
1164 for (uint8_t j = 0U; j < p->l; j++) {
1165 MLDSA_PACK_unpack_polyz(&ws->tmp, &sig_z[(size_t) j * p->polyz_packed_bytes], p->gamma1);
1166 if (MLDSA_POLY_chknorm(&ws->tmp, p->gamma1 - (int32_t) p->beta)) {
1167 error = CX_INVALID_PARAMETER;
1168 goto cleanup;
1169 }
1170 }
1171
1172 // Compute or import mu
1173 if (precomputed_mu != NULL) {
1174 memcpy(ws->mu, precomputed_mu, MLDSA_CRHBYTES);
1175 }
1176 else {
1177 MLDSA_UTIL_shake256(ws->early.tr, MLDSA_TRBYTES, pk, p->pk_bytes);
1178 mldsa_compute_mu(ws->mu, ws->early.tr, formatted_mprime);
1179 }
1180
1181 // Compress challenge for schoolbook use
1182 MLDSA_SAMPLE_challenge(&ws->tmp, ws->ctilde, p->ctilde_bytes, p->tau);
1183 MLDSA_LOWRAM_challenge_compress(ws->ccomp, &ws->tmp, p->tau);
1184
1185 // Fused streaming verify: one row at a time
1186 k_offset = 0U;
1187 for (uint8_t i = 0U; i < p->k; i++) {
1188 uint32_t limit = (uint32_t) sig_h[p->omega + i];
1189
1190 // Compute Az[i] = sum_j A[i][j] * NTT(z[j]) using fused expansion
1191 memset(ws->wcomp, 0, MLDSA_WCOMP_BYTES);
1192 for (uint8_t j = 0U; j < p->l; j++) {
1193 uint16_t nonce = ((uint16_t) i << 8U) | (uint16_t) j;
1195 &ws->tmp, &sig_z[(size_t) j * p->polyz_packed_bytes], p->gamma1);
1196 MLDSA_POLY_ntt(&ws->tmp);
1197 MLDSA_LOWRAM_expand_aij_accum(ws->wcomp, &ws->tmp, ws->rho, nonce);
1198 }
1199
1200 // Unpack Az, INTT
1201 MLDSA_LOWRAM_polyw_unpack(&ws->tmp, ws->wcomp);
1202 MLDSA_POLY_reduce(&ws->tmp);
1203 MLDSA_POLY_invntt_tomont(&ws->tmp);
1204
1205 // Subtract c*t1*2^d using schoolbook from packed pk
1206 MLDSA_LOWRAM_schoolbook_t1(&ws->ct1,
1207 ws->ccomp,
1208 &pk[MLDSA_SEEDBYTES + (size_t) i * MLDSA_POLYT1_PACKEDBYTES],
1209 p->tau);
1210 MLDSA_POLY_sub(&ws->tmp, &ws->ct1);
1211 MLDSA_POLY_reduce(&ws->tmp);
1212 MLDSA_POLY_caddq_all(&ws->tmp);
1213
1214 // Extract hint indices for this row
1215 uint32_t num_hints = limit - k_offset;
1216 for (uint32_t j = 0U; j < num_hints; j++) {
1217 ws->h_indices[j] = sig_h[k_offset + j];
1218 }
1219 k_offset = limit;
1220
1221 // Use hint (index-list form) to reconstruct w1'
1222 MLDSA_LOWRAM_use_hint_indices(&ws->tmp, &ws->tmp, ws->h_indices, num_hints, p->gamma2);
1223
1224 // Pack w1 row
1225 MLDSA_PACK_polyw1(&ws->w1_packed[(size_t) i * p->polyw1_packed_bytes], &ws->tmp, p->gamma2);
1226 }
1227
1228 // Recompute challenge hash
1229 MLDSA_UTIL_shake256_two(ws->ctilde2,
1230 p->ctilde_bytes,
1231 ws->mu,
1233 ws->w1_packed,
1234 (size_t) p->k * p->polyw1_packed_bytes);
1235
1236 // Compare c_tilde
1237 if (memcmp(ws->ctilde, ws->ctilde2, p->ctilde_bytes) != 0) {
1238 error = CX_INVALID_PARAMETER;
1239 goto cleanup;
1240 }
1241
1242 error = CX_OK;
1243
1244cleanup:
1245 explicit_bzero(ws, sizeof(*ws));
1246 return error;
1247}
1248
1249#endif /* HAVE_MLDSA_OPTIMIZATION */
1250
1251/*********************
1252 * GLOBAL FUNCTIONS
1253 *********************/
1254
1255/*---------------------------------------------------------------------------
1256 * KeyGen (FIPS 204, Algorithm 1)
1257 *---------------------------------------------------------------------------*/
1258cx_err_t MLDSA_keygen(uint8_t *pk, size_t pk_len, uint8_t *sk, size_t sk_len, MLDSA_param_t param)
1259{
1260 cx_err_t error = CX_INTERNAL_ERROR;
1261 uint8_t seed[MLDSA_SEEDBYTES] = {0};
1262
1263 if ((pk == NULL) || (sk == NULL)) {
1264 error = CX_INVALID_PARAMETER;
1265 goto cleanup;
1266 }
1267
1268 if (param >= MLDSA_NUM_PARAM_SETS) {
1269 error = CX_INVALID_PARAMETER_VALUE;
1270 goto cleanup;
1271 }
1272
1273 cx_rng_no_throw(seed, MLDSA_SEEDBYTES);
1274 error = MLDSA_internal_keygen(pk, pk_len, sk, sk_len, seed, param);
1275
1276cleanup:
1277 explicit_bzero(seed, sizeof(seed));
1278 return error;
1279}
1280
1281/*---------------------------------------------------------------------------
1282 * Sign (FIPS 204, Algorithms 2 & 7)
1283 *---------------------------------------------------------------------------*/
1284cx_err_t MLDSA_sign(uint8_t *sig,
1285 size_t sig_len,
1286 size_t *sig_actual_len,
1287 const uint8_t *msg,
1288 size_t msg_len,
1289 const uint8_t *ctx,
1290 size_t ctx_len,
1291 const uint8_t *sk,
1292 size_t sk_len,
1293 MLDSA_param_t param)
1294{
1295 MLDSA_formatted_message_t mprime = {0};
1296 uint8_t rnd[MLDSA_RNDBYTES] = {0};
1297 cx_err_t error = mldsa_format_message_pure(&mprime, ctx, ctx_len, msg, msg_len);
1298
1299 if (error != CX_OK) {
1300 goto cleanup;
1301 }
1302
1303 cx_rng_no_throw(rnd, MLDSA_RNDBYTES);
1305 sig, sig_len, sig_actual_len, &mprime, NULL, rnd, sizeof(rnd), sk, sk_len, param);
1306
1307cleanup:
1308 explicit_bzero(rnd, sizeof(rnd));
1309 return error;
1310}
1311
1312/*---------------------------------------------------------------------------
1313 * Verify (FIPS 204, Algorithms 3 & 8)
1314 *---------------------------------------------------------------------------*/
1315cx_err_t MLDSA_verify(const uint8_t *sig,
1316 size_t sig_len,
1317 const uint8_t *msg,
1318 size_t msg_len,
1319 const uint8_t *ctx,
1320 size_t ctx_len,
1321 const uint8_t *pk,
1322 size_t pk_len,
1323 MLDSA_param_t param)
1324{
1325 MLDSA_formatted_message_t mprime = {0};
1326 cx_err_t error = mldsa_format_message_pure(&mprime, ctx, ctx_len, msg, msg_len);
1327
1328 if (error != CX_OK) {
1329 return error;
1330 }
1331
1332 return MLDSA_internal_verify_core(sig, sig_len, &mprime, NULL, pk, pk_len, param);
1333}
1334
1335/*---------------------------------------------------------------------------
1336 * HashML-DSA Sign (FIPS 204, Algorithm 4)
1337 *---------------------------------------------------------------------------*/
1338cx_err_t MLDSA_sign_prehash(uint8_t *sig,
1339 size_t sig_len,
1340 size_t *sig_actual_len,
1341 const uint8_t *ph,
1342 size_t ph_len,
1343 const uint8_t *ctx,
1344 size_t ctx_len,
1345 const uint8_t *sk,
1346 size_t sk_len,
1347 MLDSA_prehash_t prehash_alg,
1348 MLDSA_param_t param)
1349{
1350 MLDSA_formatted_message_t mprime = {0};
1351 uint8_t rnd[MLDSA_RNDBYTES] = {0};
1352 cx_err_t error = mldsa_format_message_prehash(&mprime, ctx, ctx_len, prehash_alg, ph, ph_len);
1353
1354 if (error != CX_OK) {
1355 goto cleanup;
1356 }
1357
1358 cx_rng_no_throw(rnd, MLDSA_RNDBYTES);
1360 sig, sig_len, sig_actual_len, &mprime, NULL, rnd, sizeof(rnd), sk, sk_len, param);
1361
1362cleanup:
1363 explicit_bzero(rnd, sizeof(rnd));
1364 return error;
1365}
1366
1367/*---------------------------------------------------------------------------
1368 * HashML-DSA Verify (FIPS 204, Algorithm 5)
1369 *---------------------------------------------------------------------------*/
1370cx_err_t MLDSA_verify_prehash(const uint8_t *sig,
1371 size_t sig_len,
1372 const uint8_t *ph,
1373 size_t ph_len,
1374 const uint8_t *ctx,
1375 size_t ctx_len,
1376 const uint8_t *pk,
1377 size_t pk_len,
1378 MLDSA_prehash_t prehash_alg,
1379 MLDSA_param_t param)
1380{
1381 MLDSA_formatted_message_t mprime = {0};
1382 cx_err_t error = mldsa_format_message_prehash(&mprime, ctx, ctx_len, prehash_alg, ph, ph_len);
1383
1384 if (error != CX_OK) {
1385 return error;
1386 }
1387
1388 return MLDSA_internal_verify_core(sig, sig_len, &mprime, NULL, pk, pk_len, param);
1389}
struct MLDSA_sign_stack_workspace_s MLDSA_sign_stack_workspace_t
Stack-allocated workspace for MLDSA_internal_sign_core.
static cx_err_t mldsa_format_message_prehash(MLDSA_formatted_message_t *mprime, const uint8_t *ctx, size_t ctx_len, MLDSA_prehash_t prehash_alg, const uint8_t *ph, size_t ph_len)
Formats a pre-hashed message into M' for HashML-DSA (FIPS 204, Section 5.4).
Definition cx_mldsa.c:232
cx_err_t MLDSA_verify_prehash(const uint8_t *sig, size_t sig_len, const uint8_t *ph, size_t ph_len, const uint8_t *ctx, size_t ctx_len, const uint8_t *pk, size_t pk_len, MLDSA_prehash_t prehash_alg, MLDSA_param_t param)
HashML-DSA pre-hash signature verification (FIPS 204, Algorithm 5).
Definition cx_mldsa.c:1370
cx_err_t MLDSA_verify(const uint8_t *sig, size_t sig_len, const uint8_t *msg, size_t msg_len, const uint8_t *ctx, size_t ctx_len, const uint8_t *pk, size_t pk_len, MLDSA_param_t param)
ML-DSA signature verification.
Definition cx_mldsa.c:1315
#define MLDSA_MAX_SIGN_ATTEMPTS
Definition cx_mldsa.c:54
cx_err_t MLDSA_keygen(uint8_t *pk, size_t pk_len, uint8_t *sk, size_t sk_len, MLDSA_param_t param)
Generates an ML-DSA key pair.
Definition cx_mldsa.c:1258
struct MLDSA_verify_stack_workspace_s MLDSA_verify_stack_workspace_t
Stack-allocated workspace for MLDSA_internal_verify_core.
static cx_err_t mldsa_format_message_pure(MLDSA_formatted_message_t *mprime, const uint8_t *ctx, size_t ctx_len, const uint8_t *msg, size_t msg_len)
Formats a pure (non-pre-hashed) message into M' (FIPS 204, Section 5.2).
Definition cx_mldsa.c:187
cx_err_t MLDSA_sign_prehash(uint8_t *sig, size_t sig_len, size_t *sig_actual_len, const uint8_t *ph, size_t ph_len, const uint8_t *ctx, size_t ctx_len, const uint8_t *sk, size_t sk_len, MLDSA_prehash_t prehash_alg, MLDSA_param_t param)
HashML-DSA pre-hash signature generation (FIPS 204, Algorithm 4).
Definition cx_mldsa.c:1338
struct MLDSA_prehash_info_s MLDSA_prehash_info_t
DER-encoded OID and expected output length for each pre-hash algorithm. (FIPS 204,...
cx_err_t MLDSA_internal_verify_core(const uint8_t *sig, size_t sig_len, const MLDSA_formatted_message_t *formatted_mprime, const uint8_t *precomputed_mu, const uint8_t *pk, size_t pk_len, MLDSA_param_t param)
Core ML-DSA verification routine (FIPS 204, Algorithms 3 & 8).
Definition cx_mldsa.c:577
struct MLDSA_formatted_message_s MLDSA_formatted_message_t
static const MLDSA_prehash_info_t MLDSA_PREHASH_INFO[MLDSA_NUM_PREHASH_ALGS]
Definition cx_mldsa.c:155
cx_err_t MLDSA_sign(uint8_t *sig, size_t sig_len, size_t *sig_actual_len, const uint8_t *msg, size_t msg_len, const uint8_t *ctx, size_t ctx_len, const uint8_t *sk, size_t sk_len, MLDSA_param_t param)
ML-DSA signature generation.
Definition cx_mldsa.c:1284
static void mldsa_compute_mu(uint8_t mu[MLDSA_CRHBYTES], const uint8_t tr[MLDSA_TRBYTES], const MLDSA_formatted_message_t *mprime)
Computes mu = SHAKE256(tr || M', 64) for a formatted message M'.
Definition cx_mldsa.c:280
cx_err_t MLDSA_internal_sign_core(uint8_t *sig, size_t sig_len, size_t *sig_actual_len, const MLDSA_formatted_message_t *formatted_mprime, const uint8_t *precomputed_mu, uint8_t *rnd, size_t rnd_len, const uint8_t *sk, size_t sk_len, MLDSA_param_t param)
Core ML-DSA signing routine (FIPS 204, Algorithms 2 & 7).
Definition cx_mldsa.c:317
cx_err_t MLDSA_internal_keygen(uint8_t *pk, size_t pk_len, uint8_t *sk, size_t sk_len, const uint8_t seed[MLDSA_SEEDBYTES], MLDSA_param_t param)
Generates an ML-DSA key pair from a seed (deterministic).
ML-DSA low-RAM helper functions.
uint32_t MLDSA_PACK_polyw1(uint8_t *r, const mldsa_poly *a, int32_t gamma2)
Bit-pack polynomial w1 with coefficients fitting in ceil(log2((q-1)/(2*gamma2))) bits.
uint32_t MLDSA_PACK_unpack_polyeta(mldsa_poly *r, const uint8_t *a, uint8_t eta)
Unpack polynomial with coefficients in [-eta, eta].
void MLDSA_PACK_unpack_polyt0(mldsa_poly *r, const uint8_t a[MLDSA_POLYT0_PACKEDBYTES])
Unpack polynomial t0 from bytes.
uint32_t MLDSA_PACK_unpack_polyz(mldsa_poly *r, const uint8_t *a, int32_t gamma1)
Unpack polynomial z.
void MLDSA_PACK_unpack_polyt1(mldsa_poly *r, const uint8_t a[MLDSA_POLYT1_PACKEDBYTES])
Unpack polynomial t1 from bytes.
uint32_t MLDSA_PACK_polyz(uint8_t *r, const mldsa_poly *a, int32_t gamma1)
Bit-pack polynomial z with coefficients in [-(gamma1-1), gamma1].
void MLDSA_POLY_reduce(mldsa_poly *a)
Applies reduce32 to all coefficients of a polynomial.
int MLDSA_POLY_chknorm(const mldsa_poly *a, int32_t B)
Checks infinity norm of polynomial against bound B.
void MLDSA_POLY_ntt(mldsa_poly *a)
Forward NTT in place.
void MLDSA_POLY_sub(mldsa_poly *a, const mldsa_poly *b)
Subtracts polynomial b from polynomial a in place.
void MLDSA_POLY_shiftl(mldsa_poly *a)
Shifts all coefficients left by D bits.
void MLDSA_POLY_caddq_all(mldsa_poly *a)
Applies caddq to all coefficients of a polynomial.
void MLDSA_POLY_add(mldsa_poly *a, const mldsa_poly *b)
Adds polynomial b to polynomial a in place.
void MLDSA_POLY_invntt_tomont(mldsa_poly *a)
Inverse NTT and multiply by Montgomery factor.
void MLDSA_POLY_pointwise_montgomery(mldsa_poly *c, const mldsa_poly *a, const mldsa_poly *b, int first)
Pointwise multiplication (Montgomery) with accumulation.
void MLDSA_POLYVEC_invntt_tomont_k(mldsa_polyveck *v, uint8_t k)
Apply inverse NTT to all polynomials in a K-vector.
void MLDSA_POLYVEC_reduce_k(mldsa_polyveck *v, uint8_t k)
Apply reduce to all polynomials in a K-vector.
void MLDSA_POLYVEC_caddq_k(mldsa_polyveck *v, uint8_t k)
Apply caddq to all polynomials in a K-vector.
void MLDSA_ROUNDING_poly_use_hint(mldsa_poly *b, const mldsa_poly *a, const mldsa_poly *h, int32_t gamma2)
Applies use_hint to all coefficients of a polynomial.
void MLDSA_ROUNDING_poly_decompose(mldsa_poly *a1, mldsa_poly *a0, const mldsa_poly *a, int32_t gamma2)
Applies decompose to all coefficients of a polynomial.
uint32_t MLDSA_ROUNDING_make_hint(int32_t a0, int32_t a1, int32_t gamma2)
Compute hint bit. Returns 1 if adding ct0 to w - ct0 would change the high bits (i....
void MLDSA_SAMPLE_uniform(mldsa_poly *a, const uint8_t seed[MLDSA_SEEDBYTES], uint16_t nonce)
Sample polynomial with uniformly random coefficients in [0, q-1] by performing rejection sampling on ...
void MLDSA_SAMPLE_challenge(mldsa_poly *c, const uint8_t *seed, size_t seedlen, uint8_t tau)
Sample challenge polynomial with TAU coefficients in {-1, +1}.
void MLDSA_SAMPLE_gamma1(mldsa_poly *a, const uint8_t seed[MLDSA_CRHBYTES], uint16_t nonce, int32_t gamma1)
Sample polynomial with coefficients in [-(gamma1-1), gamma1] from SHAKE256(seed||nonce).
ML-DSA small polynomial type and NTT mod 3329 (low-RAM optimization).
void MLDSA_UTIL_shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen)
SHAKE256 hash wrapper.
void MLDSA_UTIL_shake256_two(uint8_t *out, size_t outlen, const uint8_t *in1, size_t in1len, const uint8_t *in2, size_t in2len)
SHAKE256 with two inputs concatenated.
#define MLDSA_RNDBYTES
Definition lcx_mldsa.h:43
#define MLDSA_TRBYTES
Definition lcx_mldsa.h:42
#define MLDSA_POLYT0_PACKEDBYTES
Definition lcx_mldsa.h:46
#define MLDSA_N
Definition lcx_mldsa.h:37
#define MLDSA_SEEDBYTES
Definition lcx_mldsa.h:40
#define MLDSA_CRHBYTES
Definition lcx_mldsa.h:41
#define MLDSA_POLYT1_PACKEDBYTES
Definition lcx_mldsa.h:45
enum MLDSA_prehash_e MLDSA_prehash_t
Hash algorithm selector for HashML-DSA pre-hash signatures.
#define MLDSA_PREHASH_OID_LEN
Definition lcx_mldsa.h:275
@ MLDSA_NUM_PREHASH_ALGS
Definition lcx_mldsa.h:287
#define CX_LAST
Definition lcx_common.h:115
Hash functions.
ML-DSA (Module-Lattice Digital Signature Algorithm) public API.
const MLDSA_param_info_t MLDSA_PARAM[MLDSA_NUM_PARAM_SETS]
Lookup table of ML-DSA parameter sets indexed by MLDSA_param_t.
enum MLDSA_param_e MLDSA_param_t
ML-DSA parameter set selector.
#define MLDSA_MAX_K
Definition lcx_mldsa.h:160
#define MLDSA_NUM_PARAM_SETS
Definition lcx_mldsa.h:162
Random Number Generation.
SHA-3 (Secure Hash Algorithm 3)
uint8_t prefix[2U+255U+MLDSA_PREHASH_OID_LEN]
Definition cx_mldsa.c:77
const uint8_t * payload
Definition cx_mldsa.c:80
ML-DSA parameter set descriptor holding all derived sizes.
Definition lcx_mldsa.h:179
uint16_t polyvech_packed_bytes
Definition lcx_mldsa.h:192
uint16_t polyz_packed_bytes
Definition lcx_mldsa.h:190
uint16_t polyeta_packed_bytes
Definition lcx_mldsa.h:189
uint16_t polyw1_packed_bytes
Definition lcx_mldsa.h:191
DER-encoded OID and expected output length for each pre-hash algorithm. (FIPS 204,...
Definition cx_mldsa.c:67
uint8_t oid[MLDSA_PREHASH_OID_LEN]
Definition cx_mldsa.c:68
Stack-allocated workspace for MLDSA_internal_sign_core.
Definition cx_mldsa.c:91
uint8_t rho[MLDSA_SEEDBYTES]
Definition cx_mldsa.c:92
uint8_t K[MLDSA_SEEDBYTES]
Definition cx_mldsa.c:93
uint8_t w1_packed[MLDSA_MAX_K *192U]
Definition cx_mldsa.c:98
uint8_t mu[MLDSA_CRHBYTES]
Definition cx_mldsa.c:95
uint8_t tr[MLDSA_TRBYTES]
Definition cx_mldsa.c:94
uint8_t rhoprime[MLDSA_CRHBYTES]
Definition cx_mldsa.c:96
Stack-allocated workspace for MLDSA_internal_verify_core.
Definition cx_mldsa.c:134
MLDSA_verify_phase_overlay_t overlay
Definition cx_mldsa.c:144
uint8_t mu[MLDSA_CRHBYTES]
Definition cx_mldsa.c:137
uint8_t rho[MLDSA_SEEDBYTES]
Definition cx_mldsa.c:135
uint8_t w1_packed[MLDSA_MAX_K *192U]
Definition cx_mldsa.c:142
Polynomial with MLDSA_N int32_t coefficients.
int32_t coeffs[MLDSA_N]
Polynomial vector of up to MLDSA_MAX_K polynomials.
mldsa_poly vec[MLDSA_MAX_K]
Phase-overlaid scratch union for MLDSA_internal_verify_core.
Definition cx_mldsa.c:116
struct MLDSA_verify_phase_overlay_t::@3 az_phase
Az product loop temporaries.
uint8_t tr[MLDSA_TRBYTES]
Definition cx_mldsa.c:119
struct MLDSA_verify_phase_overlay_t::@2 setup_phase
Early-phase temporary used to derive mu.