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 8130d24..b248b19 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -145,6 +145,68 @@ 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); + + // 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; + + // XXX this does two inversions: the above + one in .decompress() + // is it possible to do one? + CompressedEdwardsY(y.to_bytes()).decompress() + } +} + // ------------------------------------------------------------------------ // Internal point representations // ------------------------------------------------------------------------ @@ -383,6 +445,35 @@ 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. + /// + /// # 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 + // + // since y = Y/Z, x = X/Z, + // + // 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(); + + if Z_minus_Y.is_zero() == 0u8 { + Some(CompressedMontgomeryU(u.to_bytes())) + } else { + None + } + } } impl ExtendedPoint { @@ -427,6 +518,17 @@ impl ExtendedPoint { xy2d: xy2d } } + + /// 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() + } } impl CompletedPoint { @@ -919,6 +1021,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] = @@ -965,6 +1074,54 @@ 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().unwrap(); + 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); + } + + /// 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] + 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); + 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.compress_montgomery().is_none()); + } + /// 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. //