Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ml-dsa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ hybrid-array = { version = "0.4", features = ["extra-sizes"] }
module-lattice = "0.1"
sha3 = { version = "0.11.0-rc.8", default-features = false }
signature = { version = "3.0.0-rc.10", default-features = false, features = ["digest"] }
ctutils = { version = "0.4", default-features = false }

# optional dependencies
const-oid = { version = "0.10", features = ["db"], optional = true }
Expand Down
46 changes: 23 additions & 23 deletions ml-dsa/src/algebra.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use ctutils::{CtEq, CtGt, CtLt, CtSelect};
use hybrid_array::{
ArraySize,
typenum::{Shleft, U1, U13, Unsigned},
Expand Down Expand Up @@ -28,11 +29,9 @@ pub(crate) trait BarrettReduce: Unsigned {
let quotient = (x * Self::MULTIPLIER) >> Self::SHIFT;
let remainder = x - quotient * m;

if remainder < m {
Truncate::truncate(remainder)
} else {
Truncate::truncate(remainder - m)
}
let r_small: u32 = Truncate::truncate(remainder);
let r_large: u32 = Truncate::truncate(remainder.wrapping_sub(m));
u32::ct_select(&r_large, &r_small, remainder.ct_lt(&m))
}
}

Expand Down Expand Up @@ -103,14 +102,17 @@ impl Decompose for Elem {
let r_plus = self.clone();
let r0 = r_plus.mod_plus_minus::<TwoGamma2>();

if r_plus - r0 == Elem::new(BaseField::Q - 1) {
(Elem::new(0), r0 - Elem::new(1))
} else {
let diff = r_plus - r0;
// Use constant-time division instead of hardware division
let r1 = Elem::new(TwoGamma2::ct_div(diff.0));
(r1, r0)
}
let diff = r_plus - r0;
let is_edge = diff.0.ct_eq(&(BaseField::Q - 1));

// Compute both branches unconditionally
let edge = (Elem::new(0), r0 - Elem::new(1));
let r1 = Elem::new(TwoGamma2::ct_div(diff.0));
let normal = (r1, r0);

let r1_out = Elem::new(u32::ct_select(&normal.0.0, &edge.0.0, is_edge));
let r0_out = Elem::new(u32::ct_select(&normal.1.0, &edge.1.0, is_edge));
(r1_out, r0_out)
}
}

Expand All @@ -126,11 +128,12 @@ pub(crate) trait AlgebraExt: Sized {
impl AlgebraExt for Elem {
fn mod_plus_minus<M: Unsigned>(&self) -> Self {
let raw_mod = Elem::new(M::reduce(self.0));
if raw_mod.0 <= M::U32 >> 1 {
raw_mod
} else {
raw_mod - Elem::new(M::U32)
}
let in_lower_half = !raw_mod.0.ct_gt(&(M::U32 >> 1));
Elem::new(u32::ct_select(
&(raw_mod - Elem::new(M::U32)).0,
&raw_mod.0,
in_lower_half,
))
}

// FIPS 204 defines the infinity norm differently for signed vs. unsigned integers:
Expand All @@ -142,11 +145,8 @@ impl AlgebraExt for Elem {
// the signed integers used in this crate, so we can safely use the unsigned version. However,
// since mod_plus_minus is also unsigned, we need to unwrap the "negative" values.
fn infinity_norm(&self) -> u32 {
if self.0 <= BaseField::Q >> 1 {
self.0
} else {
BaseField::Q - self.0
}
let in_lower_half = !self.0.ct_gt(&(BaseField::Q >> 1));
u32::ct_select(&(BaseField::Q - self.0), &self.0, in_lower_half)
}

// Algorithm 35 Power2Round
Expand Down
31 changes: 18 additions & 13 deletions ml-dsa/src/hint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
algebra::{AlgebraExt, BaseField, Decompose, Elem, Polynomial, Vector},
param::{EncodedHint, SignatureParams},
};
use ctutils::{Choice, CtEq, CtGt, CtSelect};
use hybrid_array::{
Array,
typenum::{U256, Unsigned},
Expand All @@ -17,25 +18,29 @@ fn make_hint<TwoGamma2: Unsigned>(z: Elem, r: Elem) -> bool {
}

/// Algorithm 40 `UseHint`: returns the high bits of `r` adjusted according to hint `h`.
///
/// All branches are replaced with constant-time selection to avoid
/// leaking information about `r0` through branch timing.
#[allow(clippy::integer_division_remainder_used, reason = "params are public")]
fn use_hint<TwoGamma2: Unsigned>(h: bool, r: Elem) -> Elem {
let m: u32 = (BaseField::Q - 1) / TwoGamma2::U32;
let (r1, r0) = r.decompose::<TwoGamma2>();
let gamma2 = TwoGamma2::U32 / 2;

if h {
if r0.0 > 0 && r0.0 <= gamma2 {
Elem::new((r1.0 + 1) % m)
} else if (r0.0 == 0) || (r0.0 >= BaseField::Q - gamma2) {
Elem::new((r1.0 + m - 1) % m)
} else {
// We use the Elem encoding even for signed integers. Since r0 is computed
// mod+- 2*gamma2 (possibly minus 1), it is guaranteed to be in [-gamma2, gamma2].
unreachable!();
}
} else {
r1
}
// Compute both possible hint-adjusted results unconditionally
let r1_inc = Elem::new((r1.0 + 1) % m);
let r1_dec = Elem::new((r1.0 + m - 1) % m);

// r0 is "positive" when r0 > 0 AND r0 <= gamma2
let r0_positive = !r0.0.ct_eq(&0) & !r0.0.ct_gt(&gamma2);
let hinted = Elem::new(u32::ct_select(&r1_dec.0, &r1_inc.0, r0_positive));

// Apply hint only when h is set
Elem::new(u32::ct_select(
&r1.0,
&hinted.0,
Choice::from_u8_lsb(u8::from(h)),
))
}

#[derive(Clone, PartialEq, Debug)]
Expand Down
Loading