Skip to content

Commit 52877f1

Browse files
committed
Add CompressedEdwardsY::validated_decompress()
1 parent c3a82a8 commit 52877f1

File tree

2 files changed

+91
-1
lines changed

2 files changed

+91
-1
lines changed

curve25519-dalek/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ features = [
4040
sha2 = { version = "0.11.0-rc.2", default-features = false }
4141
bincode = "1"
4242
criterion = { version = "0.5", features = ["html_reports"] }
43+
crypto-bigint = { version = "*"}
4344
hex = "0.4.2"
45+
hex-literal = "0.4"
4446
proptest = "1"
4547
rand = "0.9"
4648
rand_core = { version = "0.9", default-features = false, features = ["os_rng"] }

curve25519-dalek/src/edwards.rs

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,57 @@ impl CompressedEdwardsY {
217217
None
218218
}
219219
}
220+
221+
/// Attempt to decompress to an `EdwardsPoint` according to NIST rules.
222+
///
223+
/// Returns `None` if the input overflows the field modulus or is not the
224+
/// \\(y\\)-coordinate of a curve point. With `PointValidation::Full` it
225+
/// will also return `None` if the point is the identity point or is not
226+
/// contained in the prime-order subgroup.
227+
pub fn validated_decompress(&self, validation: PointValidation) -> Option<EdwardsPoint> {
228+
if !bool::from(decompress::check_modulus(self)) {
229+
return None;
230+
}
231+
232+
let (is_valid_y_coord, X, Y, Z) = decompress::step_1(self);
233+
234+
if !bool::from(is_valid_y_coord) {
235+
return None;
236+
}
237+
238+
let point = decompress::step_2(self, X, Y, Z);
239+
240+
if let PointValidation::Full = validation {
241+
(!point.is_identity() && point.is_torsion_free()).then_some(point)
242+
} else {
243+
Some(point)
244+
}
245+
}
246+
}
247+
248+
/// NIST validation rules.
249+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
250+
pub enum PointValidation {
251+
/// Checks for overflowing the field modulus.
252+
Partial,
253+
/// Checks for overflowing the field modulus, prime-order subgroup and
254+
/// identity point.
255+
Full,
220256
}
221257

222258
mod decompress {
223259
use super::*;
260+
use subtle::ConstantTimeLess;
261+
262+
pub(super) fn check_modulus(repr: &CompressedEdwardsY) -> Choice {
263+
let high_without_sign = repr.as_bytes()[31] & 0x7f;
264+
265+
let high = high_without_sign.ct_lt(&0x8f);
266+
let mid = repr.as_bytes()[1..31].ct_eq(&[0xff; 30]);
267+
let low = repr.as_bytes()[0].ct_lt(&0xed);
268+
269+
high & (!mid | low)
270+
}
224271

225272
#[rustfmt::skip] // keep alignment of explanatory comments
226273
pub(super) fn step_1(
@@ -1484,8 +1531,12 @@ impl GroupEncoding for EdwardsPoint {
14841531

14851532
fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
14861533
let repr = CompressedEdwardsY(*bytes);
1534+
let is_in_modulus = decompress::check_modulus(&repr);
14871535
let (is_valid_y_coord, X, Y, Z) = decompress::step_1(&repr);
1488-
CtOption::new(decompress::step_2(&repr, X, Y, Z), is_valid_y_coord)
1536+
CtOption::new(
1537+
decompress::step_2(&repr, X, Y, Z),
1538+
is_in_modulus & is_valid_y_coord,
1539+
)
14891540
}
14901541

14911542
fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
@@ -1779,6 +1830,7 @@ impl CofactorGroup for EdwardsPoint {
17791830
mod test {
17801831
use super::*;
17811832

1833+
use hex_literal::hex;
17821834
use rand_core::TryRngCore;
17831835

17841836
#[cfg(feature = "alloc")]
@@ -1872,6 +1924,42 @@ mod test {
18721924
assert_eq!(minus_basepoint.T, -(&constants::ED25519_BASEPOINT_POINT.T));
18731925
}
18741926

1927+
/// Test validated decompression
1928+
#[test]
1929+
fn validated_decompression() {
1930+
const OVERFLOWING_POINT: CompressedEdwardsY = CompressedEdwardsY(hex!(
1931+
"edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"
1932+
));
1933+
1934+
let point = OVERFLOWING_POINT.decompress().unwrap();
1935+
assert_eq!(point.compress().0, [0; 32]);
1936+
assert!(
1937+
OVERFLOWING_POINT
1938+
.validated_decompress(PointValidation::Partial)
1939+
.is_none()
1940+
);
1941+
1942+
const TORSION_POINT: CompressedEdwardsY = CompressedEdwardsY(hex!(
1943+
"5d78934d0311e9791fb9577f568a47605f347b367db825c4e27c52eb0cbe944d"
1944+
));
1945+
1946+
let point = TORSION_POINT
1947+
.validated_decompress(PointValidation::Partial)
1948+
.unwrap();
1949+
assert!(!point.is_torsion_free());
1950+
assert!(
1951+
OVERFLOWING_POINT
1952+
.validated_decompress(PointValidation::Full)
1953+
.is_none()
1954+
);
1955+
1956+
assert!(
1957+
CompressedEdwardsY::identity()
1958+
.validated_decompress(PointValidation::Full)
1959+
.is_none()
1960+
);
1961+
}
1962+
18751963
/// Test that computing 1*basepoint gives the correct basepoint.
18761964
#[cfg(feature = "precomputed-tables")]
18771965
#[test]

0 commit comments

Comments
 (0)