mirror of
https://github.com/RustCrypto/signatures
synced 2026-06-21 13:45:42 +00:00
ml-dsa: fix use_hint when 𝓇₀ = 0 (#1194)
Originally reported as GHSA-h37v-hp6w-2pp8 by @XoifaiI
According to FIPS 204 Algorithm 40:
3: if h = 1 and r0 > 0 return (r1 + 1) mod m
4: if h = 1 and r0 <= 0 return (r1 − 1) mod m
The check that `r0 > 0` was missing, and so values were being
miscomputed in the case that `r0 = 0`.
Likewise, while there were comprehensive tests for other cases, this
case was also untested.
This adds the necessary check as well as a test that covers the case.
This commit is contained in:
+27
-11
@@ -7,27 +7,31 @@ use module_lattice::utils::Truncate;
|
||||
use crate::algebra::{AlgebraExt, BaseField, Decompose, Elem, Field, Polynomial, Vector};
|
||||
use crate::param::{EncodedHint, SignatureParams};
|
||||
|
||||
/// Algorithm 39 `MakeHint`: computes hint bit indicating whether adding `z` to `r` alters the high
|
||||
/// bits of `r`.
|
||||
fn make_hint<TwoGamma2: Unsigned>(z: Elem, r: Elem) -> bool {
|
||||
let r1 = r.high_bits::<TwoGamma2>();
|
||||
let v1 = (r + z).high_bits::<TwoGamma2>();
|
||||
r1 != v1
|
||||
}
|
||||
|
||||
// The method only deals with public data, so we don't need to worry that / and % are not
|
||||
// constant-time.
|
||||
#[allow(clippy::integer_division_remainder_used)]
|
||||
/// Algorithm 40 `UseHint`: returns the high bits of `r` adjusted according to hint `h`.
|
||||
#[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 && r0.0 <= gamma2 {
|
||||
Elem::new((r1.0 + 1) % m)
|
||||
} else if h && r0.0 >= BaseField::Q - gamma2 {
|
||||
Elem::new((r1.0 + m - 1) % m)
|
||||
} else if h {
|
||||
// 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!();
|
||||
|
||||
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
|
||||
}
|
||||
@@ -227,6 +231,18 @@ mod test {
|
||||
panic!("Could not find suitable test value");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_hint_r0_is_zero() {
|
||||
type TwoGamma2 = <MlDsa65 as ParameterSet>::TwoGamma2;
|
||||
let m = (BaseField::Q - 1) / TwoGamma2::U32;
|
||||
let r = Elem::new(0);
|
||||
let (r1, r0) = r.decompose::<TwoGamma2>();
|
||||
assert_eq!(r0.0, 0);
|
||||
|
||||
let result = use_hint::<TwoGamma2>(true, r);
|
||||
assert_eq!(result, Elem::new((r1.0 + m - 1) % m));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_hint_threshold() {
|
||||
type TwoGamma2 = <MlDsa65 as ParameterSet>::TwoGamma2;
|
||||
|
||||
Reference in New Issue
Block a user