@@ -50,28 +50,46 @@ pub(crate) trait Ntt {
5050 fn ntt ( & self ) -> Self :: Output ;
5151}
5252
53+ /// Constant-time NTT butterfly layer.
54+ ///
55+ /// Uses const generics to ensure loop bounds are compile-time constants,
56+ /// avoiding UDIV instructions from runtime `step_by` calculations.
57+ #[ allow( clippy:: inline_always) ] // Required for constant-time guarantees in crypto code
58+ #[ inline( always) ]
59+ fn ntt_layer < const LEN : usize , const ITERATIONS : usize > ( w : & mut [ Elem ; 256 ] , m : & mut usize ) {
60+ for i in 0 ..ITERATIONS {
61+ let start = i * 2 * LEN ;
62+ * m += 1 ;
63+ let z = ZETA_POW_BITREV [ * m] ;
64+ for j in start..( start + LEN ) {
65+ let t = z * w[ j + LEN ] ;
66+ w[ j + LEN ] = w[ j] - t;
67+ w[ j] = w[ j] + t;
68+ }
69+ }
70+ }
71+
5372impl Ntt for Polynomial {
5473 type Output = NttPolynomial ;
5574
5675 // Algorithm 41 NTT
76+ //
77+ // This implementation uses const-generic helper functions to ensure all loop
78+ // bounds are compile-time constants, avoiding potential UDIV instructions.
5779 fn ntt ( & self ) -> Self :: Output {
58- let mut w = self . 0 . clone ( ) ;
59-
80+ let mut w: [ Elem ; 256 ] = self . 0 . clone ( ) . into ( ) ;
6081 let mut m = 0 ;
61- for len in [ 128 , 64 , 32 , 16 , 8 , 4 , 2 , 1 ] {
62- for start in ( 0 ..256 ) . step_by ( 2 * len) {
63- m += 1 ;
64- let z = ZETA_POW_BITREV [ m] ;
65-
66- for j in start..( start + len) {
67- let t = z * w[ j + len] ;
68- w[ j + len] = w[ j] - t;
69- w[ j] = w[ j] + t;
70- }
71- }
72- }
7382
74- NttPolynomial :: new ( w)
83+ ntt_layer :: < 128 , 1 > ( & mut w, & mut m) ;
84+ ntt_layer :: < 64 , 2 > ( & mut w, & mut m) ;
85+ ntt_layer :: < 32 , 4 > ( & mut w, & mut m) ;
86+ ntt_layer :: < 16 , 8 > ( & mut w, & mut m) ;
87+ ntt_layer :: < 8 , 16 > ( & mut w, & mut m) ;
88+ ntt_layer :: < 4 , 32 > ( & mut w, & mut m) ;
89+ ntt_layer :: < 2 , 64 > ( & mut w, & mut m) ;
90+ ntt_layer :: < 1 , 128 > ( & mut w, & mut m) ;
91+
92+ NttPolynomial :: new ( w. into ( ) )
7593 }
7694}
7795
@@ -89,30 +107,51 @@ pub(crate) trait NttInverse {
89107 fn ntt_inverse ( & self ) -> Self :: Output ;
90108}
91109
110+ /// Constant-time inverse NTT butterfly layer.
111+ ///
112+ /// Uses const generics to ensure loop bounds are compile-time constants,
113+ /// avoiding UDIV instructions from runtime `step_by` calculations.
114+ #[ allow( clippy:: inline_always) ] // Required for constant-time guarantees in crypto code
115+ #[ inline( always) ]
116+ fn ntt_inverse_layer < const LEN : usize , const ITERATIONS : usize > (
117+ w : & mut [ Elem ; 256 ] ,
118+ m : & mut usize ,
119+ ) {
120+ for i in 0 ..ITERATIONS {
121+ let start = i * 2 * LEN ;
122+ * m -= 1 ;
123+ let z = -ZETA_POW_BITREV [ * m] ;
124+ for j in start..( start + LEN ) {
125+ let t = w[ j] ;
126+ w[ j] = t + w[ j + LEN ] ;
127+ w[ j + LEN ] = z * ( t - w[ j + LEN ] ) ;
128+ }
129+ }
130+ }
131+
92132impl NttInverse for NttPolynomial {
93133 type Output = Polynomial ;
94134
95135 // Algorithm 42 NTT^{−1}
136+ //
137+ // This implementation uses const-generic helper functions to ensure all loop
138+ // bounds are compile-time constants, avoiding potential UDIV instructions.
96139 fn ntt_inverse ( & self ) -> Self :: Output {
97140 const INVERSE_256 : Elem = Elem :: new ( 8_347_681 ) ;
98141
99- let mut w = self . 0 . clone ( ) ;
100-
142+ let mut w: [ Elem ; 256 ] = self . 0 . clone ( ) . into ( ) ;
101143 let mut m = 256 ;
102- for len in [ 1 , 2 , 4 , 8 , 16 , 32 , 64 , 128 ] {
103- for start in ( 0 ..256 ) . step_by ( 2 * len) {
104- m -= 1 ;
105- let z = -ZETA_POW_BITREV [ m] ;
106-
107- for j in start..( start + len) {
108- let t = w[ j] ;
109- w[ j] = t + w[ j + len] ;
110- w[ j + len] = z * ( t - w[ j + len] ) ;
111- }
112- }
113- }
114144
115- INVERSE_256 * & Polynomial :: new ( w)
145+ ntt_inverse_layer :: < 1 , 128 > ( & mut w, & mut m) ;
146+ ntt_inverse_layer :: < 2 , 64 > ( & mut w, & mut m) ;
147+ ntt_inverse_layer :: < 4 , 32 > ( & mut w, & mut m) ;
148+ ntt_inverse_layer :: < 8 , 16 > ( & mut w, & mut m) ;
149+ ntt_inverse_layer :: < 16 , 8 > ( & mut w, & mut m) ;
150+ ntt_inverse_layer :: < 32 , 4 > ( & mut w, & mut m) ;
151+ ntt_inverse_layer :: < 64 , 2 > ( & mut w, & mut m) ;
152+ ntt_inverse_layer :: < 128 , 1 > ( & mut w, & mut m) ;
153+
154+ INVERSE_256 * & Polynomial :: new ( w. into ( ) )
116155 }
117156}
118157
0 commit comments