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
93
104typedef union {
106 struct {
107 uint8_t tr[MLDSA_TRBYTES];
108 } setup_phase;
110 struct {
113 } az_phase;
115
134
135/*********************
136 * GLOBAL VARIABLES
137 *********************/
138
139/*********************
140 * STATIC VARIABLES
141 *********************/
142
144 // MLDSA_PREHASH_SHA256
145 {{0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01}, 32U},
146 // MLDSA_PREHASH_SHA512
147 {{0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03}, 64U},
148 // MLDSA_PREHASH_SHA3_256
149 {{0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08}, 32U},
150 // MLDSA_PREHASH_SHA3_512
151 {{0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0A}, 64U},
152 // MLDSA_PREHASH_SHAKE128
153 {{0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0B}, 32U},
154 // MLDSA_PREHASH_SHAKE256
155 {{0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0C}, 64U},
156};
157
158/*********************
159 * STATIC FUNCTIONS
160 *********************/
161
176 const uint8_t *ctx,
177 size_t ctx_len,
178 const uint8_t *msg,
179 size_t msg_len)
180{
181 if (mprime == NULL) {
182 return CX_INVALID_PARAMETER;
183 }
184 if ((msg == NULL) && (msg_len > 0U)) {
185 return CX_INVALID_PARAMETER;
186 }
187 if ((ctx == NULL) && (ctx_len > 0U)) {
188 return CX_INVALID_PARAMETER;
189 }
190 if (ctx_len > 255U) {
191 return CX_INVALID_PARAMETER_SIZE;
192 }
193
194 mprime->prefix[0] = 0x00U;
195 mprime->prefix[1] = (uint8_t) ctx_len;
196 if (ctx_len > 0U) {
197 memcpy(&mprime->prefix[2], ctx, ctx_len);
198 }
199 mprime->prefix_len = 2U + ctx_len;
200 mprime->payload = msg;
201 mprime->payload_len = msg_len;
202
203 return CX_OK;
204}
205
221 const uint8_t *ctx,
222 size_t ctx_len,
223 MLDSA_prehash_t prehash_alg,
224 const uint8_t *ph,
225 size_t ph_len)
226{
227 if (mprime == NULL) {
228 return CX_INVALID_PARAMETER;
229 }
230 if ((ph == NULL) && (ph_len > 0U)) {
231 return CX_INVALID_PARAMETER;
232 }
233 if ((ctx == NULL) && (ctx_len > 0U)) {
234 return CX_INVALID_PARAMETER;
235 }
236 if (ctx_len > 255U) {
237 return CX_INVALID_PARAMETER_SIZE;
238 }
239
240 if ((unsigned) prehash_alg >= MLDSA_NUM_PREHASH_ALGS) {
241 return CX_INVALID_PARAMETER_VALUE;
242 }
243 if (ph_len != MLDSA_PREHASH_INFO[prehash_alg].hash_len) {
244 return CX_INVALID_PARAMETER_SIZE;
245 }
246
247 mprime->prefix[0] = 0x01U;
248 mprime->prefix[1] = (uint8_t) ctx_len;
249 if (ctx_len > 0U) {
250 memcpy(&mprime->prefix[2], ctx, ctx_len);
251 }
252 memcpy(
253 &mprime->prefix[2U + ctx_len], MLDSA_PREHASH_INFO[prehash_alg].oid, MLDSA_PREHASH_OID_LEN);
254 mprime->prefix_len = 2U + ctx_len + MLDSA_PREHASH_OID_LEN;
255 mprime->payload = ph;
256 mprime->payload_len = ph_len;
257
258 return CX_OK;
259}
260
268static cx_err_t mldsa_compute_mu(uint8_t mu[MLDSA_CRHBYTES],
269 const uint8_t tr[MLDSA_TRBYTES],
270 const MLDSA_formatted_message_t *mprime)
271{
272 cx_sha3_t sha3_ctx = {0};
273 cx_err_t error;
274
275 memset(&sha3_ctx, 0, sizeof(sha3_ctx));
276 error = cx_shake256_init_no_throw(&sha3_ctx, MLDSA_CRHBYTES * 8U);
277 if (error != CX_OK) {
278 return error;
279 }
280 error = cx_hash_no_throw((cx_hash_t *) &sha3_ctx, 0, tr, MLDSA_TRBYTES, NULL, 0);
281 if (error != CX_OK) {
282 return error;
283 }
284 error
285 = cx_hash_no_throw((cx_hash_t *) &sha3_ctx, 0, mprime->prefix, mprime->prefix_len, NULL, 0);
286 if (error != CX_OK) {
287 return error;
288 }
289 return cx_hash_no_throw(
290 (cx_hash_t *) &sha3_ctx, CX_LAST, mprime->payload, mprime->payload_len, mu, MLDSA_CRHBYTES);
291}
292
293#ifndef HAVE_MLDSA_OPTIMIZATION
294
316cx_err_t MLDSA_internal_sign_core(uint8_t *sig,
317 size_t sig_len,
318 size_t *sig_actual_len,
319 const MLDSA_formatted_message_t *formatted_mprime,
320 const uint8_t *precomputed_mu,
321 uint8_t *rnd,
322 size_t rnd_len,
323 const uint8_t *sk,
324 size_t sk_len,
325 MLDSA_param_t param)
326{
327 MLDSA_sign_stack_workspace_t ws_local = {0};
328 MLDSA_sign_stack_workspace_t *ws = &ws_local;
329 const MLDSA_param_info_t *p = NULL;
330 const uint8_t *rnd_input = rnd;
331 uint8_t zero_rnd[MLDSA_RNDBYTES] = {0};
332 cx_err_t error = CX_INTERNAL_ERROR;
333 uint16_t kappa = 0U;
334 uint32_t attempts = 0U;
335
336 if ((sig == NULL) || (sk == NULL) || (sig_actual_len == NULL)) {
337 error = CX_INVALID_PARAMETER;
338 goto cleanup;
339 }
340 if ((formatted_mprime == NULL) && (precomputed_mu == NULL)) {
341 error = CX_INVALID_PARAMETER;
342 goto cleanup;
343 }
344
345 if ((rnd != NULL) && (rnd_len != MLDSA_RNDBYTES)) {
346 error = CX_INVALID_PARAMETER;
347 goto cleanup;
348 }
349 if ((rnd == NULL) && (rnd_len != 0U)) {
350 error = CX_INVALID_PARAMETER;
351 goto cleanup;
352 }
353
354 if (param >= MLDSA_NUM_PARAM_SETS) {
355 error = CX_INVALID_PARAMETER_VALUE;
356 goto cleanup;
357 }
358
359 p = &MLDSA_PARAM[param];
360
361 if (sk_len < p->sk_bytes) {
362 error = CX_INVALID_PARAMETER_SIZE;
363 goto cleanup;
364 }
365 if (sig_len < p->sig_bytes) {
366 error = CX_INVALID_PARAMETER_SIZE;
367 goto cleanup;
368 }
369
370 if (rnd_input == NULL) {
371 rnd_input = zero_rnd;
372 }
373
374 explicit_bzero(ws, sizeof(*ws));
375
376 // Unpack secret-key header only; large vectors are decoded on the fly.
377 memcpy(ws->rho, sk, MLDSA_SEEDBYTES);
378 memcpy(ws->K, &sk[MLDSA_SEEDBYTES], MLDSA_SEEDBYTES);
379 memcpy(ws->tr, &sk[2U * MLDSA_SEEDBYTES], MLDSA_TRBYTES);
380
381 const uint8_t *sk_s1 = &sk[2U * MLDSA_SEEDBYTES + MLDSA_TRBYTES];
382 const uint8_t *sk_s2 = &sk_s1[(size_t) p->l * p->polyeta_packed_bytes];
383 const uint8_t *sk_t0 = &sk_s2[(size_t) p->k * p->polyeta_packed_bytes];
384
385 // Compute or import mu
386 if (precomputed_mu != NULL) {
387 memcpy(ws->mu, precomputed_mu, MLDSA_CRHBYTES);
388 }
389 else {
390 error = mldsa_compute_mu(ws->mu, ws->tr, formatted_mprime);
391 if (error != CX_OK) {
392 goto cleanup;
393 }
394 }
395
396 // Compute rhoprime = H(K || rnd || mu, 64)
397 {
398 cx_sha3_t sha3_ctx = {0};
399 error = cx_shake256_init_no_throw(&sha3_ctx, MLDSA_CRHBYTES * 8U);
400 if (error != CX_OK) {
401 goto cleanup;
402 }
403 error = cx_hash_no_throw((cx_hash_t *) &sha3_ctx, 0, ws->K, MLDSA_SEEDBYTES, NULL, 0);
404 if (error != CX_OK) {
405 goto cleanup;
406 }
407 error = cx_hash_no_throw((cx_hash_t *) &sha3_ctx, 0, rnd_input, MLDSA_RNDBYTES, NULL, 0);
408 if (error != CX_OK) {
409 goto cleanup;
410 }
411 error = cx_hash_no_throw(
412 (cx_hash_t *) &sha3_ctx, CX_LAST, ws->mu, MLDSA_CRHBYTES, ws->rhoprime, MLDSA_CRHBYTES);
413 if (error != CX_OK) {
414 goto cleanup;
415 }
416 }
417
418 // Rejection sampling loop
419 while (attempts < MLDSA_MAX_SIGN_ATTEMPTS) {
420 uint16_t kappa_base = kappa;
421 attempts++;
422
423 // w = A * NTT(y) streamed one column at a time.
424 // For each l: sample y[l], NTT it (t0 = yhat[l]), then accumulate
425 // A[k][l] * yhat[l] into every row w[k]. Only one yhat poly is alive.
426 for (uint8_t l = 0U; l < p->l; l++) {
427 MLDSA_SAMPLE_gamma1(&ws->t0, ws->rhoprime, (uint16_t) (kappa_base + l), p->gamma1);
428 MLDSA_POLY_ntt(&ws->t0);
429 for (uint8_t k = 0U; k < p->k; k++) {
430 uint16_t nonce = ((uint16_t) k << 8U) | (uint16_t) l;
431 MLDSA_SAMPLE_uniform(&ws->t1, ws->rho, nonce);
432 MLDSA_POLY_pointwise_montgomery(&ws->w.vec[k], &ws->t1, &ws->t0, (l == 0U) ? 1 : 0);
433 }
434 }
435 kappa = (uint16_t) (kappa_base + p->l);
436
437 MLDSA_POLYVEC_reduce_k(&ws->w, p->k);
439 MLDSA_POLYVEC_caddq_k(&ws->w, p->k);
440
441 // Pack w1 = HighBits(w) one row at a time.
442 for (uint8_t k = 0U; k < p->k; k++) {
443 MLDSA_ROUNDING_poly_decompose(&ws->t0, &ws->t1, &ws->w.vec[k], p->gamma2);
445 &ws->w1_packed[(size_t) k * p->polyw1_packed_bytes], &ws->t0, p->gamma2);
446 }
447
448 // Challenge hash ctilde = H(mu || w1_packed)
450 p->ctilde_bytes,
451 ws->mu,
453 ws->w1_packed,
454 (size_t) p->k * p->polyw1_packed_bytes);
455 if (error != CX_OK) {
456 goto cleanup;
457 }
458
460 MLDSA_POLY_ntt(&ws->cp);
461
462 // Compute z = y + INTT(c*s1) one row at a time; pack each z[l] into sig.
463 // y[l] is re-sampled (deterministic) instead of being stored.
464 {
465 uint8_t z_reject = 0U;
466 for (uint8_t l = 0U; l < p->l; l++) {
467 MLDSA_SAMPLE_gamma1(&ws->t0, ws->rhoprime, (uint16_t) (kappa_base + l), p->gamma1);
469 &ws->t1, &sk_s1[(size_t) l * p->polyeta_packed_bytes], p->eta);
470 MLDSA_POLY_ntt(&ws->t1);
471 MLDSA_POLY_pointwise_montgomery(&ws->t2, &ws->cp, &ws->t1, 1);
473 MLDSA_POLY_add(&ws->t0, &ws->t2);
474 MLDSA_POLY_reduce(&ws->t0);
475
476 if (MLDSA_POLY_chknorm(&ws->t0, p->gamma1 - (int32_t) p->beta)) {
477 z_reject = 1U;
478 break;
479 }
480
482 sig + p->ctilde_bytes + (size_t) l * p->polyz_packed_bytes, &ws->t0, p->gamma1);
483 }
484 if (z_reject != 0U) {
485 continue;
486 }
487 }
488
489 // For each k: r0 = LowBits(w) - INTT(c*s2); ct0 = INTT(c*t0);
490 // hint = MakeHint(ct0 + r0, HighBits(w)). Hints are written straight
491 // into the signature as a sorted index list.
492 {
493 uint8_t *sig_h = sig + p->ctilde_bytes + (size_t) p->l * p->polyz_packed_bytes;
494 uint32_t n_hints = 0U;
495 unsigned int hints_written = 0U;
496 uint8_t reject = 0U;
497
498 memset(sig_h, 0, p->polyvech_packed_bytes);
499
500 for (uint8_t k = 0U; k < p->k; k++) {
501 // t0 = HighBits(w[k]) = w1[k], t1 = LowBits(w[k]) = w0[k]
502 MLDSA_ROUNDING_poly_decompose(&ws->t0, &ws->t1, &ws->w.vec[k], p->gamma2);
503
504 // r0 = w0 - INTT(c*s2[k]) (stored in t1)
506 &ws->t2, &sk_s2[(size_t) k * p->polyeta_packed_bytes], p->eta);
507 MLDSA_POLY_ntt(&ws->t2);
508 MLDSA_POLY_pointwise_montgomery(&ws->t2, &ws->cp, &ws->t2, 1);
510 MLDSA_POLY_sub(&ws->t1, &ws->t2);
511 MLDSA_POLY_reduce(&ws->t1);
512
513 // Check ||r0||_inf < gamma2 - beta
514 if (MLDSA_POLY_chknorm(&ws->t1, p->gamma2 - (int32_t) p->beta)) {
515 reject = 1U;
516 break;
517 }
518
519 // ct0 = INTT(c*t0[k]) (stored in t2)
520 MLDSA_PACK_unpack_polyt0(&ws->t2, &sk_t0[(size_t) k * MLDSA_POLYT0_PACKEDBYTES]);
521 MLDSA_POLY_ntt(&ws->t2);
522 MLDSA_POLY_pointwise_montgomery(&ws->t2, &ws->cp, &ws->t2, 1);
524 MLDSA_POLY_reduce(&ws->t2);
525
526 // Check ||ct0||_inf < gamma2
527 if (MLDSA_POLY_chknorm(&ws->t2, p->gamma2)) {
528 reject = 1U;
529 break;
530 }
531
532 // t2 = ct0 + r0
533 MLDSA_POLY_add(&ws->t2, &ws->t1);
534
535 // make_hint per coefficient against w1 (t0)
536 for (uint32_t j = 0U; j < MLDSA_N; j++) {
537 uint32_t hbit
538 = MLDSA_ROUNDING_make_hint(ws->t2.coeffs[j], ws->t0.coeffs[j], p->gamma2);
539 if (hbit != 0U) {
540 if (hints_written >= p->omega) {
541 reject = 1U;
542 break;
543 }
544 sig_h[hints_written] = (uint8_t) j;
545 hints_written++;
546 }
547 n_hints += hbit;
548 }
549 if (reject != 0U) {
550 break;
551 }
552 sig_h[p->omega + k] = (uint8_t) hints_written;
553 }
554
555 if ((reject != 0U) || (n_hints > p->omega)) {
556 continue;
557 }
558 }
559
560 // Success: c_tilde completes the signature (z and h already packed).
561 memcpy(sig, ws->ctilde, p->ctilde_bytes);
562 *sig_actual_len = p->sig_bytes;
563 error = CX_OK;
564 goto cleanup;
565 }
566
567 // Exhausted attempts
568 error = CX_INTERNAL_ERROR;
569
570cleanup:
571 explicit_bzero(zero_rnd, sizeof(zero_rnd));
572 explicit_bzero(ws, sizeof(*ws));
573
574 return error;
575}
576
594cx_err_t MLDSA_internal_verify_core(const uint8_t *sig,
595 size_t sig_len,
596 const MLDSA_formatted_message_t *formatted_mprime,
597 const uint8_t *precomputed_mu,
598 const uint8_t *pk,
599 size_t pk_len,
600 MLDSA_param_t param)
601{
602 MLDSA_verify_stack_workspace_t ws_local = {0};
603 MLDSA_verify_stack_workspace_t *ws = &ws_local;
604 const MLDSA_param_info_t *p = NULL;
605 const uint8_t *sig_z = NULL;
606 const uint8_t *sig_h = NULL;
607 uint32_t k_offset = 0U;
608 cx_err_t error = CX_INTERNAL_ERROR;
609
610 if ((sig == NULL) || (pk == NULL)) {
611 error = CX_INVALID_PARAMETER;
612 goto cleanup;
613 }
614 if ((formatted_mprime == NULL) && (precomputed_mu == NULL)) {
615 error = CX_INVALID_PARAMETER;
616 goto cleanup;
617 }
618
619 if (param >= MLDSA_NUM_PARAM_SETS) {
620 error = CX_INVALID_PARAMETER_VALUE;
621 goto cleanup;
622 }
623
624 p = &MLDSA_PARAM[param];
625
626 if (pk_len < p->pk_bytes) {
627 error = CX_INVALID_PARAMETER_SIZE;
628 goto cleanup;
629 }
630 if (sig_len < p->sig_bytes) {
631 error = CX_INVALID_PARAMETER_SIZE;
632 goto cleanup;
633 }
634
635 explicit_bzero(ws, sizeof(*ws));
636
637 memcpy(ws->rho, pk, MLDSA_SEEDBYTES);
638 memcpy(ws->ctilde, sig, p->ctilde_bytes);
639
640 sig_z = &sig[p->ctilde_bytes];
641 sig_h = &sig_z[(size_t) p->l * p->polyz_packed_bytes];
642
643 // Validate hint encoding without materializing the full h vector.
644 for (uint32_t i = 0U; i < p->k; i++) {
645 uint32_t limit = (uint32_t) sig_h[p->omega + i];
646 if ((limit < k_offset) || (limit > p->omega)) {
647 error = CX_INVALID_PARAMETER;
648 goto cleanup;
649 }
650
651 for (uint32_t j = k_offset; j < limit; j++) {
652 if ((j > k_offset) && (sig_h[j] <= sig_h[j - 1U])) {
653 error = CX_INVALID_PARAMETER;
654 goto cleanup;
655 }
656 }
657 k_offset = limit;
658 }
659
660 for (uint32_t j = k_offset; j < p->omega; j++) {
661 if (sig_h[j] != 0U) {
662 error = CX_INVALID_PARAMETER;
663 goto cleanup;
664 }
665 }
666
667 // Stream z and check ||z||_inf < gamma1 - beta.
668 for (uint8_t j = 0U; j < p->l; j++) {
669 MLDSA_PACK_unpack_polyz(&ws->ztmp, &sig_z[(size_t) j * p->polyz_packed_bytes], p->gamma1);
670 if (MLDSA_POLY_chknorm(&ws->ztmp, p->gamma1 - (int32_t) p->beta)) {
671 error = CX_INVALID_PARAMETER;
672 goto cleanup;
673 }
674 }
675
676 // Compute or import mu
677 if (precomputed_mu != NULL) {
678 memcpy(ws->mu, precomputed_mu, MLDSA_CRHBYTES);
679 }
680 else {
682 error = mldsa_compute_mu(ws->mu, ws->overlay.setup_phase.tr, formatted_mprime);
683 if (error != CX_OK) {
684 goto cleanup;
685 }
686 }
687
688 // Sample challenge c from ctilde
690 MLDSA_POLY_ntt(&ws->cp);
691
692 // Fused streaming verify: materialize one z, one t1 and one hint row at a time.
693 k_offset = 0U;
694 for (uint8_t i = 0U; i < p->k; i++) {
695 uint32_t limit = (uint32_t) sig_h[p->omega + i];
696
697 for (uint8_t j = 0U; j < p->l; j++) {
698 uint16_t nonce = ((uint16_t) i << 8U) | (uint16_t) j;
700 &ws->ztmp, &sig_z[(size_t) j * p->polyz_packed_bytes], p->gamma1);
701 MLDSA_POLY_ntt(&ws->ztmp);
702 MLDSA_SAMPLE_uniform(&ws->overlay.az_phase.aij, ws->rho, nonce);
704 &ws->overlay.az_phase.dot, &ws->overlay.az_phase.aij, &ws->ztmp, (j == 0U) ? 1 : 0);
705 }
706
708 &pk[MLDSA_SEEDBYTES + (size_t) i * MLDSA_POLYT1_PACKEDBYTES]);
710 MLDSA_POLY_ntt(&ws->t1tmp);
711 MLDSA_POLY_pointwise_montgomery(&ws->t1tmp, &ws->cp, &ws->t1tmp, 1);
712
717
718 explicit_bzero(&ws->htmp, sizeof(ws->htmp));
719 for (uint32_t j = k_offset; j < limit; j++) {
720 ws->htmp.coeffs[sig_h[j]] = 1;
721 }
722 k_offset = limit;
723
725
727 &ws->w1_packed[(size_t) i * p->polyw1_packed_bytes], &ws->t1tmp, p->gamma2);
728 }
729
731 p->ctilde_bytes,
732 ws->mu,
734 ws->w1_packed,
735 (size_t) p->k * p->polyw1_packed_bytes);
736 if (error != CX_OK) {
737 goto cleanup;
738 }
739
740 // Compare c_tilde
741 if (memcmp(ws->ctilde, ws->ctilde2, p->ctilde_bytes) != 0) {
742 error = CX_INVALID_PARAMETER;
743 goto cleanup;
744 }
745
746 error = CX_OK;
747
748cleanup:
749 explicit_bzero(ws, sizeof(*ws));
750 return error;
751}
752
753#else /* HAVE_MLDSA_OPTIMIZATION */
754
755/*===========================================================================
756 * OPTIMIZED LOW-RAM IMPLEMENTATION
757 *
758 * Key techniques applied:
759 * 1. smallpoly (int16_t) for c*s1, c*s2 via small NTT mod 3329
760 * 2. Schoolbook c*t0 / c*t1 directly from packed secret/public key
761 * 3. Compressed challenge (68 bytes instead of 1024 byte poly)
762 * 4. Compressed w buffers (768 bytes/row instead of 1024 bytes)
763 * 5. Fused A expansion (streaming 3 bytes at a time into compressed w)
764 * 6. Streaming gamma1 sampling (5-9 byte buffer)
765 * 7. Hint index list in verify (max 80 bytes vs 1024 byte poly)
766 * 8. Eliminated full y/yhat vectors
767 *===========================================================================*/
768
772typedef struct MLDSA_sign_opt_workspace_s {
773 uint8_t rho[MLDSA_SEEDBYTES];
774 uint8_t K[MLDSA_SEEDBYTES];
775 uint8_t tr[MLDSA_TRBYTES];
776 uint8_t mu[MLDSA_CRHBYTES];
777 uint8_t rhoprime[MLDSA_CRHBYTES];
778 uint8_t ctilde[64U];
779 uint8_t ccomp[MLDSA_CCOMP_BYTES];
780 uint8_t wcomp[MLDSA_MAX_K][MLDSA_WCOMP_BYTES];
781 union {
782 mldsa_poly full;
783 struct {
784 mldsa_smallpoly scp;
785 mldsa_smallpoly stmp;
786 } small;
787 } polybuf;
788 uint8_t w1_packed[MLDSA_MAX_K * 192U];
789} MLDSA_sign_opt_workspace_t;
790
794cx_err_t MLDSA_internal_sign_core(uint8_t *sig,
795 size_t sig_len,
796 size_t *sig_actual_len,
797 const MLDSA_formatted_message_t *formatted_mprime,
798 const uint8_t *precomputed_mu,
799 uint8_t *rnd,
800 size_t rnd_len,
801 const uint8_t *sk,
802 size_t sk_len,
803 MLDSA_param_t param)
804{
805 MLDSA_sign_opt_workspace_t ws_local = {0};
806 MLDSA_sign_opt_workspace_t *ws = &ws_local;
807 const MLDSA_param_info_t *p = NULL;
808 const uint8_t *rnd_input = rnd;
809 uint8_t zero_rnd[MLDSA_RNDBYTES] = {0};
810 cx_err_t error = CX_INTERNAL_ERROR;
811 uint16_t kappa = 0U;
812 uint32_t attempts = 0U;
813
814 if ((sig == NULL) || (sk == NULL) || (sig_actual_len == NULL)) {
815 error = CX_INVALID_PARAMETER;
816 goto cleanup;
817 }
818 if ((formatted_mprime == NULL) && (precomputed_mu == NULL)) {
819 error = CX_INVALID_PARAMETER;
820 goto cleanup;
821 }
822 if ((rnd != NULL) && (rnd_len != MLDSA_RNDBYTES)) {
823 error = CX_INVALID_PARAMETER;
824 goto cleanup;
825 }
826 if ((rnd == NULL) && (rnd_len != 0U)) {
827 error = CX_INVALID_PARAMETER;
828 goto cleanup;
829 }
830 if (param >= MLDSA_NUM_PARAM_SETS) {
831 error = CX_INVALID_PARAMETER_VALUE;
832 goto cleanup;
833 }
834
835 p = &MLDSA_PARAM[param];
836
837 if (sk_len < p->sk_bytes) {
838 error = CX_INVALID_PARAMETER_SIZE;
839 goto cleanup;
840 }
841 if (sig_len < p->sig_bytes) {
842 error = CX_INVALID_PARAMETER_SIZE;
843 goto cleanup;
844 }
845
846 if (rnd_input == NULL) {
847 rnd_input = zero_rnd;
848 }
849
850 explicit_bzero(ws, sizeof(*ws));
851
852 // Unpack secret-key header
853 memcpy(ws->rho, sk, MLDSA_SEEDBYTES);
854 memcpy(ws->K, &sk[MLDSA_SEEDBYTES], MLDSA_SEEDBYTES);
855 memcpy(ws->tr, &sk[2U * MLDSA_SEEDBYTES], MLDSA_TRBYTES);
856
857 const uint8_t *sk_s1 = &sk[2U * MLDSA_SEEDBYTES + MLDSA_TRBYTES];
858 const uint8_t *sk_s2 = &sk_s1[(size_t) p->l * p->polyeta_packed_bytes];
859 const uint8_t *sk_t0 = &sk_s2[(size_t) p->k * p->polyeta_packed_bytes];
860
861 // Compute or import mu
862 if (precomputed_mu != NULL) {
863 memcpy(ws->mu, precomputed_mu, MLDSA_CRHBYTES);
864 }
865 else {
866 error = mldsa_compute_mu(ws->mu, ws->tr, formatted_mprime);
867 if (error != CX_OK) {
868 goto cleanup;
869 }
870 }
871
872 // Compute rhoprime = H(K || rnd || mu, 64)
873 {
874 cx_sha3_t sha3_ctx = {0};
875 error = cx_shake256_init_no_throw(&sha3_ctx, MLDSA_CRHBYTES * 8U);
876 if (error != CX_OK) {
877 goto cleanup;
878 }
879 error = cx_hash_no_throw((cx_hash_t *) &sha3_ctx, 0, ws->K, MLDSA_SEEDBYTES, NULL, 0);
880 if (error != CX_OK) {
881 goto cleanup;
882 }
883 error = cx_hash_no_throw((cx_hash_t *) &sha3_ctx, 0, rnd_input, MLDSA_RNDBYTES, NULL, 0);
884 if (error != CX_OK) {
885 goto cleanup;
886 }
887 error = cx_hash_no_throw(
888 (cx_hash_t *) &sha3_ctx, CX_LAST, ws->mu, MLDSA_CRHBYTES, ws->rhoprime, MLDSA_CRHBYTES);
889 if (error != CX_OK) {
890 goto cleanup;
891 }
892 }
893
894 // Rejection sampling loop
895 while (attempts < MLDSA_MAX_SIGN_ATTEMPTS) {
896 attempts++;
897
898 // Zero compressed w buffers
899 for (uint8_t k_idx = 0U; k_idx < p->k; k_idx++) {
900 memset(ws->wcomp[k_idx], 0, MLDSA_WCOMP_BYTES);
901 }
902
903 // For each y polynomial: sample, NTT, fuse A expansion into wcomp
904 for (uint8_t l_idx = 0U; l_idx < p->l; l_idx++) {
905 MLDSA_LOWRAM_sample_gamma1(
906 &ws->polybuf.full, ws->rhoprime, (uint16_t) (kappa + l_idx), p->gamma1);
907 MLDSA_POLY_ntt(&ws->polybuf.full);
908
909 for (uint8_t k_idx = 0U; k_idx < p->k; k_idx++) {
910 uint16_t nonce = ((uint16_t) k_idx << 8U) | (uint16_t) l_idx;
911 MLDSA_LOWRAM_expand_aij_accum(ws->wcomp[k_idx], &ws->polybuf.full, ws->rho, nonce);
912 }
913 }
914 kappa += (uint16_t) p->l;
915
916 // For each row: unpack wcomp, INTT, repack as reduced, compute highbits, pack w1
917 for (uint8_t k_idx = 0U; k_idx < p->k; k_idx++) {
918 MLDSA_LOWRAM_polyw_unpack(&ws->polybuf.full, ws->wcomp[k_idx]);
919 MLDSA_POLY_invntt_tomont(&ws->polybuf.full);
920 MLDSA_POLY_caddq_all(&ws->polybuf.full);
921 MLDSA_LOWRAM_polyw_pack(ws->wcomp[k_idx], &ws->polybuf.full);
922 MLDSA_LOWRAM_poly_highbits(&ws->polybuf.full, &ws->polybuf.full, p->gamma2);
923 MLDSA_PACK_polyw1(&ws->w1_packed[(size_t) k_idx * p->polyw1_packed_bytes],
924 &ws->polybuf.full,
925 p->gamma2);
926 }
927
928 // Compute challenge hash ctilde
929 error = MLDSA_UTIL_shake256_two(ws->ctilde,
930 p->ctilde_bytes,
931 ws->mu,
933 ws->w1_packed,
934 (size_t) p->k * p->polyw1_packed_bytes);
935 if (error != CX_OK) {
936 goto cleanup;
937 }
938
939 MLDSA_SAMPLE_challenge(&ws->polybuf.full, ws->ctilde, p->ctilde_bytes, p->tau);
940 MLDSA_LOWRAM_challenge_compress(ws->ccomp, &ws->polybuf.full, p->tau);
941
942 // Convert challenge to small NTT form for c*s multiplications
943 MLDSA_SMALLPOLY_ntt_copy(&ws->polybuf.small.scp, &ws->polybuf.full);
944
945 // Compute z = y + c*s1 for each l, check norm, and pack into sig
946 {
947 uint8_t z_reject = 0U;
948 for (uint8_t l_idx = 0U; l_idx < p->l; l_idx++) {
949 // Need challenge in small NTT form: re-decompress
950 if (l_idx != 0U) {
951 MLDSA_LOWRAM_challenge_decompress(&ws->polybuf.full, ws->ccomp, p->tau);
952 MLDSA_SMALLPOLY_ntt_copy(&ws->polybuf.small.scp, &ws->polybuf.full);
953 }
954
955 // Unpack s1[l_idx] into small poly
956 MLDSA_SMALLPOLY_unpack_eta(&ws->polybuf.small.stmp,
957 &sk_s1[(size_t) l_idx * p->polyeta_packed_bytes],
958 p->eta);
959 MLDSA_SMALLPOLY_ntt(ws->polybuf.small.stmp.coeffs);
960
961 // Compute c*s1[l_idx] via small basemul + INTT (full poly)
962 MLDSA_SMALLPOLY_basemul_invntt(
963 &ws->polybuf.full, &ws->polybuf.small.scp, &ws->polybuf.small.stmp);
964
965 // z[l_idx] = y[l_idx] + c*s1[l_idx] (re-sample y and add)
966 MLDSA_LOWRAM_sample_gamma1_add(&ws->polybuf.full,
967 &ws->polybuf.full,
968 ws->rhoprime,
969 (uint16_t) (kappa - (uint16_t) p->l + l_idx),
970 p->gamma1);
971 MLDSA_POLY_reduce(&ws->polybuf.full);
972
973 // Check ||z||_inf < gamma1 - beta
974 if (MLDSA_POLY_chknorm(&ws->polybuf.full, p->gamma1 - (int32_t) p->beta)) {
975 z_reject = 1U;
976 break;
977 }
978
979 // Pack z into signature
980 MLDSA_PACK_polyz(sig + p->ctilde_bytes + (size_t) l_idx * p->polyz_packed_bytes,
981 &ws->polybuf.full,
982 p->gamma1);
983 }
984 if (z_reject != 0U) {
985 continue;
986 }
987 }
988
989 // Compute r0 = w - c*s2, check norm
990 {
991 uint8_t r0_reject = 0U;
992 for (uint8_t k_idx = 0U; k_idx < p->k; k_idx++) {
993 // Re-decompress challenge and prepare small NTT
994 MLDSA_LOWRAM_challenge_decompress(&ws->polybuf.full, ws->ccomp, p->tau);
995 MLDSA_SMALLPOLY_ntt_copy(&ws->polybuf.small.scp, &ws->polybuf.full);
996
997 // Unpack s2[k_idx] into small poly
998 MLDSA_SMALLPOLY_unpack_eta(&ws->polybuf.small.stmp,
999 &sk_s2[(size_t) k_idx * p->polyeta_packed_bytes],
1000 p->eta);
1001 MLDSA_SMALLPOLY_ntt(ws->polybuf.small.stmp.coeffs);
1002
1003 // c*s2[k_idx] via small basemul + INTT
1004 MLDSA_SMALLPOLY_basemul_invntt(
1005 &ws->polybuf.full, &ws->polybuf.small.scp, &ws->polybuf.small.stmp);
1006
1007 // r0 = wcomp - c*s2: subtract from compressed w
1008 MLDSA_LOWRAM_polyw_sub(&ws->polybuf.full, ws->wcomp[k_idx], &ws->polybuf.full);
1009 MLDSA_POLY_reduce(&ws->polybuf.full);
1010
1011 // Store back into wcomp for hint computation
1012 MLDSA_LOWRAM_polyw_pack(ws->wcomp[k_idx], &ws->polybuf.full);
1013
1014 // r0 = LowBits(w) - c*s2: reconstruct
1015 // from the stored (w - c*s2) and w1 = HighBits(w).
1016 MLDSA_LOWRAM_poly_r0(&ws->polybuf.full,
1017 ws->wcomp[k_idx],
1018 &ws->w1_packed[(size_t) k_idx * p->polyw1_packed_bytes],
1019 p->gamma2);
1020
1021 if (MLDSA_POLY_chknorm(&ws->polybuf.full, p->gamma2 - (int32_t) p->beta)) {
1022 r0_reject = 1U;
1023 break;
1024 }
1025 }
1026 if (r0_reject != 0U) {
1027 continue;
1028 }
1029 }
1030
1031 // Fused ct0 computation, norm check, and hint generation
1032 {
1033 uint32_t n_hints = 0U;
1034 uint8_t reject = 0U;
1035 unsigned int hints_written = 0U;
1036
1037 // Zero hint portion of signature
1038 memset(sig + p->ctilde_bytes + (size_t) p->l * p->polyz_packed_bytes,
1039 0,
1041
1042 for (uint8_t k_idx = 0U; k_idx < p->k; k_idx++) {
1043 MLDSA_LOWRAM_schoolbook_t0(&ws->polybuf.full,
1044 ws->ccomp,
1045 &sk_t0[(size_t) k_idx * MLDSA_POLYT0_PACKEDBYTES],
1046 p->tau);
1047 MLDSA_POLY_reduce(&ws->polybuf.full);
1048
1049 // Check ||ct0||_inf < gamma2
1050 if (MLDSA_POLY_chknorm(&ws->polybuf.full, p->gamma2)) {
1051 reject = 1U;
1052 break;
1053 }
1054
1055 // make_hint from ct0 and (w - cs2) stored in wcomp
1056 uint32_t row_hints = MLDSA_LOWRAM_make_hint(
1057 &ws->polybuf.full,
1058 &ws->polybuf.full,
1059 ws->wcomp[k_idx],
1060 &ws->w1_packed[(size_t) k_idx * p->polyw1_packed_bytes],
1061 p->gamma2);
1062 n_hints += row_hints;
1063
1064 if (n_hints > p->omega) {
1065 reject = 1U;
1066 break;
1067 }
1068
1069 // Pack hint into signature
1070 {
1071 uint8_t *sig_h = sig + p->ctilde_bytes + (size_t) p->l * p->polyz_packed_bytes;
1072 for (uint32_t j = 0U; j < MLDSA_N; j++) {
1073 if (ws->polybuf.full.coeffs[j] != 0) {
1074 sig_h[hints_written] = (uint8_t) j;
1075 hints_written++;
1076 }
1077 }
1078 sig_h[p->omega + k_idx] = (uint8_t) hints_written;
1079 }
1080 }
1081
1082 if (reject != 0U) {
1083 continue;
1084 }
1085
1086 // Pad remaining hint bytes with zeros
1087 {
1088 uint8_t *sig_h = sig + p->ctilde_bytes + (size_t) p->l * p->polyz_packed_bytes;
1089 while (hints_written < p->omega) {
1090 sig_h[hints_written] = 0U;
1091 hints_written++;
1092 }
1093 }
1094 }
1095
1096 // Success: pack ctilde into signature
1097 memcpy(sig, ws->ctilde, p->ctilde_bytes);
1098 *sig_actual_len = p->sig_bytes;
1099 error = CX_OK;
1100 goto cleanup;
1101 }
1102
1103 // Exhausted attempts
1104 error = CX_INTERNAL_ERROR;
1105
1106cleanup:
1107 explicit_bzero(zero_rnd, sizeof(zero_rnd));
1108 explicit_bzero(ws, sizeof(*ws));
1109 return error;
1110}
1111
1115typedef struct MLDSA_verify_opt_workspace_s {
1116 uint8_t rho[MLDSA_SEEDBYTES];
1117 uint8_t ctilde[64U];
1118 uint8_t mu[MLDSA_CRHBYTES];
1119 uint8_t ccomp[MLDSA_CCOMP_BYTES];
1120 uint8_t wcomp[MLDSA_WCOMP_BYTES];
1121 uint8_t w1_packed[MLDSA_MAX_K * 192U];
1122 uint8_t ctilde2[64U];
1123 uint8_t h_indices[MLDSA_MAX_OMEGA];
1124 mldsa_poly tmp;
1125 mldsa_poly ct1;
1126 union {
1127 uint8_t tr[MLDSA_TRBYTES];
1128 } early;
1129} MLDSA_verify_opt_workspace_t;
1130
1134cx_err_t MLDSA_internal_verify_core(const uint8_t *sig,
1135 size_t sig_len,
1136 const MLDSA_formatted_message_t *formatted_mprime,
1137 const uint8_t *precomputed_mu,
1138 const uint8_t *pk,
1139 size_t pk_len,
1140 MLDSA_param_t param)
1141{
1142 MLDSA_verify_opt_workspace_t ws_local = {0};
1143 MLDSA_verify_opt_workspace_t *ws = &ws_local;
1144 const MLDSA_param_info_t *p = NULL;
1145 const uint8_t *sig_z = NULL;
1146 const uint8_t *sig_h = NULL;
1147 uint32_t k_offset = 0U;
1148 cx_err_t error = CX_INTERNAL_ERROR;
1149
1150 if ((sig == NULL) || (pk == NULL)) {
1151 error = CX_INVALID_PARAMETER;
1152 goto cleanup;
1153 }
1154 if ((formatted_mprime == NULL) && (precomputed_mu == NULL)) {
1155 error = CX_INVALID_PARAMETER;
1156 goto cleanup;
1157 }
1158 if (param >= MLDSA_NUM_PARAM_SETS) {
1159 error = CX_INVALID_PARAMETER_VALUE;
1160 goto cleanup;
1161 }
1162
1163 p = &MLDSA_PARAM[param];
1164
1165 if (pk_len < p->pk_bytes) {
1166 error = CX_INVALID_PARAMETER_SIZE;
1167 goto cleanup;
1168 }
1169 if (sig_len < p->sig_bytes) {
1170 error = CX_INVALID_PARAMETER_SIZE;
1171 goto cleanup;
1172 }
1173
1174 explicit_bzero(ws, sizeof(*ws));
1175
1176 memcpy(ws->rho, pk, MLDSA_SEEDBYTES);
1177 memcpy(ws->ctilde, sig, p->ctilde_bytes);
1178
1179 sig_z = &sig[p->ctilde_bytes];
1180 sig_h = &sig_z[(size_t) p->l * p->polyz_packed_bytes];
1181
1182 // Validate hint encoding
1183 for (uint32_t i = 0U; i < p->k; i++) {
1184 uint32_t limit = (uint32_t) sig_h[p->omega + i];
1185 if ((limit < k_offset) || (limit > p->omega)) {
1186 error = CX_INVALID_PARAMETER;
1187 goto cleanup;
1188 }
1189 for (uint32_t j = k_offset; j < limit; j++) {
1190 if ((j > k_offset) && (sig_h[j] <= sig_h[j - 1U])) {
1191 error = CX_INVALID_PARAMETER;
1192 goto cleanup;
1193 }
1194 }
1195 k_offset = limit;
1196 }
1197 for (uint32_t j = k_offset; j < p->omega; j++) {
1198 if (sig_h[j] != 0U) {
1199 error = CX_INVALID_PARAMETER;
1200 goto cleanup;
1201 }
1202 }
1203
1204 // Stream z and check ||z||_inf < gamma1 - beta
1205 for (uint8_t j = 0U; j < p->l; j++) {
1206 MLDSA_PACK_unpack_polyz(&ws->tmp, &sig_z[(size_t) j * p->polyz_packed_bytes], p->gamma1);
1207 if (MLDSA_POLY_chknorm(&ws->tmp, p->gamma1 - (int32_t) p->beta)) {
1208 error = CX_INVALID_PARAMETER;
1209 goto cleanup;
1210 }
1211 }
1212
1213 // Compute or import mu
1214 if (precomputed_mu != NULL) {
1215 memcpy(ws->mu, precomputed_mu, MLDSA_CRHBYTES);
1216 }
1217 else {
1218 MLDSA_UTIL_shake256(ws->early.tr, MLDSA_TRBYTES, pk, p->pk_bytes);
1219 error = mldsa_compute_mu(ws->mu, ws->early.tr, formatted_mprime);
1220 if (error != CX_OK) {
1221 goto cleanup;
1222 }
1223 }
1224
1225 // Compress challenge for schoolbook use
1226 MLDSA_SAMPLE_challenge(&ws->tmp, ws->ctilde, p->ctilde_bytes, p->tau);
1227 MLDSA_LOWRAM_challenge_compress(ws->ccomp, &ws->tmp, p->tau);
1228
1229 // Fused streaming verify: one row at a time
1230 k_offset = 0U;
1231 for (uint8_t i = 0U; i < p->k; i++) {
1232 uint32_t limit = (uint32_t) sig_h[p->omega + i];
1233
1234 // Compute Az[i] = sum_j A[i][j] * NTT(z[j]) using fused expansion
1235 memset(ws->wcomp, 0, MLDSA_WCOMP_BYTES);
1236 for (uint8_t j = 0U; j < p->l; j++) {
1237 uint16_t nonce = ((uint16_t) i << 8U) | (uint16_t) j;
1239 &ws->tmp, &sig_z[(size_t) j * p->polyz_packed_bytes], p->gamma1);
1240 MLDSA_POLY_ntt(&ws->tmp);
1241 MLDSA_LOWRAM_expand_aij_accum(ws->wcomp, &ws->tmp, ws->rho, nonce);
1242 }
1243
1244 // Unpack Az, INTT
1245 MLDSA_LOWRAM_polyw_unpack(&ws->tmp, ws->wcomp);
1246 MLDSA_POLY_reduce(&ws->tmp);
1247 MLDSA_POLY_invntt_tomont(&ws->tmp);
1248
1249 // Subtract c*t1*2^d using schoolbook from packed pk
1250 MLDSA_LOWRAM_schoolbook_t1(&ws->ct1,
1251 ws->ccomp,
1252 &pk[MLDSA_SEEDBYTES + (size_t) i * MLDSA_POLYT1_PACKEDBYTES],
1253 p->tau);
1254 MLDSA_POLY_sub(&ws->tmp, &ws->ct1);
1255 MLDSA_POLY_reduce(&ws->tmp);
1256 MLDSA_POLY_caddq_all(&ws->tmp);
1257
1258 // Extract hint indices for this row
1259 uint32_t num_hints = limit - k_offset;
1260 for (uint32_t j = 0U; j < num_hints; j++) {
1261 ws->h_indices[j] = sig_h[k_offset + j];
1262 }
1263 k_offset = limit;
1264
1265 // Use hint (index-list form) to reconstruct w1'
1266 MLDSA_LOWRAM_use_hint_indices(&ws->tmp, &ws->tmp, ws->h_indices, num_hints, p->gamma2);
1267
1268 // Pack w1 row
1269 MLDSA_PACK_polyw1(&ws->w1_packed[(size_t) i * p->polyw1_packed_bytes], &ws->tmp, p->gamma2);
1270 }
1271
1272 // Recompute challenge hash
1273 error = MLDSA_UTIL_shake256_two(ws->ctilde2,
1274 p->ctilde_bytes,
1275 ws->mu,
1277 ws->w1_packed,
1278 (size_t) p->k * p->polyw1_packed_bytes);
1279 if (error != CX_OK) {
1280 goto cleanup;
1281 }
1282
1283 // Compare c_tilde
1284 if (memcmp(ws->ctilde, ws->ctilde2, p->ctilde_bytes) != 0) {
1285 error = CX_INVALID_PARAMETER;
1286 goto cleanup;
1287 }
1288
1289 error = CX_OK;
1290
1291cleanup:
1292 explicit_bzero(ws, sizeof(*ws));
1293 return error;
1294}
1295
1296#endif /* HAVE_MLDSA_OPTIMIZATION */
1297
1298/*********************
1299 * GLOBAL FUNCTIONS
1300 *********************/
1301
1302/*---------------------------------------------------------------------------
1303 * KeyGen (FIPS 204, Algorithm 1)
1304 *---------------------------------------------------------------------------*/
1305cx_err_t MLDSA_keygen(uint8_t *pk, size_t pk_len, uint8_t *sk, size_t sk_len, MLDSA_param_t param)
1306{
1307 cx_err_t error = CX_INTERNAL_ERROR;
1308 uint8_t seed[MLDSA_SEEDBYTES] = {0};
1309
1310 if ((pk == NULL) || (sk == NULL)) {
1311 error = CX_INVALID_PARAMETER;
1312 goto cleanup;
1313 }
1314
1315 if (param >= MLDSA_NUM_PARAM_SETS) {
1316 error = CX_INVALID_PARAMETER_VALUE;
1317 goto cleanup;
1318 }
1319
1320 cx_rng_no_throw(seed, MLDSA_SEEDBYTES);
1321 error = MLDSA_internal_keygen(pk, pk_len, sk, sk_len, seed, param);
1322
1323cleanup:
1324 explicit_bzero(seed, sizeof(seed));
1325 return error;
1326}
1327
1328/*---------------------------------------------------------------------------
1329 * Sign (FIPS 204, Algorithms 2 & 7)
1330 *---------------------------------------------------------------------------*/
1331cx_err_t MLDSA_sign(uint8_t *sig,
1332 size_t sig_len,
1333 size_t *sig_actual_len,
1334 const uint8_t *msg,
1335 size_t msg_len,
1336 const uint8_t *ctx,
1337 size_t ctx_len,
1338 const uint8_t *sk,
1339 size_t sk_len,
1340 MLDSA_param_t param)
1341{
1342 MLDSA_formatted_message_t mprime = {0};
1343 uint8_t rnd[MLDSA_RNDBYTES] = {0};
1344 cx_err_t error = mldsa_format_message_pure(&mprime, ctx, ctx_len, msg, msg_len);
1345
1346 if (error != CX_OK) {
1347 goto cleanup;
1348 }
1349
1350 cx_rng_no_throw(rnd, MLDSA_RNDBYTES);
1352 sig, sig_len, sig_actual_len, &mprime, NULL, rnd, sizeof(rnd), sk, sk_len, param);
1353
1354cleanup:
1355 explicit_bzero(rnd, sizeof(rnd));
1356 return error;
1357}
1358
1359/*---------------------------------------------------------------------------
1360 * Verify (FIPS 204, Algorithms 3 & 8)
1361 *---------------------------------------------------------------------------*/
1362cx_err_t MLDSA_verify(const uint8_t *sig,
1363 size_t sig_len,
1364 const uint8_t *msg,
1365 size_t msg_len,
1366 const uint8_t *ctx,
1367 size_t ctx_len,
1368 const uint8_t *pk,
1369 size_t pk_len,
1370 MLDSA_param_t param)
1371{
1372 MLDSA_formatted_message_t mprime = {0};
1373 cx_err_t error = mldsa_format_message_pure(&mprime, ctx, ctx_len, msg, msg_len);
1374
1375 if (error != CX_OK) {
1376 return error;
1377 }
1378
1379 return MLDSA_internal_verify_core(sig, sig_len, &mprime, NULL, pk, pk_len, param);
1380}
1381
1382/*---------------------------------------------------------------------------
1383 * HashML-DSA Sign (FIPS 204, Algorithm 4)
1384 *---------------------------------------------------------------------------*/
1385cx_err_t MLDSA_sign_prehash(uint8_t *sig,
1386 size_t sig_len,
1387 size_t *sig_actual_len,
1388 const uint8_t *ph,
1389 size_t ph_len,
1390 const uint8_t *ctx,
1391 size_t ctx_len,
1392 const uint8_t *sk,
1393 size_t sk_len,
1394 MLDSA_prehash_t prehash_alg,
1395 MLDSA_param_t param)
1396{
1397 MLDSA_formatted_message_t mprime = {0};
1398 uint8_t rnd[MLDSA_RNDBYTES] = {0};
1399 cx_err_t error = mldsa_format_message_prehash(&mprime, ctx, ctx_len, prehash_alg, ph, ph_len);
1400
1401 if (error != CX_OK) {
1402 goto cleanup;
1403 }
1404
1405 cx_rng_no_throw(rnd, MLDSA_RNDBYTES);
1407 sig, sig_len, sig_actual_len, &mprime, NULL, rnd, sizeof(rnd), sk, sk_len, param);
1408
1409cleanup:
1410 explicit_bzero(rnd, sizeof(rnd));
1411 return error;
1412}
1413
1414/*---------------------------------------------------------------------------
1415 * HashML-DSA Verify (FIPS 204, Algorithm 5)
1416 *---------------------------------------------------------------------------*/
1417cx_err_t MLDSA_verify_prehash(const uint8_t *sig,
1418 size_t sig_len,
1419 const uint8_t *ph,
1420 size_t ph_len,
1421 const uint8_t *ctx,
1422 size_t ctx_len,
1423 const uint8_t *pk,
1424 size_t pk_len,
1425 MLDSA_prehash_t prehash_alg,
1426 MLDSA_param_t param)
1427{
1428 MLDSA_formatted_message_t mprime = {0};
1429 cx_err_t error = mldsa_format_message_prehash(&mprime, ctx, ctx_len, prehash_alg, ph, ph_len);
1430
1431 if (error != CX_OK) {
1432 return error;
1433 }
1434
1435 return MLDSA_internal_verify_core(sig, sig_len, &mprime, NULL, pk, pk_len, param);
1436}
struct MLDSA_sign_stack_workspace_s MLDSA_sign_stack_workspace_t
Stack-allocated workspace for MLDSA_internal_sign_core.
static cx_err_t 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:268
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:220
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:1417
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:1362
#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:1305
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:175
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:1385
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:594
static const MLDSA_prehash_info_t MLDSA_PREHASH_INFO[MLDSA_NUM_PREHASH_ALGS]
Definition cx_mldsa.c:143
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:1331
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:316
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.
cx_err_t 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]
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:79
uint8_t rho[MLDSA_SEEDBYTES]
Definition cx_mldsa.c:80
uint8_t K[MLDSA_SEEDBYTES]
Definition cx_mldsa.c:81
uint8_t w1_packed[MLDSA_MAX_K *192U]
Definition cx_mldsa.c:86
uint8_t mu[MLDSA_CRHBYTES]
Definition cx_mldsa.c:83
uint8_t tr[MLDSA_TRBYTES]
Definition cx_mldsa.c:82
uint8_t rhoprime[MLDSA_CRHBYTES]
Definition cx_mldsa.c:84
Stack-allocated workspace for MLDSA_internal_verify_core.
Definition cx_mldsa.c:122
MLDSA_verify_phase_overlay_t overlay
Definition cx_mldsa.c:132
uint8_t mu[MLDSA_CRHBYTES]
Definition cx_mldsa.c:125
uint8_t rho[MLDSA_SEEDBYTES]
Definition cx_mldsa.c:123
uint8_t w1_packed[MLDSA_MAX_K *192U]
Definition cx_mldsa.c:130
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:104
struct MLDSA_verify_phase_overlay_t::@0 setup_phase
Early-phase temporary used to derive mu.
uint8_t tr[MLDSA_TRBYTES]
Definition cx_mldsa.c:107
struct MLDSA_verify_phase_overlay_t::@1 az_phase
Az product loop temporaries.