From 45fbcb45f63a558f12501153da05e44b8d98a80e Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 27 Dec 2016 12:42:49 -0500 Subject: [PATCH 1/4] First notes and code on Montgomery conversion --- src/constants.rs | 5 +++ src/curve.rs | 109 +++++++++++++++++++++++++++++++++++++++++++++++ src/field.rs | 2 + 3 files changed, 116 insertions(+) diff --git a/src/constants.rs b/src/constants.rs index 0535515..43194a3 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -72,6 +72,11 @@ pub const SQRT_MINUS_A: FieldElement = FieldElement([ // sqrtMinusA 12222970, 8312128, 11511410, -9067497, 15300785, 241793, -25456130, -14121551, 12187136, -3972024, ]); +/// SQRT_MINUS_APLUS2 is sqrt(-486664) +pub const SQRT_MINUS_APLUS2: FieldElement = FieldElement([ + -12222970, -8312128, -11511410, 9067497, -15300785, + -241793, 25456130, 14121551, -12187136, 3972024]); + /// SQRT_MINUS_HALF is sqrt(-1/2) pub const SQRT_MINUS_HALF: FieldElement = FieldElement([ // sqrtMinusHalf -17256545, 3971863, 28865457, -1750208, 27359696, diff --git a/src/curve.rs b/src/curve.rs index 34085d4..d48fba1 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -148,6 +148,56 @@ impl CompressedEdwardsY { } } +/// In "Montgomery u" format, as used in X25519, a point `(u,v)` on +/// the Montgomery curve +/// +/// v^2 = u * (u^2 + 486662*u + 1) +/// +/// is represented just by `u`. Note that we use `(u,v)` instead of +/// `(x,y)` for Montgomery coordinates to avoid confusion with Edwards +/// coordinates. For Montgomery curves, it is possible to compute the +/// `u`-coordinate of `n(u,v)` just from `n` and `u`, so it is not +/// necessary to use `v` for a Diffie-Hellman key exchange. +/// +/// XXX add note on monty, twist security, edwards impl of x25519, rfc7748 +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct CompressedMontgomeryU(pub [u8; 32]); + +impl CompressedMontgomeryU { + /// View this `CompressedMontgomeryU` as an array of bytes. + pub fn to_bytes(&self) -> [u8;32] { + self.0 + } + + /// Attempt to decompress to an `ExtendedPoint`. + /// + /// Note that since there are two curve points with the same + /// `u`-coordinate, the `u`-coordinate does not fully specify a + /// point. + /// + /// XXX match behaviour in Signal specification re: sign choice + /// and rewrite this note + /// + /// XXX check for div by zero: when is u = -1 ? + /// XXX exceptional points for the birational map + pub fn decompress(&self) -> Option { + // u = (1 + y) / (1 - y) + // v = sqrt(-486664) * u / x + // + // so + // + // y = (u - 1) / (u + 1) + + let u = FieldElement::from_bytes(&self.0); + let u_plus_1_inv = (&u + &FieldElement::one()).invert(); + let y = &(&u - &FieldElement::one()) * &u_plus_1_inv; + + // XXX this does two inversions: the above + one in .decompress() + // is it possible to do one? + CompressedEdwardsY(y.to_bytes()).decompress() + } +} + // ------------------------------------------------------------------------ // Internal point representations // ------------------------------------------------------------------------ @@ -376,6 +426,26 @@ impl ProjectivePoint { s[31] ^= (x.is_negative_ed25519() << 7) as u8; CompressedEdwardsY(s) } + + /// Convert this point to a `CompressedMontgomeryU`. + /// Note that this discards the sign. + /// + /// XXX check for div by zero: when is Z = Y ? + /// XXX exceptional points for the birational map + pub fn compress_montgomery(&self) -> CompressedMontgomeryU { + // u = (1 + y) / (1 - y) + // v = sqrt(-486664) * u / x + // + // since y = Y/Z, x = X/Z, + // + // u = (1 + Y/Z) / (1 - Y/Z); + // = (Z + Y) / (Z - Y); + let Z_plus_Y = &self.Z + &self.Y; + let Z_minus_Y = &self.Z - &self.Y; + let u = &Z_plus_Y * &Z_minus_Y.invert(); + + CompressedMontgomeryU(u.to_bytes()) + } } impl ExtendedPoint { @@ -420,6 +490,11 @@ impl ExtendedPoint { xy2d: xy2d } } + + /// Compress this point to `CompressedMontgomeryU` format + pub fn compress_montgomery(&self) -> CompressedMontgomeryU { + self.to_projective().compress_montgomery() + } } impl CompletedPoint { @@ -912,6 +987,13 @@ mod test { use super::*; use super::select_precomputed_point; + /// The X25519 basepoint, in compressed Montgomery form. + static BASE_CMPRSSD_MONTY: CompressedMontgomeryU = + CompressedMontgomeryU([0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + /// X coordinate of the basepoint. /// = 15112221349535400772501151409588531511454012693041857206046113283949847762202 static BASE_X_COORD_BYTES: [u8; 32] = @@ -958,6 +1040,33 @@ mod test { 0xc0, 0x46, 0x83, 0x43, 0xde, 0x70, 0x4b, 0x85, 0x09, 0x6f, 0xfe, 0x35, 0x4f, 0x13, 0x2b, 0x42]); + #[test] + /// Test that the constant for sqrt(-486664) really is a square + /// root of -486664. + /// XXX this should be a test in constants.rs ?? + fn test_sqrt_minus_aplus2() { + let minus_aplus2 = FieldElement([-486664,0,0,0,0,0,0,0,0,0]); + let sqrt = constants::SQRT_MINUS_APLUS2; + let sq = &sqrt * &sqrt; + assert_eq!(sq, minus_aplus2); + } + + /// Test Montgomery conversion against the X25519 basepoint. + #[test] + fn test_basepoint_to_montgomery() { + let bp = BASE_CMPRSSD.decompress().unwrap(); + let bp_monty = bp.compress_montgomery(); + assert_eq!(bp_monty, BASE_CMPRSSD_MONTY); + } + + /// Test Montgomery conversion against the X25519 basepoint. + #[test] + fn test_basepoint_from_montgomery() { + let bp = BASE_CMPRSSD_MONTY.decompress().unwrap(); + let bp_compressed_edwards = bp.compress(); + assert_eq!(bp_compressed_edwards, BASE_CMPRSSD); + } + /// Test round-trip decompression for the basepoint. #[test] fn test_basepoint_decompression_compression() { diff --git a/src/field.rs b/src/field.rs index 926804e..cb29d70 100644 --- a/src/field.rs +++ b/src/field.rs @@ -798,6 +798,8 @@ impl FieldElement { /// Given a nonzero field element, compute its inverse. /// The inverse is computed as self^(p-2), since /// x^(p-2)x = x^(p-1) = 1 (mod p). + /// + /// XXX should we add a debug_assert that self is nonzero? pub fn invert(&self) -> FieldElement { // The bits of p-2 = 2^255 -19 -2 are 11010111111...11. // From 19d452c2dc23a17165c571147dff570eb52501cc Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Mon, 2 Jan 2017 09:24:58 -0500 Subject: [PATCH 2/4] Test decompressing an invalid montgomery point --- src/curve.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/curve.rs b/src/curve.rs index d48fba1..99c08e9 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -1067,6 +1067,21 @@ mod test { assert_eq!(bp_compressed_edwards, BASE_CMPRSSD); } + /// If u = -1, then v^2 = u*(u^2+486662*u+1) = 486660. + /// But 486660 is nonsquare mod p, so this should fail. + /// + /// XXX what does Signal do here? + #[test] + #[should_panic] + fn test_u_minus_one_monty() { + let mut m1 = FieldElement::zero(); + m1[0] = -1; + let m1_bytes = m1.to_bytes(); + let div_by_zero_u = CompressedMontgomeryU(m1_bytes); + let p = div_by_zero_u.decompress().unwrap(); + println!("{:?}", p); + } + /// Test round-trip decompression for the basepoint. #[test] fn test_basepoint_decompression_compression() { From db53b58e89f43663ad5941e52063f95801cf2ea3 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Mon, 27 Feb 2017 21:13:24 -0800 Subject: [PATCH 3/4] Properly handle exceptional points. --- src/curve.rs | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/curve.rs b/src/curve.rs index 99c08e9..30b7d5e 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -189,6 +189,18 @@ impl CompressedMontgomeryU { // y = (u - 1) / (u + 1) let u = FieldElement::from_bytes(&self.0); + + // If u = -1, then v^2 = u*(u^2+486662*u+1) = 486660. + // But 486660 is nonsquare mod p, so this is not a curve point. + // + // XXX what does Signal do here? + // + // Note: currently, without this check, u = -1 will accidentally + // decode to a valid (but incorrect) point, since 0.invert() = 0. + if u == FieldElement::minus_one() { + return None; + } + let u_plus_1_inv = (&u + &FieldElement::one()).invert(); let y = &(&u - &FieldElement::one()) * &u_plus_1_inv; @@ -430,9 +442,11 @@ impl ProjectivePoint { /// Convert this point to a `CompressedMontgomeryU`. /// Note that this discards the sign. /// - /// XXX check for div by zero: when is Z = Y ? - /// XXX exceptional points for the birational map - pub fn compress_montgomery(&self) -> CompressedMontgomeryU { + /// # Return + /// - `None` if `self` is the identity point; + /// - `Some(CompressedMontgomeryU)` otherwise. + /// + pub fn compress_montgomery(&self) -> Option { // u = (1 + y) / (1 - y) // v = sqrt(-486664) * u / x // @@ -440,11 +454,18 @@ impl ProjectivePoint { // // u = (1 + Y/Z) / (1 - Y/Z); // = (Z + Y) / (Z - Y); + // + // exceptional points: + // y = 1 <=> Y/Z = 1 <=> Z - Y = 0 let Z_plus_Y = &self.Z + &self.Y; let Z_minus_Y = &self.Z - &self.Y; let u = &Z_plus_Y * &Z_minus_Y.invert(); - CompressedMontgomeryU(u.to_bytes()) + if Z_minus_Y.is_zero() == 0u8 { + CompressedMontgomeryU(u.to_bytes()) + } else { + None + } } } @@ -1072,14 +1093,20 @@ mod test { /// /// XXX what does Signal do here? #[test] - #[should_panic] fn test_u_minus_one_monty() { let mut m1 = FieldElement::zero(); m1[0] = -1; let m1_bytes = m1.to_bytes(); let div_by_zero_u = CompressedMontgomeryU(m1_bytes); - let p = div_by_zero_u.decompress().unwrap(); - println!("{:?}", p); + assert!(div_by_zero_u.decompress().is_none()); + } + + /// Montgomery compression of the identity point should + /// fail (it's sent to infinity). + #[test] + fn test_identity_to_monty() { + let id = ExtendedPoint::identity(); + assert!(id.compressed_montgomery().is_none()); } /// Test round-trip decompression for the basepoint. From 5d3d114628075822c36582851f89e12cdca141bf Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Mon, 27 Feb 2017 23:15:39 -0800 Subject: [PATCH 4/4] fixup! Properly handle exceptional points. --- src/curve.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/curve.rs b/src/curve.rs index 30b7d5e..7a7e01c 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -462,7 +462,7 @@ impl ProjectivePoint { let u = &Z_plus_Y * &Z_minus_Y.invert(); if Z_minus_Y.is_zero() == 0u8 { - CompressedMontgomeryU(u.to_bytes()) + Some(CompressedMontgomeryU(u.to_bytes())) } else { None } @@ -512,8 +512,14 @@ impl ExtendedPoint { } } - /// Compress this point to `CompressedMontgomeryU` format - pub fn compress_montgomery(&self) -> CompressedMontgomeryU { + /// Convert this point to a `CompressedMontgomeryU`. + /// Note that this discards the sign. + /// + /// # Return + /// - `None` if `self` is the identity point; + /// - `Some(CompressedMontgomeryU)` otherwise. + /// + pub fn compress_montgomery(&self) -> Option { self.to_projective().compress_montgomery() } } @@ -1076,7 +1082,7 @@ mod test { #[test] fn test_basepoint_to_montgomery() { let bp = BASE_CMPRSSD.decompress().unwrap(); - let bp_monty = bp.compress_montgomery(); + let bp_monty = bp.compress_montgomery().unwrap(); assert_eq!(bp_monty, BASE_CMPRSSD_MONTY); } @@ -1106,7 +1112,7 @@ mod test { #[test] fn test_identity_to_monty() { let id = ExtendedPoint::identity(); - assert!(id.compressed_montgomery().is_none()); + assert!(id.compress_montgomery().is_none()); } /// Test round-trip decompression for the basepoint.