Skip to content

Commit 31b7540

Browse files
ml-dsa: fix constant time issues (#1386)
* ml-dsa: make coeff_from_half_byte constant-time Algorithm 15 (CoeffFromHalfByte) is called during ExpandS in key generation to sample the secret signing key vectors s1 and s2 from rho'. Rejection sampling inherently leaks accept/reject decisions, but data-dependent branches within the accepted range leak secret coefficient values via branch timing and predictor state. Using ctutils ensures coefficient reduction and sign selection are branch-free. * ml-dsa: make Polynomial and Vector infinity_norm branchless Polynomial::infinity_norm and Vector::infinity_norm are called during the signing loop to check rejection bounds on secret-derived vectors. Replacing Iterator::max() with a constant-time fold reduction via ctutils eliminates data-dependent comparison branches.
1 parent 8e7d8f1 commit 31b7540

2 files changed

Lines changed: 6 additions & 22 deletions

File tree

ml-dsa/src/algebra.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,7 @@ impl AlgebraExt for Polynomial {
186186
self.0
187187
.iter()
188188
.map(AlgebraExt::infinity_norm)
189-
.max()
190-
.expect("should have a maximum")
189+
.fold(0, |acc, x| u32::ct_select(&x, &acc, acc.ct_gt(&x)))
191190
}
192191

193192
fn power2round(&self) -> (Self, Self) {
@@ -229,8 +228,7 @@ impl<K: ArraySize> AlgebraExt for Vector<K> {
229228
self.0
230229
.iter()
231230
.map(AlgebraExt::infinity_norm)
232-
.max()
233-
.expect("should have a maximum")
231+
.fold(0, |acc, x| u32::ct_select(&x, &acc, acc.ct_gt(&x)))
234232
}
235233

236234
fn power2round(&self) -> (Self, Self) {

ml-dsa/src/sampling.rs

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::{
33
crypto::{G, H},
44
param::{Eta, MaskSamplingSize},
55
};
6+
use ctutils::{CtLt, CtSelect};
67
use hybrid_array::Array;
78
use module_lattice::{ArraySize, Field, Truncate};
89
#[cfg(feature = "zeroize")]
@@ -31,26 +32,11 @@ fn coeff_from_three_bytes(b: [u8; 3]) -> Option<Elem> {
3132
fn coeff_from_half_byte(b: u8, eta: Eta) -> Option<Elem> {
3233
match eta {
3334
Eta::Two if b < 15 => {
34-
let b = Int::from(match b {
35-
b if b < 5 => b,
36-
b if b < 10 => b - 5,
37-
_ => b - 10,
38-
});
39-
40-
if b <= 2 {
41-
Some(Elem::new(2 - b))
42-
} else {
43-
Some(-Elem::new(b - 2))
44-
}
45-
}
46-
Eta::Four if b < 9 => {
4735
let b = Int::from(b);
48-
if b <= 4 {
49-
Some(Elem::new(4 - b))
50-
} else {
51-
Some(-Elem::new(b - 4))
52-
}
36+
let sub = Int::ct_select(&10, &Int::ct_select(&5, &0, b.ct_lt(&5)), b.ct_lt(&10));
37+
Some(Elem::new(2) - Elem::new(b - sub))
5338
}
39+
Eta::Four if b < 9 => Some(Elem::new(4) - Elem::new(b.into())),
5440
_ => None,
5541
}
5642
}

0 commit comments

Comments
 (0)