1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Cédric Mesnil <cslashm@pm.me>
// Index-based loops in this module deliberately mirror the reference
// algorithm (parallel-array access `a[i]`/`b[i]`, or a position-indexed
// packing/reduction state) and keep the constant-time data layout explicit;
// the `iter().enumerate()` rewrite would obscure it. Documented, single-lint
// allow per CLAUDE.md §6 (scoped to this module, not a blanket suppression).
//! Fisher-Yates shuffle for ML-DSA NTT butterfly index randomization
//! (**countermeasure: SPA / SEMA on secret-polynomial NTT**).
//!
//! Same principle as the ML-KEM `shuffle` module, ported to the
//! ML-DSA arithmetic (`q = 8 380 417`, `i32` coefficients,
//! 256-coefficient polynomials, NTT all the way down to length-1).
//!
//! ## Principle
//!
//! Within a given NTT level, all butterfly groups (and all butterflies
//! within a group) are independent — permuting their execution order
//! does not affect correctness, only the pattern of memory accesses
//! and instantaneous power consumption. By drawing a fresh permutation
//! per level and per group from an SCA-dedicated CSPRNG, two successive
//! NTTs on the same input produce different power / EM traces,
//! defeating trace-alignment-based SPA and template attacks. The
//! CSPRNG (`ScaRng`) is seeded with `K ‖ rnd ‖ tr ‖ M'` inside
//! `dsa::sign_internal`.
//!
//! Applied to `s1`, `s2`, `t0` (the three secret vectors). The public
//! matrix `A` keeps the classical NTT for performance — public values
//! need no shuffling.
//!
//! The primary entry point is `ntt_shuffled`, a drop-in replacement
//! for `super::ntt::ntt` that draws from a [`CryptoRng`].
//!
//! ## References
//!
//! * *Hardware NTT shuffling as a lightweight countermeasure for
//! ML-KEM* (arXiv, 2024) — original shuffling analysis; the
//! construction transfers directly to ML-DSA.
//! * *Slothy-assisted Cortex-M4/M7 implementations of ML-DSA*
//! (IACR ePrint 2025) — performance measurements for the shuffled
//! variant.
//! * *Physical security considerations for ML-DSA* (NIST, 2025) —
//! recommended posture including shuffling as an SPA mitigation.
//!
//! ## Where to look next
//!
//! * Countermeasure description and threat analysis:
//! `doc/sca/countermeasures/ml_dsa.rst`, section *SPA / SEMA —
//! Fisher-Yates shuffled NTT*.
//! * Call site: `dsa::sign_internal`, Step 1 of
//! the protected branch (three `for` loops applying
//! `ntt_shuffled` to each polynomial of `s1_hat`, `s2_hat`,
//! `t0_hat`).
use MlDsaError;
use ;
use N;
use CryptoRng;
use vec;
/// Generate a uniform random permutation of `0..n` in-place using
/// Fisher-Yates with rejection sampling on 16-bit RNG output.
///
/// `perm` must be a slice of length `n`; on entry its contents are
/// overwritten with the identity permutation, then shuffled in place.
/// Forward NTT with randomized butterfly ordering (SPA countermeasure).
///
/// Functionally equivalent to `super::ntt::ntt` but draws fresh
/// random permutations from `rng` for both the inter-group and
/// intra-group butterfly orderings at each NTT level.
///
/// Uses the non-Montgomery [`ZETAS`] table together with the public
/// `mul_mod_q` helper, so the implementation is fully self-contained
/// — at the cost of being slightly slower than the in-place
/// Montgomery butterflies in `super::ntt::ntt`. Acceptable for the
/// SCA-protected build because the shuffled NTT only runs three times
/// per signature (on `s1`, `s2`, `t0`), once at the start of
/// `sign_internal` and never inside the rejection loop.
///
/// Output coefficients are in `[0, q-1]`, matching the regular NTT.