mirror of
https://github.com/RustCrypto/signatures
synced 2026-06-21 13:45:42 +00:00
ml-dsa: use ctutils for constant-time selection; avoid branches (#1245)
This commit is contained in:
committed by
GitHub
parent
cdc95068ae
commit
7b0ffd01c1
Generated
+1
@@ -813,6 +813,7 @@ version = "0.1.0-rc.7"
|
||||
dependencies = [
|
||||
"const-oid 0.10.2",
|
||||
"criterion",
|
||||
"ctutils",
|
||||
"getrandom 0.4.2",
|
||||
"hex",
|
||||
"hex-literal",
|
||||
|
||||
@@ -36,6 +36,7 @@ hybrid-array = { version = "0.4", features = ["extra-sizes"] }
|
||||
module-lattice = "0.2.0-rc.0"
|
||||
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 }
|
||||
|
||||
+23
-23
@@ -1,3 +1,4 @@
|
||||
use ctutils::{CtEq, CtGt, CtLt, CtSelect};
|
||||
use hybrid_array::{
|
||||
ArraySize,
|
||||
typenum::{Shleft, U1, U13, Unsigned},
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
@@ -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
|
||||
|
||||
+18
-13
@@ -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},
|
||||
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user