From acd3826fe28d2dddcc97e6ead9bb1acb834e1c1c Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 7 Sep 2017 20:31:06 +0000 Subject: [PATCH 01/16] Implement Montgomery arithmetic and laddering. * ADDs part of https://github.com/isislovecruft/curve25519-dalek/issues/47 --- src/constants.rs | 9 ++ src/constants_32bit.rs | 3 + src/constants_64bit.rs | 3 + src/edwards.rs | 45 +++++- src/field.rs | 8 +- src/montgomery.rs | 334 +++++++++++++++++++++++++++++++++++++++-- 6 files changed, 385 insertions(+), 17 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index c8d6b8d..4c08102 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -21,6 +21,7 @@ use edwards::CompressedEdwardsY; #[cfg(feature = "yolocrypto")] use decaf::{DecafPoint, DecafBasepointTable}; +use montgomery::CompressedMontgomeryU; use scalar::Scalar; #[cfg(feature="radix_51")] @@ -52,6 +53,14 @@ pub const BASE_CMPRSSD: CompressedEdwardsY = 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66]); +/// The X25519 basepoint, in compressed Montgomery form. +pub const BASE_COMPRESSED_MONTGOMERY: 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]); + + /// The Ed25519 basepoint, as a `DecafPoint`. This is called `_POINT` to distinguish it from /// `_TABLE`, which provides fast scalar multiplication. #[cfg(feature = "yolocrypto")] pub const DECAF_ED25519_BASEPOINT_POINT: DecafPoint = diff --git a/src/constants_32bit.rs b/src/constants_32bit.rs index 88db84d..02b629b 100644 --- a/src/constants_32bit.rs +++ b/src/constants_32bit.rs @@ -75,6 +75,9 @@ pub const HALF: FieldElement32 = FieldElement32([ pub const A: FieldElement32 = FieldElement32([ 486662, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); +/// `APLUS2_OVER_FOUR` is (A+2)/4. (This is used internally within Montgomery laddering.) +pub const APLUS2_OVER_FOUR: FieldElement32 = FieldElement32([121666, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + /// `SQRT_MINUS_A` is sqrt(-486662) // XXX I think that this was used in Adam's code for his elligator // implementation, but that should maybe be using sqrt(-486664) diff --git a/src/constants_64bit.rs b/src/constants_64bit.rs index 046da20..8beee81 100644 --- a/src/constants_64bit.rs +++ b/src/constants_64bit.rs @@ -54,6 +54,9 @@ pub const HALF: FieldElement64 = FieldElement64([2251799813685239, 2251799813685 /// In Montgomery form y² = x³+Ax²+x, Curve25519 has A=486662. pub const A: FieldElement64 = FieldElement64([486662, 0, 0, 0, 0]); +/// `APLUS2_OVER_FOUR` is (A+2)/4. (This is used internally within Montgomery laddering.) +pub const APLUS2_OVER_FOUR: FieldElement64 = FieldElement64([121666, 0, 0, 0, 0]); + /// `SQRT_MINUS_A` is sqrt(-486662) // XXX I think that this was used in Adam's code for his elligator // implementation, but that should maybe be using sqrt(-486664) diff --git a/src/edwards.rs b/src/edwards.rs index 2456954..9a03295 100644 --- a/src/edwards.rs +++ b/src/edwards.rs @@ -90,6 +90,7 @@ use constants; use field::FieldElement; use scalar::Scalar; use montgomery::CompressedMontgomeryU; +use montgomery::MontgomeryPoint; use subtle::slices_equal; use subtle::bytes_equal; @@ -447,14 +448,14 @@ impl ProjectivePoint { CompressedEdwardsY(s) } - /// Convert this point to a `CompressedMontgomeryU`. + /// Convert this point to a Montgomery u-coordinate (affine). /// Note that this discards the sign. /// /// # Return /// - `None` if `self` is the identity point; - /// - `Some(CompressedMontgomeryU)` otherwise. + /// - `Some(FieldElement)` otherwise. /// - pub fn compress_montgomery(&self) -> Option { + fn convert_to_montgomery(&self) -> Option { // u = (1 + y) / (1 - y) // v = sqrt(-486664) * u / x // @@ -470,7 +471,38 @@ impl ProjectivePoint { let u = &Z_plus_Y * &Z_minus_Y.invert(); if Z_minus_Y.is_zero() == 0u8 { - Some(CompressedMontgomeryU(u.to_bytes())) + Some(u) + } else { + None + } + } + + /// 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 { + let u: Option = self.convert_to_montgomery(); + + if u.is_some() { + Some(CompressedMontgomeryU(u.unwrap().to_bytes())) + } else { + None + } + } + + /// Convert this point to its equivalent on the Montgomery form of + /// the curve, without compressing. + /// + /// DOCDOC + pub fn to_montgomery(&self) -> Option { + let u: Option = self.convert_to_montgomery(); + + if u.is_some() { + Some(MontgomeryPoint{ U: u.unwrap(), Z: FieldElement::one() }) } else { None } @@ -515,6 +547,11 @@ impl ExtendedPoint { } } + /// DOCDOC + pub fn to_montgomery(&self) -> Option { + self.to_projective().to_montgomery() + } + /// Compress this point to `CompressedEdwardsY` format. pub fn compress_edwards(&self) -> CompressedEdwardsY { self.to_projective().compress_edwards() diff --git a/src/field.rs b/src/field.rs index 617adbc..730c3fa 100644 --- a/src/field.rs +++ b/src/field.rs @@ -197,11 +197,15 @@ 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? + // + // XXX do we want the debug assertion to check for zero? it breaks behaviour + // such as that such as in curve25519_dalek::montgomery::test::identity_to_monty. pub fn invert(&self) -> FieldElement { + // debug_assert!(*self != FieldElement::zero()); + // The bits of p-2 = 2^255 -19 -2 are 11010111111...11. // // nonzero bits of exponent diff --git a/src/montgomery.rs b/src/montgomery.rs index 29e1b20..0b2c16a 100644 --- a/src/montgomery.rs +++ b/src/montgomery.rs @@ -8,7 +8,19 @@ // - Isis Agora Lovecruft // - Henry de Valence -//! Montgomery arithmetic prototype, subject to revision. +//! Montgomery arithmetic. +//! +//! Apart from the compressed point implementation +//! (i.e. `CompressedMontgomeryU`), this module is a "clean room" implementation +//! of the Montgomery arithmetic described in the following papers: +//! +//! * Costello, Craig, and Benjamin Smith. "Montgomery curves and their +//! arithmetic." Journal of Cryptographic Engineering (2017): 1-14. +//! [PDF](http://eprint.iacr.org/2017/212.pdf) +//! +//! * Montgomery, Peter L. "Speeding the Pollard and elliptic curve methods of +//! factorization." Mathematics of computation 48.177 (1987): 243-264. +//! [PDF](http://www.ams.org/mcom/1987-48-177/S0025-5718-1987-0866113-7/) // We allow non snake_case names because coordinates in projective space are // traditionally denoted by the capitalisation of their respective @@ -16,12 +28,21 @@ // affine and projective cakes and eat both of them too. #![allow(non_snake_case)] +use core::ops::{Mul, MulAssign}; use constants; use field::FieldElement; use edwards::{ExtendedPoint, CompressedEdwardsY}; +use scalar::Scalar; +// XXX move these to a common "traits" or "group" module? —isis +use edwards::{Identity, ValidityCheck}; + +use subtle::slices_equal; use subtle::ConditionallyAssignable; +use subtle::ConditionallySwappable; +use subtle::Equal; +use subtle::Mask; /// In "Montgomery u" format, as used in X25519, a point `(u,v)` on /// the Montgomery curve @@ -40,6 +61,11 @@ pub struct CompressedMontgomeryU(pub [u8; 32]); impl CompressedMontgomeryU { /// View this `CompressedMontgomeryU` as an array of bytes. + pub fn as_bytes<'a>(&'a self) -> &'a [u8; 32] { + &self.0 + } + + /// Convert this `CompressedMontgomeryU` to an array of bytes. pub fn to_bytes(&self) -> [u8; 32] { self.0 } @@ -65,7 +91,7 @@ impl CompressedMontgomeryU { /// * `v` is not square. // // XXX any other exceptional points for the birational map? - pub fn decompress(&self) -> Option { + pub fn decompress_edwards(&self) -> Option { let u: FieldElement = FieldElement::from_bytes(&self.0); // If u = -1, then v^2 = u*(u^2+486662*u+1) = 486660. @@ -84,6 +110,23 @@ impl CompressedMontgomeryU { CompressedEdwardsY(y.to_bytes()).decompress() } + /// Decompress this `CompressedMontgomeryU` to a `MontgomeryPoint`. + /// + /// Going from affine to projective coordinates, we have: + /// + ///     u → U/W + /// + /// # Returns + /// + /// A projective `MontgomeryPoint` corresponding to this compressed point. + pub fn decompress_montgomery(&self) -> MontgomeryPoint { + MontgomeryPoint{ + // XXX is it a problem here if we're not using a canonical encoding? —isis + U: FieldElement::from_bytes(&self.0), + W: FieldElement::one(), + } + } + /// Given a Montgomery `u` coordinate, compute an Edwards `y` via /// `y = (u-1)/(u+1)`. /// @@ -150,33 +193,246 @@ impl CompressedMontgomeryU { } } +/// A point on the Montgomery form of the curve, in projective 𝗣^2 coordinates. +/// +/// The transition between affine and projective is given by +/// +///     u → U/W +///     v → V/W +/// +/// thus the Montgomery curve equation +/// +///     E_(A,B) : Bv² = u(u² + Au + 1) +/// +/// becomes +/// +///     E_(A,B) : BV²W = U(U² + AUW + W²) ⊆ 𝗣^2 +/// +/// Here, again, to differentiate from points in the twisted Edwards model, we +/// call the point `(x,y)` in affine coordinates `(u,v)` and similarly in projective +/// space we use `(U:V:W)`. However, since (as per Montgomery's original work) the +/// v-coordinate is superfluous to the definition of the group law, we merely +/// use `(U:W)`. +#[derive(Copy, Clone, Debug)] +#[allow(missing_docs)] +pub struct MontgomeryPoint{ + pub U: FieldElement, + pub W: FieldElement, +} + +/// The identity point is a unique point (the only where `W = 0`) on the curve. +/// +/// In projective coordinates, the quotient map `x : E (A,B) → E/<⦵> = 𝗣¹` is +/// +///     ⎧ (x_P:1) if P = (x_P:y_P:1) , +///     x : P ↦ ⎨ +///     ⎩ (1:0) if P = O = (0:1:0) . +/// +/// We emphasize that the formula `x((U: V : W)) = (U : W)` only holds on the +/// open subset of `E_(A,B)` where `W ≠ 0`; it does not extend to the point +/// `O = (0:1:0)` at infinity, because `(0:0)` is not a projective point. +/// +/// # Returns +/// +/// The (exceptional) point at infinity in the Montgomery model. +impl Identity for MontgomeryPoint { + fn identity() -> MontgomeryPoint { + MontgomeryPoint { + U: FieldElement::one(), + W: FieldElement::zero(), + } + } +} + +/// Determine if two `MontgomeryPoint`s are equal, in constant time. +/// +/// # Note +/// +/// Because a compressed point on the Montgomery form of the curve doesn't +/// include the sign bit, there's two points here (if translated from the +/// Edwards form) which will equate. +/// +/// # Returns +/// +/// `1` if the points are equal, and `0` otherwise. +impl Equal for MontgomeryPoint { + fn ct_eq(&self, that: &MontgomeryPoint) -> u8 { + slices_equal(self.compress_montgomery().as_bytes(), + that.compress_montgomery().as_bytes()) + } +} + +/// Determine if this `MontgomeryPoint` is valid. +/// +/// # Note +/// +/// All points, except for `(X:W) = (0:0)`, are valid, since the projective +/// model is linear through the origin and is comprised by all `X` in +/// ℤ/(2²⁵⁵-19). +/// +/// # Returns +/// +/// `true` if it is valid, and `false` otherwise. +impl ValidityCheck for MontgomeryPoint { + fn is_valid(&self) -> bool { + let zero = FieldElement::zero(); + + if (self.U.ct_eq(&zero) & self.W.ct_eq(&zero)) == 1 { + return true; + } + false + } +} + +/// Conditionally assign another `MontgomeryPoint` to this point, in constant time. +/// +/// If `choice == 1`, assign `that` to `self`. Otherwise, leave `self` +/// unchanged. +impl ConditionallyAssignable for MontgomeryPoint { + fn conditional_assign(&mut self, that: &MontgomeryPoint, choice: Mask) { + self.U.conditional_assign(&that.U, choice); + self.W.conditional_assign(&that.W, choice); + } +} + +impl MontgomeryPoint { + /// Compress this point to only its u-coordinate (note: affine). + /// + /// # Returns + /// + /// A `CompressedMontgomeryU`. + pub fn compress_montgomery(&self) -> CompressedMontgomeryU { + let u_affine: FieldElement = &self.U * &self.W.invert(); + + CompressedMontgomeryU(u_affine.to_bytes()) + } + + /// Differential addition for single-coordinate Montgomery points. + /// + /// Montgomery coordinates in projective 𝗣¹ space are odd in that 𝗣¹ + /// inherits none of the group structure from E_(A,B). Hence, the mapping + /// of the group operation, `⊕`, is undefined for the pair `(x(P), x(Q))`; + /// that is, given `x(P)` and `x(Q)`, we cannot derive `x(P ⊕ Q)`. This is + /// due to the fact that, in Montgomery coordinates, `x(P)` determines `P` + /// only up to a sign, and thus we cannot differentiate `x(P ⊕ Q)` from + /// `x(P ⊖ Q)`. However, via differential addition, any three of the values + /// `{x(P), x(Q), x(P ⊕ Q), x(P ⊖ Q)}` determines the forth, so we can + /// define *pseudo-addition* for a singular coordinate. + /// + /// # Warning + /// + /// If the `difference` is the identity point, or a two torsion point, the + /// results of this method are not correct, but instead result in `(0:0)` + /// (an invalid projective point in the Montgomery model). + /// + // XXX API-wise, do we care that doubling is degenerate, or should we allow + // the user to do a stupid and inefficient (albeit not incorrect) thing? + fn differential_add(&self, that: &MontgomeryPoint, + difference: &MontgomeryPoint) -> MontgomeryPoint { + // debug_assert!(self.ct_eq(that) != 1); // The doubling case is degenerate + // debug_assert!(!difference.is_identity()); // P ⦵ Q ∉ {O,T} + // debug_assert!(!difference.is_two_torsion_point()); + + let v1: FieldElement = &(&self.U + &self.W) * &(&that.U - &that.W); + let v2: FieldElement = &(&self.U - &self.W) * &(&that.U + &that.W); + + MontgomeryPoint { + U: &difference.W * &(&v1 + &v2).square(), // does reduction on square() + W: &difference.U * &(&v1 - &v2).square(), // does reduction on square() + } + } + + /// Differential doubling for single-coordinate Montgomery points. + /// + /// DOCDOC + /// + /// # Returns + /// + /// A Montgomery point. + fn differential_double(&self) -> MontgomeryPoint { + let mut v1: FieldElement; + let v2: FieldElement; + let v3: FieldElement; + + v1 = (&self.U + &self.W).square(); + v2 = (&self.U - &self.W).square(); + + let U: FieldElement = &v1 * &v2; + + v1 -= &v2; + v3 = &(&constants::APLUS2_OVER_FOUR * &v1) + &v2; + + let W: FieldElement = &v1 * &v3; + + MontgomeryPoint{ U: U, W: W } + } +} + +/// Multiply this `MontgomeryPoint` by a `Scalar`. +/// +/// DOCDOC +/// explain montgomery laddering +impl<'a, 'b> Mul<&'b Scalar> for &'a MontgomeryPoint { + type Output = MontgomeryPoint; + + fn mul(self, scalar: &'b Scalar) -> MontgomeryPoint { + let mut x0: MontgomeryPoint = MontgomeryPoint::identity(); + let mut x1: MontgomeryPoint = *self; + + let bits: [i8; 256] = scalar.bits(); + + for i in (0..255).rev() { + let mask: u8 = (bits[i+1] ^ bits[i]) as u8; + + debug_assert!(mask == 0 || mask == 1); + + x0.conditional_swap(&mut x1, mask); + x1 = x0.differential_add(&x1, &self); + x0 = x0.differential_double(); + } + x0.conditional_swap(&mut x1, bits[0] as u8); + x0 + } +} + +impl<'b> MulAssign<&'b Scalar> for MontgomeryPoint { + fn mul_assign(&mut self, scalar: &'b Scalar) { + let result = (self as &MontgomeryPoint) * scalar; + *self = result; + } +} + +impl<'a, 'b> Mul<&'b MontgomeryPoint> for &'a Scalar { + type Output = MontgomeryPoint; + + fn mul(self, point: &'b MontgomeryPoint) -> MontgomeryPoint { + point * &self + } +} + // ------------------------------------------------------------------------ // Tests // ------------------------------------------------------------------------ #[cfg(test)] mod test { + use constants::BASE_COMPRESSED_MONTGOMERY; use edwards::Identity; use super::*; - /// 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]); + use rand::OsRng; /// Test Montgomery conversion against the X25519 basepoint. #[test] fn basepoint_to_montgomery() { assert_eq!(constants::ED25519_BASEPOINT_POINT.compress_montgomery().unwrap(), - BASE_CMPRSSD_MONTY); + BASE_COMPRESSED_MONTGOMERY); } /// Test Montgomery conversion against the X25519 basepoint. #[test] fn basepoint_from_montgomery() { - assert_eq!(BASE_CMPRSSD_MONTY.decompress().unwrap().compress_edwards(), + assert_eq!(BASE_COMPRESSED_MONTGOMERY.decompress_edwards().unwrap().compress_edwards(), constants::BASE_CMPRSSD); } @@ -189,7 +445,7 @@ mod test { let minus_one = FieldElement::minus_one(); let minus_one_bytes = minus_one.to_bytes(); let div_by_zero_u = CompressedMontgomeryU(minus_one_bytes); - assert!(div_by_zero_u.decompress().is_none()); + assert!(div_by_zero_u.decompress_edwards().is_none()); } /// Montgomery compression of the identity point should @@ -199,4 +455,60 @@ mod test { let id = ExtendedPoint::identity(); assert!(id.compress_montgomery().is_none()); } + + #[test] + fn projective_to_affine_roundtrips() { + let p = BASE_COMPRESSED_MONTGOMERY.decompress_montgomery(); + + } + + #[test] + fn differential_double_matches_double() { + let p: ExtendedPoint = constants::ED25519_BASEPOINT_POINT.double(); + let q: MontgomeryPoint = BASE_COMPRESSED_MONTGOMERY.decompress_montgomery().differential_double(); + + assert_eq!(p.compress_montgomery().unwrap(), q.compress_montgomery()); + } + + #[test] + fn differential_add_matches_edwards_model() { + let mut csprng: OsRng = OsRng::new().unwrap(); + + let s1: Scalar = Scalar::random(&mut csprng); + let s2: Scalar = Scalar::random(&mut csprng); + let p1: ExtendedPoint = &constants::ED25519_BASEPOINT_TABLE * &s1; + let p2: ExtendedPoint = &constants::ED25519_BASEPOINT_TABLE * &s2; + let diff: ExtendedPoint = &p1 - &p2; + + let p1m: MontgomeryPoint = p1.to_montgomery().unwrap(); + let p2m: MontgomeryPoint = p2.to_montgomery().unwrap(); + let diffm: MontgomeryPoint = diff.to_montgomery().unwrap(); + + let result = p1m.differential_add(&p2m, &diffm); + + assert_eq!(result.compress_montgomery(), (&p1 + &p2).compress_montgomery().unwrap()); + } + + #[test] + fn ladder_matches_scalarmult() { + let mut csprng: OsRng = OsRng::new().unwrap(); + + let s: Scalar = Scalar::random(&mut csprng); + let p_edwards: ExtendedPoint = &constants::ED25519_BASEPOINT_TABLE * &s; + let p_montgomery: MontgomeryPoint = p_edwards.to_montgomery().unwrap(); + + let expected = &s * &p_edwards; + let result = &s * &p_montgomery; + + assert_eq!(result.compress_montgomery(), expected.compress_montgomery().unwrap()) + } + + #[test] + fn ladder_basepoint_times_two_matches_double() { + let two: Scalar = Scalar::from_u64(2u64); + let result: MontgomeryPoint = &BASE_COMPRESSED_MONTGOMERY.decompress_montgomery() * &two; + let mut expected: ExtendedPoint = constants::ED25519_BASEPOINT_POINT.double(); + + assert_eq!(result.compress_montgomery(), expected.compress_montgomery().unwrap()); + } } From 2939d26b5c42b414b7626f504868948dd1e69171 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 14 Sep 2017 01:54:28 +0000 Subject: [PATCH 02/16] Remove direct compression methods between points in curve models. compress_edwards() is now named compress() and works only on points which are in Edwards form. Similarly, compress_montgomery() is now also called compress(), and it only works on point already in Mongomery form. To switch between forms, use to_montgomery(). Conversion from Montgomery directly to Edwards is not yet implemented. * CHANGE the API requested in https://github.com/isislovecruft/curve25519-dalek/issues/47, hopefully for the better. --- src/decaf.rs | 13 +++- src/edwards.rs | 182 ++++++++++++++++++++++------------------------ src/montgomery.rs | 47 ++++++------ 3 files changed, 121 insertions(+), 121 deletions(-) diff --git a/src/decaf.rs b/src/decaf.rs index f8eb0c4..23511dd 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -194,6 +194,11 @@ impl<'de> Deserialize<'de> for DecafPoint { pub struct DecafPoint(pub ExtendedPoint); impl DecafPoint { + /// Convert this `DecafPoint` to its underlying `ExtendedPoint`. + pub fn to_edwards(&self) -> ExtendedPoint { + self.0 + } + /// Compress in Decaf format. pub fn compress(&self) -> CompressedDecaf { // Q: Do we want to encode twisted or untwisted? @@ -752,7 +757,7 @@ mod test { fn decaf_decompress_id() { let compressed_id = CompressedDecaf::identity(); let id = compressed_id.decompress().unwrap(); - assert_eq!(id.0.compress_edwards(), CompressedEdwardsY::identity()); + assert_eq!(id.to_edwards().compress(), CompressedEdwardsY::identity()); } #[test] @@ -764,11 +769,11 @@ mod test { #[test] fn decaf_basepoint_roundtrip() { let bp_compressed_decaf = constants::DECAF_ED25519_BASEPOINT_POINT.compress(); - let bp_recaf = bp_compressed_decaf.decompress().unwrap().0; + let bp_recaf = bp_compressed_decaf.decompress().unwrap().to_edwards(); // Check that bp_recaf differs from bp by a point of order 4 let diff = &constants::ED25519_BASEPOINT_POINT - &bp_recaf; let diff4 = diff.mult_by_pow_2(4); // XXX this is wrong - assert_eq!(diff4.compress_edwards(), CompressedEdwardsY::identity()); + assert_eq!(diff4.compress(), CompressedEdwardsY::identity()); } #[test] @@ -838,7 +843,7 @@ mod test { for _ in 0..100 { let P = DecafPoint::random(&mut rng); // Check that P is on the curve - assert!(P.0.is_valid()); + assert!(P.to_edwards().is_valid()); // Check that P is in the image of the decaf map P.compress(); } diff --git a/src/edwards.rs b/src/edwards.rs index 9a03295..0b8b333 100644 --- a/src/edwards.rs +++ b/src/edwards.rs @@ -89,7 +89,6 @@ use core::ops::Index; use constants; use field::FieldElement; use scalar::Scalar; -use montgomery::CompressedMontgomeryU; use montgomery::MontgomeryPoint; use subtle::slices_equal; @@ -124,7 +123,6 @@ impl CompressedEdwardsY { } /// Copy this `CompressedEdwardsY` to an array of bytes. - /// XXX is this useful? pub fn to_bytes(&self) -> [u8; 32] { self.0 } @@ -170,7 +168,7 @@ impl Serialize for ExtendedPoint { fn serialize(&self, serializer: S) -> Result where S: Serializer { - serializer.serialize_bytes(self.compress_edwards().as_bytes()) + serializer.serialize_bytes(self.compress().as_bytes()) } } @@ -394,8 +392,8 @@ impl ConditionallyAssignable for ExtendedPoint { impl Equal for ExtendedPoint { fn ct_eq(&self, other: &ExtendedPoint) -> u8 { - slices_equal(self.compress_edwards().as_bytes(), - other.compress_edwards().as_bytes()) + slices_equal(self.compress().as_bytes(), + other.compress().as_bytes()) } } @@ -437,7 +435,7 @@ impl ProjectivePoint { } /// Convert this point to a `CompressedEdwardsY` - pub fn compress_edwards(&self) -> CompressedEdwardsY { + pub fn compress(&self) -> CompressedEdwardsY { let recip = self.Z.invert(); let x = &self.X * &recip; let y = &self.Y * &recip; @@ -448,63 +446,67 @@ impl ProjectivePoint { CompressedEdwardsY(s) } - /// Convert this point to a Montgomery u-coordinate (affine). - /// Note that this discards the sign. + /// Convert this projective point in the Edwards model to its equivalent + /// projective point on the Montgomery form of the curve. /// - /// # Return - /// - `None` if `self` is the identity point; - /// - `Some(FieldElement)` otherwise. + /// Taking the Montgomery curve equation in affine coordinates: /// - fn convert_to_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(u) - } else { - None - } - } - - /// Convert this point to a `CompressedMontgomeryU`. - /// Note that this discards the sign. + ///     E_(A,B) = Bv² = u³ + Au² + u   (1) /// - /// # Return - /// - `None` if `self` is the identity point; - /// - `Some(CompressedMontgomeryU)` otherwise. + /// and given its relations to the coordinates of the Edwards model: /// - pub fn compress_montgomery(&self) -> Option { - let u: Option = self.convert_to_montgomery(); - - if u.is_some() { - Some(CompressedMontgomeryU(u.unwrap().to_bytes())) - } else { - None - } - } - - /// Convert this point to its equivalent on the Montgomery form of - /// the curve, without compressing. + ///     u = (1+y)/(1-y)        (2) + ///     v = (λu)/(x) /// - /// DOCDOC - pub fn to_montgomery(&self) -> Option { - let u: Option = self.convert_to_montgomery(); - - if u.is_some() { - Some(MontgomeryPoint{ U: u.unwrap(), Z: FieldElement::one() }) - } else { - None + /// Converting from affine to projective coordinates in the Montgomery + /// model, we arrive at: + /// + ///     u = (Z+Y)/(Z-Y)        (3) + ///     v = λ * ((Z+Y)/(Z-Y)) * (Z/X) + /// + /// The transition between affine and projective is given by + /// + ///     u → U/W        (4) + ///     v → V/W + /// + /// thus the Montgomery curve equation (1) becomes + /// + ///     E_(A,B) : BV²W = U³ + AU²W + UW² ⊆ 𝗣^2  (5) + /// + /// Here, again, to differentiate from points in the twisted Edwards model, we + /// call the point `(x,y)` in affine coordinates `(u,v)` and similarly in projective + /// space we use `(U:V:W)`. However, since (as per Montgomery's original work) the + /// v-coordinate is superfluous to the definition of the group law, we merely + /// use `(U:W)`. + /// + /// Therefore, the direct translation between projective Montgomery points + /// and projective twisted Edwards points is + /// + ///     (U:W) = (Z+Y:Z-Y) (6) + /// + /// Note, however, that there appears to be an exception where `Z=Y`, + /// since—from equation 2—this would imply that `y=1` (thus causing the + /// denominator to be zero). If this is the case, then it follows from the + /// twisted Edwards curve equation + /// + ///     -x² + y² = 1 + dx²y² (7) + /// + /// that + /// + ///     -x² + 1 = 1 + dx² + /// + /// and, assuming that `d ≠ -1`, + /// + ///     -x² = x² + /// x = 0 + /// + /// Therefore, the only valid point with `y=1` is the twisted Edwards + /// identity point, which correctly becomes `(1:0)`, that is, the identity, + /// in the Montgomery model. + pub fn to_montgomery(&self) -> MontgomeryPoint { + MontgomeryPoint{ + U: &self.Z + &self.Y, + W: &self.Z - &self.Y, } } } @@ -547,25 +549,15 @@ impl ExtendedPoint { } } - /// DOCDOC - pub fn to_montgomery(&self) -> Option { + /// Convert this point to its equivalent on the Montgomery form of the + /// curve. + pub fn to_montgomery(&self) -> MontgomeryPoint { self.to_projective().to_montgomery() } /// Compress this point to `CompressedEdwardsY` format. - pub fn compress_edwards(&self) -> CompressedEdwardsY { - self.to_projective().compress_edwards() - } - - /// 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() + pub fn compress(&self) -> CompressedEdwardsY { + self.to_projective().compress() } } @@ -1342,7 +1334,7 @@ mod test { assert!(bp.is_valid()); // Check that decompression actually gives the correct X coordinate assert_eq!(base_X, bp.X); - assert_eq!(bp.compress_edwards(), constants::BASE_CMPRSSD); + assert_eq!(bp.compress(), constants::BASE_CMPRSSD); } /// Test sign handling in decompression @@ -1365,7 +1357,7 @@ mod test { #[test] fn basepoint_mult_one_vs_basepoint() { let bp = &constants::ED25519_BASEPOINT_TABLE * &Scalar::one(); - let compressed = bp.compress_edwards(); + let compressed = bp.compress(); assert_eq!(compressed, constants::BASE_CMPRSSD); } @@ -1373,7 +1365,7 @@ mod test { #[test] fn basepoint_table_basepoint_function_correct() { let bp = constants::ED25519_BASEPOINT_TABLE.basepoint(); - assert_eq!(bp.compress_edwards(), constants::BASE_CMPRSSD); + assert_eq!(bp.compress(), constants::BASE_CMPRSSD); } /// Test `impl Add for ExtendedPoint` @@ -1382,7 +1374,7 @@ mod test { fn basepoint_plus_basepoint_vs_basepoint2() { let bp = constants::ED25519_BASEPOINT_POINT; let bp_added = &bp + &bp; - assert_eq!(bp_added.compress_edwards(), BASE2_CMPRSSD); + assert_eq!(bp_added.compress(), BASE2_CMPRSSD); } /// Test `impl Add for ExtendedPoint` @@ -1391,7 +1383,7 @@ mod test { fn basepoint_plus_basepoint_projective_niels_vs_basepoint2() { let bp = constants::ED25519_BASEPOINT_POINT; let bp_added = (&bp + &bp.to_projective_niels()).to_extended(); - assert_eq!(bp_added.compress_edwards(), BASE2_CMPRSSD); + assert_eq!(bp_added.compress(), BASE2_CMPRSSD); } /// Test `impl Add for ExtendedPoint` @@ -1401,7 +1393,7 @@ mod test { let bp = constants::ED25519_BASEPOINT_POINT; let bp_affine_niels = bp.to_affine_niels(); let bp_added = (&bp + &bp_affine_niels).to_extended(); - assert_eq!(bp_added.compress_edwards(), BASE2_CMPRSSD); + assert_eq!(bp_added.compress(), BASE2_CMPRSSD); } /// Check that equality of `ExtendedPoints` handles projective @@ -1426,15 +1418,15 @@ mod test { let aB = &constants::ED25519_BASEPOINT_TABLE * &A_SCALAR; let aB_affine_niels = aB.to_affine_niels(); let also_aB = (&ExtendedPoint::identity() + &aB_affine_niels).to_extended(); - assert_eq!( aB.compress_edwards(), - also_aB.compress_edwards()); + assert_eq!( aB.compress(), + also_aB.compress()); } /// Test basepoint_mult versus a known scalar multiple from ed25519.py #[test] fn basepoint_mult_vs_ed25519py() { let aB = &constants::ED25519_BASEPOINT_TABLE * &A_SCALAR; - assert_eq!(aB.compress_edwards(), A_TIMES_BASEPOINT); + assert_eq!(aB.compress(), A_TIMES_BASEPOINT); } /// Test that multiplication by the basepoint order kills the basepoint @@ -1452,20 +1444,20 @@ mod test { let table = EdwardsBasepointTable::create(&constants::ED25519_BASEPOINT_POINT); let aB_1 = &constants::ED25519_BASEPOINT_TABLE * &A_SCALAR; let aB_2 = &table * &A_SCALAR; - assert_eq!(aB_1.compress_edwards(), aB_2.compress_edwards()); + assert_eq!(aB_1.compress(), aB_2.compress()); } /// Test scalar_mult versus a known scalar multiple from ed25519.py #[test] fn scalar_mult_vs_ed25519py() { let aB = &constants::ED25519_BASEPOINT_POINT * &A_SCALAR; - assert_eq!(aB.compress_edwards(), A_TIMES_BASEPOINT); + assert_eq!(aB.compress(), A_TIMES_BASEPOINT); } /// Test basepoint.double() versus the 2*basepoint constant. #[test] fn basepoint_double_vs_basepoint2() { - assert_eq!(constants::ED25519_BASEPOINT_POINT.double().compress_edwards(), + assert_eq!(constants::ED25519_BASEPOINT_POINT.double().compress(), BASE2_CMPRSSD); } @@ -1474,14 +1466,14 @@ mod test { fn basepoint_mult_two_vs_basepoint2() { let mut two_bytes = [0u8; 32]; two_bytes[0] = 2; let bp2 = &constants::ED25519_BASEPOINT_TABLE * &Scalar(two_bytes); - assert_eq!(bp2.compress_edwards(), BASE2_CMPRSSD); + assert_eq!(bp2.compress(), BASE2_CMPRSSD); } /// Check that converting to projective and then back to extended round-trips. #[test] fn basepoint_projective_extended_round_trip() { assert_eq!(constants::ED25519_BASEPOINT_POINT - .to_projective().to_extended().compress_edwards(), + .to_projective().to_extended().compress(), constants::BASE_CMPRSSD); } @@ -1489,7 +1481,7 @@ mod test { #[test] fn basepoint16_vs_mult_by_pow_2_4() { let bp16 = constants::ED25519_BASEPOINT_POINT.mult_by_pow_2(4); - assert_eq!(bp16.compress_edwards(), BASE16_CMPRSSD); + assert_eq!(bp16.compress(), BASE16_CMPRSSD); } /// Test that the conditional assignment trait works for AffineNielsPoints. @@ -1517,7 +1509,7 @@ mod test { #[test] fn compressed_identity() { - assert_eq!(ExtendedPoint::identity().compress_edwards(), + assert_eq!(ExtendedPoint::identity().compress(), CompressedEdwardsY::identity()); } @@ -1555,7 +1547,7 @@ mod test { let P1 = &G * &s; let P2 = &s * &G; - assert!(P1.compress_edwards().to_bytes() == P2.compress_edwards().to_bytes()); + assert!(P1.compress().to_bytes() == P2.compress().to_bytes()); } #[test] @@ -1579,7 +1571,7 @@ mod test { fn double_scalar_mult_basepoint_vs_ed25519py() { let A = A_TIMES_BASEPOINT.decompress().unwrap(); let result = vartime::double_scalar_mult_basepoint(&A_SCALAR, &A, &B_SCALAR); - assert_eq!(result.compress_edwards(), DOUBLE_SCALAR_MULT_RESULT); + assert_eq!(result.compress(), DOUBLE_SCALAR_MULT_RESULT); } #[test] @@ -1589,7 +1581,7 @@ mod test { &[A_SCALAR, B_SCALAR], &[A, constants::ED25519_BASEPOINT_POINT] ); - assert_eq!(result.compress_edwards(), DOUBLE_SCALAR_MULT_RESULT); + assert_eq!(result.compress(), DOUBLE_SCALAR_MULT_RESULT); } #[test] @@ -1604,7 +1596,7 @@ mod test { &[A, constants::ED25519_BASEPOINT_POINT] ); - assert_eq!(result_vartime.compress_edwards(), result_consttime.compress_edwards()); + assert_eq!(result_vartime.compress(), result_consttime.compress()); } } @@ -1616,7 +1608,7 @@ mod test { fn serde_cbor_basepoint_roundtrip() { let output = serde_cbor::to_vec(&constants::ED25519_BASEPOINT_POINT).unwrap(); let parsed: ExtendedPoint = serde_cbor::from_slice(&output).unwrap(); - assert_eq!(parsed.compress_edwards(), constants::BASE_CMPRSSD); + assert_eq!(parsed.compress(), constants::BASE_CMPRSSD); } #[test] @@ -1652,7 +1644,7 @@ mod bench { #[bench] fn edwards_compress(b: &mut Bencher) { let B = &constants::ED25519_BASEPOINT_POINT; - b.iter(|| B.compress_edwards()); + b.iter(|| B.compress()); } #[bench] diff --git a/src/montgomery.rs b/src/montgomery.rs index 0b2c16a..f2e0268 100644 --- a/src/montgomery.rs +++ b/src/montgomery.rs @@ -119,7 +119,7 @@ impl CompressedMontgomeryU { /// # Returns /// /// A projective `MontgomeryPoint` corresponding to this compressed point. - pub fn decompress_montgomery(&self) -> MontgomeryPoint { + pub fn decompress(&self) -> MontgomeryPoint { MontgomeryPoint{ // XXX is it a problem here if we're not using a canonical encoding? —isis U: FieldElement::from_bytes(&self.0), @@ -257,8 +257,8 @@ impl Identity for MontgomeryPoint { /// `1` if the points are equal, and `0` otherwise. impl Equal for MontgomeryPoint { fn ct_eq(&self, that: &MontgomeryPoint) -> u8 { - slices_equal(self.compress_montgomery().as_bytes(), - that.compress_montgomery().as_bytes()) + slices_equal(self.compress().as_bytes(), + that.compress().as_bytes()) } } @@ -301,7 +301,7 @@ impl MontgomeryPoint { /// # Returns /// /// A `CompressedMontgomeryU`. - pub fn compress_montgomery(&self) -> CompressedMontgomeryU { + pub fn compress(&self) -> CompressedMontgomeryU { let u_affine: FieldElement = &self.U * &self.W.invert(); CompressedMontgomeryU(u_affine.to_bytes()) @@ -425,15 +425,15 @@ mod test { /// Test Montgomery conversion against the X25519 basepoint. #[test] fn basepoint_to_montgomery() { - assert_eq!(constants::ED25519_BASEPOINT_POINT.compress_montgomery().unwrap(), + assert_eq!(constants::ED25519_BASEPOINT_POINT.to_montgomery().compress(), BASE_COMPRESSED_MONTGOMERY); } /// Test Montgomery conversion against the X25519 basepoint. #[test] fn basepoint_from_montgomery() { - assert_eq!(BASE_COMPRESSED_MONTGOMERY.decompress_edwards().unwrap().compress_edwards(), - constants::BASE_CMPRSSD); + assert_eq!(BASE_COMPRESSED_MONTGOMERY, + constants::BASE_CMPRSSD.decompress().unwrap().to_montgomery().compress()); } /// If u = -1, then v^2 = u*(u^2+486662*u+1) = 486660. @@ -448,26 +448,28 @@ mod test { assert!(div_by_zero_u.decompress_edwards().is_none()); } - /// Montgomery compression of the identity point should - /// fail (it's sent to infinity). + /// Montgomery compression of the identity point should not fail (since the + /// mapping in `ProjectivePoint.to_montgomery()` should be valid for the + /// identity. #[test] fn identity_to_monty() { let id = ExtendedPoint::identity(); - assert!(id.compress_montgomery().is_none()); + assert_eq!(id.to_montgomery().compress(), MontgomeryPoint::identity().compress()); } #[test] fn projective_to_affine_roundtrips() { - let p = BASE_COMPRESSED_MONTGOMERY.decompress_montgomery(); + assert_eq!(BASE_COMPRESSED_MONTGOMERY.decompress().compress(), + BASE_COMPRESSED_MONTGOMERY); } #[test] fn differential_double_matches_double() { let p: ExtendedPoint = constants::ED25519_BASEPOINT_POINT.double(); - let q: MontgomeryPoint = BASE_COMPRESSED_MONTGOMERY.decompress_montgomery().differential_double(); + let q: MontgomeryPoint = BASE_COMPRESSED_MONTGOMERY.decompress().differential_double(); - assert_eq!(p.compress_montgomery().unwrap(), q.compress_montgomery()); + assert_eq!(p.to_montgomery().compress(), q.compress()); } #[test] @@ -480,13 +482,13 @@ mod test { let p2: ExtendedPoint = &constants::ED25519_BASEPOINT_TABLE * &s2; let diff: ExtendedPoint = &p1 - &p2; - let p1m: MontgomeryPoint = p1.to_montgomery().unwrap(); - let p2m: MontgomeryPoint = p2.to_montgomery().unwrap(); - let diffm: MontgomeryPoint = diff.to_montgomery().unwrap(); + let p1m: MontgomeryPoint = p1.to_montgomery(); + let p2m: MontgomeryPoint = p2.to_montgomery(); + let diffm: MontgomeryPoint = diff.to_montgomery(); let result = p1m.differential_add(&p2m, &diffm); - assert_eq!(result.compress_montgomery(), (&p1 + &p2).compress_montgomery().unwrap()); + assert_eq!(result.compress(), (&p1 + &p2).to_montgomery().compress()); } #[test] @@ -495,20 +497,21 @@ mod test { let s: Scalar = Scalar::random(&mut csprng); let p_edwards: ExtendedPoint = &constants::ED25519_BASEPOINT_TABLE * &s; - let p_montgomery: MontgomeryPoint = p_edwards.to_montgomery().unwrap(); + let p_montgomery: MontgomeryPoint = p_edwards.to_montgomery(); let expected = &s * &p_edwards; let result = &s * &p_montgomery; - assert_eq!(result.compress_montgomery(), expected.compress_montgomery().unwrap()) + assert_eq!(result.compress(), expected.to_montgomery().compress()) } #[test] fn ladder_basepoint_times_two_matches_double() { let two: Scalar = Scalar::from_u64(2u64); - let result: MontgomeryPoint = &BASE_COMPRESSED_MONTGOMERY.decompress_montgomery() * &two; - let mut expected: ExtendedPoint = constants::ED25519_BASEPOINT_POINT.double(); + let result: MontgomeryPoint = &BASE_COMPRESSED_MONTGOMERY.decompress() * &two; + let expected: ExtendedPoint = constants::ED25519_BASEPOINT_POINT.double(); + + assert_eq!(result.compress(), expected.to_montgomery().compress()); - assert_eq!(result.compress_montgomery(), expected.compress_montgomery().unwrap()); } } From 6be10341e2003648344661a72b28a39157ab41e7 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 14 Sep 2017 02:08:11 +0000 Subject: [PATCH 03/16] Add benchmarks for Mongomery point (de)compression and laddering. --- src/montgomery.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/montgomery.rs b/src/montgomery.rs index f2e0268..1f4f52d 100644 --- a/src/montgomery.rs +++ b/src/montgomery.rs @@ -512,6 +512,35 @@ mod test { let expected: ExtendedPoint = constants::ED25519_BASEPOINT_POINT.double(); assert_eq!(result.compress(), expected.to_montgomery().compress()); - + } +} + +#[cfg(all(test, feature = "bench"))] +mod bench { + use rand::OsRng; + use constants::ED25519_BASEPOINT_TABLE; + use constants::BASE_COMPRESSED_MONTGOMERY; + use test::Bencher; + use super::*; + + #[bench] + fn montgomery_decompress(b: &mut Bencher) { + b.iter(| | BASE_COMPRESSED_MONTGOMERY.decompress()); + } + + #[bench] + fn montgomery_compress(b: &mut Bencher) { + let p: MontgomeryPoint = BASE_COMPRESSED_MONTGOMERY.decompress(); + + b.iter(| | p.compress()); + } + + #[bench] + fn montgomery_ladder(b: &mut Bencher) { + let mut csprng: OsRng = OsRng::new().unwrap(); + let s: Scalar = Scalar::random(&mut csprng); + let p: MontgomeryPoint = (&Scalar::random(&mut csprng) * &ED25519_BASEPOINT_TABLE).to_montgomery(); + + b.iter(| | &s * &p); } } From d86fcd4e17e8a6cdb3913d3a2fa82a91d8403801 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 14 Sep 2017 02:40:18 +0000 Subject: [PATCH 04/16] Use subtle branch with ConditionallySwappable trait for now. --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 8c22506..c1cd666 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,8 @@ version = "0.6" [dependencies.subtle] version = "^0.2" default-features = false +git = "https://github.com/isislovecruft/subtle" +branch = "feature/conditional-swap_r1" [dependencies.generic-array] # same version that digest depends on From c08591a7d81da1f5471e399f571621811351b723 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Wed, 4 Oct 2017 04:43:04 +0000 Subject: [PATCH 05/16] Improve documentation for Mongomery code. --- src/montgomery.rs | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/montgomery.rs b/src/montgomery.rs index 1f4f52d..1020a24 100644 --- a/src/montgomery.rs +++ b/src/montgomery.rs @@ -35,7 +35,9 @@ use field::FieldElement; use edwards::{ExtendedPoint, CompressedEdwardsY}; use scalar::Scalar; -// XXX move these to a common "traits" or "group" module? —isis +// XXX Move these to a common "group" module? At the same time, we should +// XXX probably make a `trait Group` once const generics are implemented in +// XXX Rust. —isis use edwards::{Identity, ValidityCheck}; use subtle::slices_equal; @@ -54,8 +56,6 @@ use subtle::Mask; /// 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]); @@ -211,7 +211,7 @@ impl CompressedMontgomeryU { /// Here, again, to differentiate from points in the twisted Edwards model, we /// call the point `(x,y)` in affine coordinates `(u,v)` and similarly in projective /// space we use `(U:V:W)`. However, since (as per Montgomery's original work) the -/// v-coordinate is superfluous to the definition of the group law, we merely +/// v-coordinate is superfluous for the purposes of scalar multiplication, we merely /// use `(U:W)`. #[derive(Copy, Clone, Debug)] #[allow(missing_docs)] @@ -224,9 +224,9 @@ pub struct MontgomeryPoint{ /// /// In projective coordinates, the quotient map `x : E (A,B) → E/<⦵> = 𝗣¹` is /// -///     ⎧ (x_P:1) if P = (x_P:y_P:1) , -///     x : P ↦ ⎨ -///     ⎩ (1:0) if P = O = (0:1:0) . +///     ⎧ (x_P:1) if P = (x_P:y_P:1) , +///     x : P ↦ ⎨ +///     ⎩ (1:0) if P = O = (0:1:0) . /// /// We emphasize that the formula `x((U: V : W)) = (U : W)` only holds on the /// open subset of `E_(A,B)` where `W ≠ 0`; it does not extend to the point @@ -325,11 +325,12 @@ impl MontgomeryPoint { /// results of this method are not correct, but instead result in `(0:0)` /// (an invalid projective point in the Montgomery model). /// - // XXX API-wise, do we care that doubling is degenerate, or should we allow - // the user to do a stupid and inefficient (albeit not incorrect) thing? + /// The doubling case is degenerate, in that using this method to accomplish + /// point doubling is less efficient than using `differential_double()`. fn differential_add(&self, that: &MontgomeryPoint, difference: &MontgomeryPoint) -> MontgomeryPoint { - // debug_assert!(self.ct_eq(that) != 1); // The doubling case is degenerate + // XXX Do we want these debug assertions? We would need to implement + // XXX is_two_torsion_point(). —isis // debug_assert!(!difference.is_identity()); // P ⦵ Q ∉ {O,T} // debug_assert!(!difference.is_two_torsion_point()); @@ -342,13 +343,20 @@ impl MontgomeryPoint { } } - /// Differential doubling for single-coordinate Montgomery points. + /// Pseudo-doubling for single-coordinate Montgomery points. /// - /// DOCDOC + /// Given a Montgomery U-coordinate of a point `P`, compute the + /// U-coordinate given by + /// + ///     differential_double: x(P) ⟼ x([2]P) /// /// # Returns /// - /// A Montgomery point. + /// A Montgomery point equal to doubling this one. + /// + // XXX It seems possible that combining the differential_add() and + // XXX differential_double() methods would save a non-trivial amount of + // XXX computation in the ladder. —isis fn differential_double(&self) -> MontgomeryPoint { let mut v1: FieldElement; let v2: FieldElement; @@ -370,8 +378,9 @@ impl MontgomeryPoint { /// Multiply this `MontgomeryPoint` by a `Scalar`. /// -/// DOCDOC -/// explain montgomery laddering +/// The reader is refered to §5.3 of ["Montgomery Curves and Their Arithmetic" +/// by Craig Costello and Benjamin Smith](https://eprint.iacr.org/2017/212.pdf) +/// for an overview of side-channel-free Montgomery laddering algorithms. impl<'a, 'b> Mul<&'b Scalar> for &'a MontgomeryPoint { type Output = MontgomeryPoint; From 49f8781681016b0ac626dcbe8e007e15509030d5 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 5 Oct 2017 00:49:49 +0000 Subject: [PATCH 06/16] Revert "Use subtle branch with ConditionallySwappable trait for now." This reverts commit d86fcd4e17e8a6cdb3913d3a2fa82a91d8403801. --- Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c1cd666..8c22506 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,8 +35,6 @@ version = "0.6" [dependencies.subtle] version = "^0.2" default-features = false -git = "https://github.com/isislovecruft/subtle" -branch = "feature/conditional-swap_r1" [dependencies.generic-array] # same version that digest depends on From cb98a23230b94ff96693b4589a9a00c2c042ccbd Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 5 Oct 2017 00:51:42 +0000 Subject: [PATCH 07/16] Change Cargo.toml to use subtle-0.3.0. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8c22506..f8a879e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ version = "0.3" version = "0.6" [dependencies.subtle] -version = "^0.2" +version = "^0.3" default-features = false [dependencies.generic-array] From d39cb275c5a8d8c315d916760753dc9604a015fd Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 5 Oct 2017 00:56:40 +0000 Subject: [PATCH 08/16] Remove DecafPoint.to_edwards() method. --- src/decaf.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/decaf.rs b/src/decaf.rs index 23511dd..9bc2c8a 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -189,16 +189,11 @@ impl<'de> Deserialize<'de> for DecafPoint { /// A point in a prime-order group. /// -/// XXX think about how this API should work +// XXX think about how this API should work #[derive(Copy, Clone)] pub struct DecafPoint(pub ExtendedPoint); impl DecafPoint { - /// Convert this `DecafPoint` to its underlying `ExtendedPoint`. - pub fn to_edwards(&self) -> ExtendedPoint { - self.0 - } - /// Compress in Decaf format. pub fn compress(&self) -> CompressedDecaf { // Q: Do we want to encode twisted or untwisted? @@ -757,7 +752,7 @@ mod test { fn decaf_decompress_id() { let compressed_id = CompressedDecaf::identity(); let id = compressed_id.decompress().unwrap(); - assert_eq!(id.to_edwards().compress(), CompressedEdwardsY::identity()); + assert_eq!(id.0.compress(), CompressedEdwardsY::identity()); } #[test] @@ -769,7 +764,7 @@ mod test { #[test] fn decaf_basepoint_roundtrip() { let bp_compressed_decaf = constants::DECAF_ED25519_BASEPOINT_POINT.compress(); - let bp_recaf = bp_compressed_decaf.decompress().unwrap().to_edwards(); + let bp_recaf = bp_compressed_decaf.decompress().unwrap().0; // Check that bp_recaf differs from bp by a point of order 4 let diff = &constants::ED25519_BASEPOINT_POINT - &bp_recaf; let diff4 = diff.mult_by_pow_2(4); // XXX this is wrong @@ -843,7 +838,7 @@ mod test { for _ in 0..100 { let P = DecafPoint::random(&mut rng); // Check that P is on the curve - assert!(P.to_edwards().is_valid()); + assert!(P.0.is_valid()); // Check that P is in the image of the decaf map P.compress(); } From 7e4fd5677cde54837e2583e3662a1bccfbc49d97 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 5 Oct 2017 01:26:15 +0000 Subject: [PATCH 09/16] Add tests and benchmark for MontgomeryPoint.ct_eq(). --- src/montgomery.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/montgomery.rs b/src/montgomery.rs index 1020a24..6c32d81 100644 --- a/src/montgomery.rs +++ b/src/montgomery.rs @@ -425,6 +425,7 @@ impl<'a, 'b> Mul<&'b MontgomeryPoint> for &'a Scalar { #[cfg(test)] mod test { + use constants::ED25519_BASEPOINT_TABLE; use constants::BASE_COMPRESSED_MONTGOMERY; use edwards::Identity; use super::*; @@ -481,6 +482,26 @@ mod test { assert_eq!(p.to_montgomery().compress(), q.compress()); } + #[test] + fn montgomery_ct_eq_ne() { + let mut csprng: OsRng = OsRng::new().unwrap(); + let s1: Scalar = Scalar::random(&mut csprng); + let s2: Scalar = Scalar::random(&mut csprng); + let p1: MontgomeryPoint = (&s1 * &ED25519_BASEPOINT_TABLE).to_montgomery(); + let p2: MontgomeryPoint = (&s2 * &ED25519_BASEPOINT_TABLE).to_montgomery(); + + assert_eq!(p1.ct_eq(&p2), 0); + } + + #[test] + fn montgomery_ct_eq_eq() { + let mut csprng: OsRng = OsRng::new().unwrap(); + let s1: Scalar = Scalar::random(&mut csprng); + let p1: MontgomeryPoint = (&s1 * &ED25519_BASEPOINT_TABLE).to_montgomery(); + + assert_eq!(p1.ct_eq(&p1), 1); + } + #[test] fn differential_add_matches_edwards_model() { let mut csprng: OsRng = OsRng::new().unwrap(); @@ -532,6 +553,17 @@ mod bench { use test::Bencher; use super::*; + #[bench] + fn montgomery_ct_eq(b: &mut Bencher) { + let mut csprng: OsRng = OsRng::new().unwrap(); + let s1: Scalar = Scalar::random(&mut csprng); + let s2: Scalar = Scalar::random(&mut csprng); + let p1: MontgomeryPoint = (&s1 * &ED25519_BASEPOINT_TABLE).to_montgomery(); + let p2: MontgomeryPoint = (&s2 * &ED25519_BASEPOINT_TABLE).to_montgomery(); + + b.iter(| | p1.ct_eq(&p2)) + } + #[bench] fn montgomery_decompress(b: &mut Bencher) { b.iter(| | BASE_COMPRESSED_MONTGOMERY.decompress()); From 5e6e6c3fa824ea0a9b7678113e25325324df0b04 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 5 Oct 2017 01:32:44 +0000 Subject: [PATCH 10/16] Eliminate extra inversions in MontgomeryPoint.ct_eq(). --- src/montgomery.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/montgomery.rs b/src/montgomery.rs index 6c32d81..ba3d556 100644 --- a/src/montgomery.rs +++ b/src/montgomery.rs @@ -257,8 +257,9 @@ impl Identity for MontgomeryPoint { /// `1` if the points are equal, and `0` otherwise. impl Equal for MontgomeryPoint { fn ct_eq(&self, that: &MontgomeryPoint) -> u8 { - slices_equal(self.compress().as_bytes(), - that.compress().as_bytes()) + // (U_P:W_P) = (U_Q:W_Q) iff U_P * W_Q == U_Q * W_P, + // since U_P/W_P == U_Q/W_Q. + (&self.U * &that.W).ct_eq(&(&self.W * &that.U)) } } From 7b378ada6bfacb237cfae2d24a0df4046a171959 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 5 Oct 2017 01:46:42 +0000 Subject: [PATCH 11/16] Rephrase doc note on exceptional projective Montgomery points. --- src/montgomery.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/montgomery.rs b/src/montgomery.rs index ba3d556..c42fb42 100644 --- a/src/montgomery.rs +++ b/src/montgomery.rs @@ -267,9 +267,10 @@ impl Equal for MontgomeryPoint { /// /// # Note /// -/// All points, except for `(X:W) = (0:0)`, are valid, since the projective -/// model is linear through the origin and is comprised by all `X` in -/// ℤ/(2²⁵⁵-19). +/// All projective points, except for `(X:W) = (0:0)`, are valid, since the +/// projective model is linear through the origin and is comprised by all `X` in +/// ℤ/(2²⁵⁵-19), thus `(0:0)` is the only element in Fₚ² which is not a +/// projective point. /// /// # Returns /// From ca5b58c2b75d48656d142bb8c686369448f33945 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 5 Oct 2017 01:54:26 +0000 Subject: [PATCH 12/16] Clarify doc note on degenerate cases for differential addition. --- src/montgomery.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/montgomery.rs b/src/montgomery.rs index c42fb42..bb6a9ad 100644 --- a/src/montgomery.rs +++ b/src/montgomery.rs @@ -327,8 +327,8 @@ impl MontgomeryPoint { /// results of this method are not correct, but instead result in `(0:0)` /// (an invalid projective point in the Montgomery model). /// - /// The doubling case is degenerate, in that using this method to accomplish - /// point doubling is less efficient than using `differential_double()`. + /// The doubling case is degenerate, in that `P ⦵ Q ∉ {O,T}`, where `T` is + /// the two torsion point. fn differential_add(&self, that: &MontgomeryPoint, difference: &MontgomeryPoint) -> MontgomeryPoint { // XXX Do we want these debug assertions? We would need to implement From 9da24d8afaf0b9443664604d3272dd336cf99c88 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 5 Oct 2017 02:15:28 +0000 Subject: [PATCH 13/16] Add test for Montgomery ladder with a scalar with high bit set. --- src/montgomery.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/montgomery.rs b/src/montgomery.rs index bb6a9ad..37f447a 100644 --- a/src/montgomery.rs +++ b/src/montgomery.rs @@ -545,6 +545,19 @@ mod test { assert_eq!(result.compress(), expected.to_montgomery().compress()); } + + #[test] + #[should_panic(expected = "assertion failed: self[31] <= 127")] + fn ladder_matches_scalarmult_with_scalar_high_bit_set() { + let mut s: Scalar = Scalar::one(); + + s[31] = 255; + + let result: MontgomeryPoint = &BASE_COMPRESSED_MONTGOMERY.decompress() * &s; + let expected: ExtendedPoint = &constants::ED25519_BASEPOINT_TABLE * &s; + + assert_eq!(result.compress(), expected.to_montgomery().compress()) + } } #[cfg(all(test, feature = "bench"))] From 29f9090411e2e9d3d06e8a916a38ddcba67e7b2d Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 5 Oct 2017 02:23:34 +0000 Subject: [PATCH 14/16] Fix two typos in docstrings for constants. --- src/constants_32bit.rs | 2 +- src/constants_64bit.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/constants_32bit.rs b/src/constants_32bit.rs index 02b629b..bf46fe5 100644 --- a/src/constants_32bit.rs +++ b/src/constants_32bit.rs @@ -75,7 +75,7 @@ pub const HALF: FieldElement32 = FieldElement32([ pub const A: FieldElement32 = FieldElement32([ 486662, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); -/// `APLUS2_OVER_FOUR` is (A+2)/4. (This is used internally within Montgomery laddering.) +/// `APLUS2_OVER_FOUR` is (A+2)/4. (This is used internally within the Montgomery ladder.) pub const APLUS2_OVER_FOUR: FieldElement32 = FieldElement32([121666, 0, 0, 0, 0, 0, 0, 0, 0, 0]); /// `SQRT_MINUS_A` is sqrt(-486662) diff --git a/src/constants_64bit.rs b/src/constants_64bit.rs index 8beee81..ba7b2d9 100644 --- a/src/constants_64bit.rs +++ b/src/constants_64bit.rs @@ -54,7 +54,7 @@ pub const HALF: FieldElement64 = FieldElement64([2251799813685239, 2251799813685 /// In Montgomery form y² = x³+Ax²+x, Curve25519 has A=486662. pub const A: FieldElement64 = FieldElement64([486662, 0, 0, 0, 0]); -/// `APLUS2_OVER_FOUR` is (A+2)/4. (This is used internally within Montgomery laddering.) +/// `APLUS2_OVER_FOUR` is (A+2)/4. (This is used internally within the Montgomery ladder.) pub const APLUS2_OVER_FOUR: FieldElement64 = FieldElement64([121666, 0, 0, 0, 0]); /// `SQRT_MINUS_A` is sqrt(-486662) From 4965238b5a4e3a16ec0009a52f5af0e0943e72dd Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 5 Oct 2017 02:23:55 +0000 Subject: [PATCH 15/16] Removed now unused subtle import from montgomery module. --- src/montgomery.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/montgomery.rs b/src/montgomery.rs index 37f447a..5c34114 100644 --- a/src/montgomery.rs +++ b/src/montgomery.rs @@ -40,7 +40,6 @@ use scalar::Scalar; // XXX Rust. —isis use edwards::{Identity, ValidityCheck}; -use subtle::slices_equal; use subtle::ConditionallyAssignable; use subtle::ConditionallySwappable; use subtle::Equal; From d39e47ff116cf1c6c47c84b7354225772e5f2d97 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 5 Oct 2017 02:27:27 +0000 Subject: [PATCH 16/16] Remove comment on non-canonical encodings in CompressedMontgomeryU.decompress(). --- src/montgomery.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/montgomery.rs b/src/montgomery.rs index 5c34114..9ba71ed 100644 --- a/src/montgomery.rs +++ b/src/montgomery.rs @@ -120,7 +120,6 @@ impl CompressedMontgomeryU { /// A projective `MontgomeryPoint` corresponding to this compressed point. pub fn decompress(&self) -> MontgomeryPoint { MontgomeryPoint{ - // XXX is it a problem here if we're not using a canonical encoding? —isis U: FieldElement::from_bytes(&self.0), W: FieldElement::one(), }