From cb4ff3097c7ad340e805c07b5ad1a2c1c77330dc Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Wed, 1 Feb 2017 00:29:44 -0500 Subject: [PATCH 01/35] Add FieldElement::is_zero() --- src/field.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/field.rs b/src/field.rs index 7d5bc44..f26108d 100644 --- a/src/field.rs +++ b/src/field.rs @@ -521,6 +521,15 @@ impl FieldElement { (bytes[0] & 1) as i32 } + /// Determine if this `FieldElement` is zero. + /// + /// # Return + /// + /// If zero, return `1u8`. Otherwise, return `0u8`. + pub fn is_zero(&self) -> u8 { + return 1u8 & (!self.is_nonzero()); + } + /// Determine if this `FieldElement` is non-zero. /// /// # Return From 79afc6bd0fa6da5fd38576c7e0541280d84bba80 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 29 Jan 2017 19:19:16 -0500 Subject: [PATCH 02/35] Add debugging checks to test if points are on the curve --- src/curve.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/curve.rs b/src/curve.rs index 9dc6f22..7456e4f 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -271,6 +271,38 @@ impl Identity for PreComputedPoint { } } +// ------------------------------------------------------------------------ +// Validity checks (for debugging, not CT) +// ------------------------------------------------------------------------ + +/// Trait for checking whether a point is on the curve +pub trait ValidityCheck { + /// Checks whether the point is on the curve. Not CT. + fn is_valid(&self) -> bool; +} + +impl ValidityCheck for ProjectivePoint { + fn is_valid(&self) -> bool { + // Curve equation is -x^2 + y^2 = 1 + d*x^2*y^2, + // homogenized as (-X^2 + Y^2)*Z^2 = Z^4 + d*X^2*Y^2 + let XX = self.X.square(); + let YY = self.Y.square(); + let ZZ = self.Z.square(); + let ZZZZ = ZZ.square(); + let lhs = &(&YY - &XX) * &ZZ; + let rhs = &ZZZZ + &(&constants::d * &(&XX * &YY)); + + lhs == rhs + } +} + +impl ValidityCheck for ExtendedPoint { + // XXX this should also check that T is correct + fn is_valid(&self) -> bool { + self.to_projective().is_valid() + } +} + // ------------------------------------------------------------------------ // Constant-time assignment // ------------------------------------------------------------------------ @@ -922,6 +954,8 @@ mod test { let base_X = FieldElement::from_bytes(&BASE_X_COORD_BYTES); let bp = BASE_CMPRSSD.decompress().unwrap(); let bp2 = BASE2_CMPRSSD.decompress().unwrap(); + assert!( bp.is_valid()); + assert!(bp2.is_valid()); let compressed = bp.compress(); let compressed2 = bp2.compress(); // Check that decompression actually gives the correct X coordinate From e1feb653be748a93b93b4936d3cfa4fb613909ea Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Fri, 13 Jan 2017 13:19:08 -0500 Subject: [PATCH 03/35] Add constant value of 1/2 (mod p) --- src/constants.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/constants.rs b/src/constants.rs index 71417d8..97e7fe7 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -32,6 +32,9 @@ pub const d2: FieldElement = FieldElement([ pub const SQRT_M1: FieldElement = FieldElement([ -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482, ]); +/// Precomputed value of 1/2 (mod p). +pub const HALF: FieldElement = FieldElement([ + 10, 0, 0, 0, 0, 0, 0, 0, 0, -16777216, ]); /// In Montgomery form y² = x³+Ax²+x, Curve25519 has A=486662. pub const A: FieldElement = FieldElement([ @@ -1480,6 +1483,13 @@ mod test { use curve::PreComputedPoint; use constants; + #[test] + fn test_half() { + let one = FieldElement([1,0,0,0,0,0,0,0,0,0]); + let two = FieldElement([2,0,0,0,0,0,0,0,0,0]); + assert_eq!(one, &two * &constants::HALF); + } + #[test] /// Test that SQRT_M1 is a square root of -1 fn test_sqrt_minus_one() { From fb0c431184a1da6b61bca51b4045a824840d591c Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 19 Feb 2017 18:09:02 -0500 Subject: [PATCH 04/35] Add constant for MSQRT_M1, equal to -SQRT_M1 --- src/constants.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 97e7fe7..09f1772 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -29,9 +29,18 @@ pub const d: FieldElement = FieldElement([ pub const d2: FieldElement = FieldElement([ -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199, ]); + +/// Precomputed value of one of the square roots of -1 (mod p) pub const SQRT_M1: FieldElement = FieldElement([ -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482, ]); + +/// Precomputed value of the other square root of -1 (mod p), +/// i.e., MSQRT_M1 = -SQRT_M1. +pub const MSQRT_M1: FieldElement = FieldElement([ + 32595792, 7943725, -9377950, -3500415, -12389472, + 272473, 25146209, 2005654, -326686, -11406482, ]); + /// Precomputed value of 1/2 (mod p). pub const HALF: FieldElement = FieldElement([ 10, 0, 0, 0, 0, 0, 0, 0, 0, -16777216, ]); @@ -1491,11 +1500,13 @@ mod test { } #[test] - /// Test that SQRT_M1 is a square root of -1 + /// Test that SQRT_M1 and MSQRT_M1 are square roots of -1 fn test_sqrt_minus_one() { let minus_one = FieldElement([-1,0,0,0,0,0,0,0,0,0]); let sqrt_m1_sq = &constants::SQRT_M1 * &constants::SQRT_M1; - assert_eq!(minus_one, sqrt_m1_sq); + let msqrt_m1_sq = &constants::MSQRT_M1 * &constants::MSQRT_M1; + assert_eq!(minus_one, sqrt_m1_sq); + assert_eq!(minus_one, msqrt_m1_sq); } #[test] From 6fd30289569f72df0ba2d27ae3b7048cdbf354d7 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 31 Jan 2017 17:19:34 -0500 Subject: [PATCH 05/35] Add constant for 4*d --- src/constants.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/constants.rs b/src/constants.rs index 09f1772..1607258 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -29,6 +29,9 @@ pub const d: FieldElement = FieldElement([ pub const d2: FieldElement = FieldElement([ -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199, ]); +pub const d4: FieldElement = FieldElement([ + 23454405, -11679213, 5618422, -5756869, 458917, + -1596832, -25103633, -12990876, -7676928, -14666033 ]); /// Precomputed value of one of the square roots of -1 (mod p) pub const SQRT_M1: FieldElement = FieldElement([ @@ -1520,6 +1523,13 @@ mod test { assert_eq!(d2, constants::d2); } + #[test] + fn test_d4() { + let mut four = FieldElement::zero(); + four[0] = 4; + assert_eq!(&constants::d * &four, constants::d4); + } + /// Test the values in the lookup table of precomputed multiples /// of the basepoint. #[test] From 363ea405d4ef3e26e20008ed9ab8aecb35350668 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 19 Feb 2017 19:02:55 -0500 Subject: [PATCH 06/35] Add constant for a-d = -1-d --- src/constants.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/constants.rs b/src/constants.rs index 1607258..7c0e664 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -32,6 +32,9 @@ pub const d2: FieldElement = FieldElement([ pub const d4: FieldElement = FieldElement([ 23454405, -11679213, 5618422, -5756869, 458917, -1596832, -25103633, -12990876, -7676928, -14666033 ]); +pub const a_minus_d: FieldElement = FieldElement([ + 10913609, -13857413, 15372611, -6949391, -114729, + 8787816, 6275908, 3247719, 18696448, 12055116, ]); /// Precomputed value of one of the square roots of -1 (mod p) pub const SQRT_M1: FieldElement = FieldElement([ @@ -1530,6 +1533,13 @@ mod test { assert_eq!(&constants::d * &four, constants::d4); } + #[test] + fn test_a_minus_d() { + let a = FieldElement([-1,0,0,0,0,0,0,0,0,0]); + let a_minus_d = &a - &constants::d; + assert_eq!(a_minus_d, constants::a_minus_d); + } + /// Test the values in the lookup table of precomputed multiples /// of the basepoint. #[test] From 82041c2c67f7181dfb4be49cecc9429fd83f8c1a Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 19 Feb 2017 19:50:36 -0500 Subject: [PATCH 07/35] Add constant for bytes of (p-1)/2. --- src/constants.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/constants.rs b/src/constants.rs index 7c0e664..a5a2740 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -36,6 +36,13 @@ pub const a_minus_d: FieldElement = FieldElement([ 10913609, -13857413, 15372611, -6949391, -114729, 8787816, 6275908, 3247719, 18696448, 12055116, ]); +/// (p-1)/2, in little-endian bytes. +pub const HALF_P_MINUS_1_BYTES: [u8; 32] = + [0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f]; + /// Precomputed value of one of the square roots of -1 (mod p) pub const SQRT_M1: FieldElement = FieldElement([ -32595792, -7943725, 9377950, 3500415, 12389472, From dd23a48adeb2cce1ad04b46040761b021425e412 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 19 Feb 2017 19:10:45 -0500 Subject: [PATCH 08/35] Make ExtendedPoint attrs pub as we need them in constants.rs --- src/curve.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/curve.rs b/src/curve.rs index 7456e4f..0e5da25 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -174,12 +174,16 @@ impl CompressedEdwardsY { /// An `ExtendedPoint` is a point on the curve in 𝗣³(𝔽ₚ). /// A point (x,y) in the affine model corresponds to (x:y:1:xy). +// XXX members should not be public, but that's needed for the +// constants module. Fix when RFC #1422 lands: +// https://github.com/rust-lang/rust/issues/32409 #[derive(Copy, Clone)] +#[allow(missing_docs)] pub struct ExtendedPoint { - X: FieldElement, - Y: FieldElement, - Z: FieldElement, - T: FieldElement, + pub X: FieldElement, + pub Y: FieldElement, + pub Z: FieldElement, + pub T: FieldElement, } /// A `ProjectivePoint` is a point on the curve in 𝗣²(𝔽ₚ). From ee5ea0a01345510fb1efc75340565ccd7cebce64 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 29 Jan 2017 19:19:39 -0500 Subject: [PATCH 09/35] Make double functions public --- src/curve.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/curve.rs b/src/curve.rs index 0e5da25..529f396 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -467,7 +467,7 @@ impl CompletedPoint { impl ProjectivePoint { /// Double this point: return self + self - fn double(&self) -> CompletedPoint { // Double() + pub fn double(&self) -> CompletedPoint { // Double() let XX = self.X.square(); let YY = self.Y.square(); let ZZ2 = self.Z.square2(); @@ -487,7 +487,7 @@ impl ProjectivePoint { impl ExtendedPoint { /// Add this point to itself. - fn double(&self) -> ExtendedPoint { + pub fn double(&self) -> ExtendedPoint { self.to_projective().double().to_extended() } } From 5720e249abdcfc3158b1b36112f93ca73a888b6d Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 19 Feb 2017 19:11:41 -0500 Subject: [PATCH 10/35] Add constants for points of the eight-torsion subgroup. --- src/constants.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/src/constants.rs b/src/constants.rs index a5a2740..f58cd2d 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -17,8 +17,10 @@ #![allow(non_snake_case)] #![allow(non_upper_case_globals)] #![allow(missing_docs)] +#![allow(non_snake_case)] use field::FieldElement; +use curve::ExtendedPoint; use curve::PreComputedPoint; use curve::CompressedEdwardsY; use scalar::Scalar; @@ -105,6 +107,63 @@ pub const lminus1: Scalar = Scalar([ 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 ]); +/// The 8-torsion subgroup Ɛ[8]. +/// +/// In the case of Curve25519, it is cyclic; the `i`th element of the +/// array is `i*P`, where `P` is a point of order 8 generating Ɛ[8]. +/// +/// Thus Ɛ[4] is the points indexed by 0,2,4,6 and Ɛ[2] is the points +/// indexed by 0,4. +pub const EIGHT_TORSION: [ExtendedPoint; 8] = [ + ExtendedPoint{ + X: FieldElement([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + Y: FieldElement([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + Z: FieldElement([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + }, + ExtendedPoint{ + X: FieldElement([21352778, 5345713, 4660180, -8347857, 24143090, 14568123, 30185756, -12247770, -33528939, 8345319]), + Y: FieldElement([6952922, 1265500, -6862341, 7057498, 4037696, 5447722, -31680899, 15325402, 19365852, -1569102]), + Z: FieldElement([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement([-25262188, -11972680, 11716002, -5869612, -18193162, 16297739, 20670665, -8559098, 3541543, -5011181]) + }, + ExtendedPoint{ + X: FieldElement([32595792, 7943725, -9377950, -3500415, -12389472, 272473, 25146209, 2005654, -326686, -11406482]), + Y: FieldElement([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + Z: FieldElement([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + }, + ExtendedPoint{ + X: FieldElement([21352778, 5345713, 4660180, -8347857, 24143090, 14568123, 30185756, -12247770, -33528939, 8345319]), + Y: FieldElement([-6952922, -1265500, 6862341, -7057498, -4037696, -5447722, 31680899, -15325402, -19365852, 1569102]), + Z: FieldElement([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement([25262188, 11972680, -11716002, 5869612, 18193162, -16297739, -20670665, 8559098, -3541543, 5011181]) + }, + ExtendedPoint{ + X: FieldElement([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + Y: FieldElement([-1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + Z: FieldElement([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + }, + ExtendedPoint{ + X: FieldElement([-21352778, -5345713, -4660180, 8347857, -24143090, -14568123, -30185756, 12247770, 33528939, -8345319]), + Y: FieldElement([-6952922, -1265500, 6862341, -7057498, -4037696, -5447722, 31680899, -15325402, -19365852, 1569102]), + Z: FieldElement([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement([-25262188, -11972680, 11716002, -5869612, -18193162, 16297739, 20670665, -8559098, 3541543, -5011181]) + }, + ExtendedPoint{ + X: FieldElement([-32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482]), + Y: FieldElement([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + Z: FieldElement([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + }, + ExtendedPoint{ + X: FieldElement([-21352778, -5345713, -4660180, 8347857, -24143090, -14568123, -30185756, 12247770, 33528939, -8345319]), + Y: FieldElement([6952922, 1265500, -6862341, 7057498, 4037696, 5447722, -31680899, 15325402, 19365852, -1569102]), + Z: FieldElement([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement([25262188, 11972680, -11716002, 5869612, 18193162, -16297739, -20670665, 8559098, -3541543, 5011181]) + }, +]; pub const bi: [PreComputedPoint; 8] = [ PreComputedPoint{ @@ -1503,8 +1562,48 @@ pub const base: [[PreComputedPoint; 8]; 32] = [ mod test { use field::FieldElement; use curve::PreComputedPoint; + use curve::CompressedEdwardsY; + use curve::ExtendedPoint; + use curve::Identity; + use curve::ValidityCheck; use constants; + #[test] + fn test_eight_torsion() { + let mut bytes = [0;32]; + bytes[0] = 1; + let compressed_id = CompressedEdwardsY(bytes); + for i in 0..8 { + let Q = constants::EIGHT_TORSION[i].mult_by_pow_2(3); + assert!(Q.is_valid()); + assert!(Q.compress() == compressed_id); + } + } + + #[test] + fn test_four_torsion() { + let mut bytes = [0;32]; + bytes[0] = 1; + let compressed_id = CompressedEdwardsY(bytes); + for i in (0..8).filter(|i| i % 2 == 0) { + let Q = constants::EIGHT_TORSION[i].mult_by_pow_2(2); + assert!(Q.is_valid()); + assert!(Q.compress() == compressed_id); + } + } + + #[test] + fn test_two_torsion() { + let mut bytes = [0;32]; + bytes[0] = 1; + let compressed_id = CompressedEdwardsY(bytes); + for i in (0..8).filter(|i| i % 4 == 0) { + let Q = constants::EIGHT_TORSION[i].mult_by_pow_2(1); + assert!(Q.is_valid()); + assert!(Q.compress() == compressed_id); + } + } + #[test] fn test_half() { let one = FieldElement([1,0,0,0,0,0,0,0,0,0]); From 3fc5f753005251e76df30612a906504246969369 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 31 Jan 2017 20:03:29 -0500 Subject: [PATCH 11/35] Rename is_negative to is_negative_ed25519 --- src/curve.rs | 4 ++-- src/field.rs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/curve.rs b/src/curve.rs index 529f396..200dd19 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -159,7 +159,7 @@ impl CompressedEdwardsY { X *= &constants::SQRT_M1; } - if X.is_negative() != (self[31] >> 7) as i32 { + if X.is_negative_ed25519() != (self[31] >> 7) as i32 { X = X.neg(); } T = &X * &Y; @@ -391,7 +391,7 @@ impl ProjectivePoint { let mut s: [u8; 32]; s = y.to_bytes(); - s[31] ^= (x.is_negative() << 7) as u8; + s[31] ^= (x.is_negative_ed25519() << 7) as u8; CompressedEdwardsY(s) } } diff --git a/src/field.rs b/src/field.rs index f26108d..572b39f 100644 --- a/src/field.rs +++ b/src/field.rs @@ -510,13 +510,14 @@ impl FieldElement { (!equal_so_far & 1 & greater) as u8 } - /// Determine if this `FieldElement` is negative. + /// Determine if this `FieldElement` is negative, in the + /// sense used in the ed25519 paper. /// /// # Return /// /// If negative, return `1i32`. Otherwise, return `0i32`. // XXX should return u8 - pub fn is_negative(&self) -> i32 { //FeIsNegative + pub fn is_negative_ed25519(&self) -> i32 { //FeIsNegative let bytes = self.to_bytes(); (bytes[0] & 1) as i32 } From ee5958c4072f89d66bde43c170190d8b587db840 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 19 Feb 2017 19:48:51 -0500 Subject: [PATCH 12/35] Add is_[non]negative_decaf functions. Computes whether a point is nonnegative in the same way as Mike Hamburg's code. --- src/field.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/field.rs b/src/field.rs index 572b39f..3910792 100644 --- a/src/field.rs +++ b/src/field.rs @@ -522,6 +522,39 @@ impl FieldElement { (bytes[0] & 1) as i32 } + /// Determine if this `FieldElement` is negative, in the + /// sense used by Decaf: `x` is nonnegative if the least + /// absolute residue for `x` lies in `[0, (p-1)/2]`, and + /// is negative otherwise. + /// + /// # Return + /// + /// Returns `1u8` if negative, `0u8` if nonnegative. + /// + /// # Implementation + /// + /// Uses a trick borrowed from Mike Hamburg's code. Let `x \in + /// F_p` and let `y \in Z` be the least absolute residue for `x`. + /// Suppose `y ≤ (p-1)/2`. Then `2y < p` so `2y = 2y mod p` and + /// `2y mod p` is even. On the other hand, if `y > (p-1)/2` then + /// `2y ≥ p`; since `y < p`, `2y \in [p, 2p)`, so `2y mod p = + /// 2y-p`, which is odd. + /// + /// Thus we can test whether `y ≤ (p-1)/2` by checking whether `2y + /// mod p` is even. + pub fn is_negative_decaf(&self) -> u8 { + let y = self + self; + (y.to_bytes()[0] & 1) as u8 + } + + /// Determine if this `FieldElement` is nonnegative, in the + /// sense used by Decaf: `x` is nonnegative if the least + /// absolute residue for `x` lies in `[0, (p-1)/2]`, and + /// is negative otherwise. + pub fn is_nonnegative_decaf(&self) -> u8 { + 1u8 & (!self.is_negative_decaf()) + } + /// Determine if this `FieldElement` is zero. /// /// # Return From a415caa9fb9aacbf6fa1230f94bb77875f17eccd Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 19 Feb 2017 20:00:19 -0500 Subject: [PATCH 13/35] Add a seperate invsqrt function. This code isn't constant-time, but maybe should be. However that would prevent a bunch of nice things (e.g., Option types). Probably good to think about this. --- src/constants.rs | 13 +++++++++++++ src/field.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/constants.rs b/src/constants.rs index f58cd2d..46d4094 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1621,6 +1621,19 @@ mod test { assert_eq!(minus_one, msqrt_m1_sq); } + #[test] + fn test_sqrt_constants_sign() { + let one = FieldElement([ 1,0,0,0,0,0,0,0,0,0]); + let minus_one = FieldElement([-1,0,0,0,0,0,0,0,0,0]); + let invsqrt_m1 = minus_one.invsqrt().unwrap(); + let sign_test_sqrt = &invsqrt_m1 * &constants::SQRT_M1; + let sign_test_msqrt = &invsqrt_m1 * &constants::MSQRT_M1; + // XXX it seems we have flipped the sign relative to + // the invsqrt function? + assert_eq!(sign_test_sqrt, minus_one); + assert_eq!(sign_test_msqrt, one); + } + #[test] /// Test that d = -121665/121666 fn test_d_vs_ratio() { diff --git a/src/field.rs b/src/field.rs index 3910792..e0a1203 100644 --- a/src/field.rs +++ b/src/field.rs @@ -31,6 +31,8 @@ use subtle::CTEq; use utils::{load3, load4}; +use constants; + /// FieldElements are represented as an array of ten "Limbs", which are radix /// 25.5, that is, each Limb of a FieldElement alternates between being /// represented as a factor of 2^25 or 2^26 more than the last corresponding @@ -815,6 +817,48 @@ impl FieldElement { t21 } + /// Try to compute 1/sqrt(self). + /// + /// # Return + /// + /// * If `self` is zero, returns zero. + /// * If `self` is square, returns 1/sqrt(self). + /// * If `self` is nonsquare, returns `None`. + pub fn invsqrt(&self) -> Option { + // We are to compute v as: + // / 1/sqrt(self) if self is square, nonzero; + // v = | 0 if self is zero; + // \ [reject] if self is nonsquare. + // + // Using the same trick as in ed25519 decoding, we merge the + // inversion, the square root, and the square test as follows. + // + // To compute sqrt(α), we can compute β = α^((p+3)/8). + // Then β^2 = ±α, so multiplying β by sqrt(-1) if necessary + // gives sqrt(α). + // + // To compute 1/sqrt(α), we observe that + // 1/β = α^(p-1 - (p+3)/8) = α^((7p-11)/8) + // = α^3 * (α^7)^((p-5)/8). + // + // If α is square, then (1/β)^2 = ±(1/α), so that (1/β)^2 α = ±1. + let a3 = &self.square() * self; // α^3 + let a7 = &a3.square() * self; // α^7 + let mut v = &a3 * &a7.pow_p58(); // α^(p-1-(p+3)/8) + let check = self * &v.square(); // ±1 if α is square + + if v.is_zero() == 1u8 { + return Some(v); // α was zero all along + } else if check == FieldElement::one() { + return Some(v); // computed the correct sqrt + } else if check == -&FieldElement::one() { + // wrong sign, multiply by sqrt(-1) + return Some(&v * &constants::SQRT_M1); + } else { + return None; // input was nonsquare + } + } + /// chi calculates `self^((p-1)/2)`. /// /// # Return From f18ebe94309141bba61e6cd4773e8db03bbb9548 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 19 Feb 2017 20:01:42 -0500 Subject: [PATCH 14/35] First draft of decaf decompression --- src/curve.rs | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/src/curve.rs b/src/curve.rs index 200dd19..7431d6e 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -80,6 +80,7 @@ use core::fmt::Debug; use core::iter::Iterator; use core::ops::{Add, Sub, Neg, Index}; +use core::cmp::{PartialEq, Eq}; use constants; use field::FieldElement; @@ -168,6 +169,48 @@ impl CompressedEdwardsY { } } +/// A point serialized using Mike Hamburg's Decaf scheme. +/// +/// XXX think about how this API should work +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct DecafPoint(pub [u8; 32]); + +impl DecafPoint { + /// View this `DecafPoint` as an array of bytes. + pub fn to_bytes(&self) -> [u8;32] { + self.0 + } + + /// Attempt to decompress to an `ExtendedPoint`. + pub fn decompress(&self) -> Option { + // XXX should decoding be CT ? + // XXX should reject unless s = |s| + // XXX need to check that xy is nonnegative and reject otherwise + let s = FieldElement::from_bytes(&self.0); + let ss = s.square(); + let X = &s + &s; // X = 2s + let Z = &FieldElement::one() - &ss; // Z = 1+as^2 + let u = &(&Z * &Z) - &(&constants::d4 * &ss); // u = Z^2 - 4ds^2 + let uss = &u * &ss; + let mut v = match uss.invsqrt() { + Some(v) => v, + None => return None, + }; + // Now v = 1/sqrt(us^2) if us^2 is a nonzero square, 0 if us^2 is zero. + let uv = &v * &u; + if uv.is_negative_decaf() == 1u8 { + v.negate(); + } + let mut two_minus_Z = -&Z; two_minus_Z[0] += 2; + let mut w = &v * &(&s * &two_minus_Z); + w.conditional_assign(&FieldElement::one(), s.is_zero()); + let Y = &w * &Z; + let T = &w * &X; + + Some(ExtendedPoint{ X: X, Y: Y, Z: Z, T: T }) + } +} + // ------------------------------------------------------------------------ // Internal point representations // ------------------------------------------------------------------------ @@ -425,6 +468,75 @@ impl ExtendedPoint { self.to_projective().compress() } + /// Compress in Decaf format. + pub fn compress_decaf(&self) -> DecafPoint { + // Q: Do we want to encode twisted or untwisted? + // + // Notes: + // Recall that the twisted Edwards curve E_{a,d} is of the form + // + // ax^2 + y^2 = 1 + dx^2y^2. + // + // Internally, we operate on the curve with a = -1, d = + // -121665/121666, a.k.a., the twist. But maybe we would like + // to use Decaf on the untwisted curve with a = 1, d = + // 121665/121666. (why? interop?) + // + // Fix i, a square root of -1 (mod p). + // + // The map x -> ix is an isomorphism from E_{a,d} to E_{-a,-d}. + // Its inverse is x -> -ix. + // let untwisted_X = &self.X * &constants::MSQRT_M1; + // etc. + + // Step 0: pre-rotation, needed for Decaf with E[8] = Z/8 + + let mut X = self.X; + let mut Y = self.Y; + let mut XY = self.T; + + // If y nonzero and xy nonnegative, continue. + // Otherwise, add Q_6 = (i,0) = constants::EIGHT_TORSION[6] + // (x,y) + Q_6 = (iy,ix) + // (X:Y:Z:T) + Q_6 = (iY:iX:Z:-T) + + // XXX it should be possible to avoid this inversion, but + // let's make sure the code is correct first + let xy = &XY * &self.Z.invert(); + let is_neg_mask = 1u8 & !(Y.is_nonzero() & xy.is_nonnegative_decaf()); + let iX = &X * &constants::SQRT_M1; + let iY = &Y * &constants::SQRT_M1; + X.conditional_assign(&iY, is_neg_mask); + Y.conditional_assign(&iX, is_neg_mask); + let minus_XY = -&XY; + XY.conditional_assign(&minus_XY, is_neg_mask); + + // Step 1: Compute r = 1/sqrt((a-d)(Z+Y)(Z-Y)) + let Z_plus_Y = &self.Z + &Y; + let Z_minus_Y = &self.Z - &Y; + let t = &constants::a_minus_d * &(&Z_plus_Y * &Z_minus_Y); + // t should always be square (why?) + // XXX is it safe to use option types here? + let mut r = t.invsqrt().unwrap(); + + // Step 2: Compute u = (a-d)r + let u = &constants::a_minus_d * &r; + + // Step 3: Negate r if -2uZ is negative. + let uZ = &u * &self.Z; + let minus_r = -&r; + let m2uZ = -&(&uZ + &uZ); + let mask = m2uZ.is_negative_decaf(); + r.conditional_assign(&minus_r, mask); + + // Step 4: Compute s = |u(r(aZX - dYT)+Y)/a| + let minus_ZX = -&(&self.Z * &X); + let dYT = &constants::d * &(&Y * &XY); + let mut s = &u * &(&(&r * &(&minus_ZX - &dYT)) + &Y); + s.negate(); + DecafPoint(s.abs_decaf().to_bytes()) + } + /// Dehomogenize to a PreComputedPoint. /// Mainly for testing. pub fn to_precomputed(&self) -> PreComputedPoint { @@ -856,6 +968,12 @@ impl ExtendedPoint { // Debug traits // ------------------------------------------------------------------------ +impl Debug for DecafPoint { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "DecafPoint: {:?}", &self.0[..]) + } +} + impl Debug for ExtendedPoint { fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { write!(f, "ExtendedPoint(\n\tX: {:?},\n\tY: {:?},\n\tZ: {:?},\n\tT: {:?}\n)", @@ -898,6 +1016,8 @@ impl Debug for CachedPoint { #[cfg(test)] mod test { use test::Bencher; + use rand::OsRng; + use field::FieldElement; use scalar::Scalar; use subtle::CTAssignable; @@ -1162,6 +1282,67 @@ mod test { assert!(ExtendedPoint::identity().is_identity()); } + #[test] + fn test_decaf_decompress_id() { + let compressed_id = DecafPoint([0u8; 32]); + let id = compressed_id.decompress().unwrap(); + // This should compress (as ed25519) to the following: + let mut bytes = [0u8; 32]; bytes[0] = 1; + assert_eq!(id.compress(), CompressedEdwardsY(bytes)); + } + + #[test] + fn test_decaf_compress_id() { + let id = ExtendedPoint::identity(); + assert_eq!(id.compress_decaf(), DecafPoint([0u8; 32])); + } + + #[test] + fn test_decaf_basepoint_roundtrip() { + // XXX fix up this test + let bp = BASE_CMPRSSD.decompress().unwrap(); + let bp_decaf = bp.compress_decaf(); + let bp_recaf = bp_decaf.decompress().unwrap(); + let diff = &bp - &bp_recaf; + let diff2 = diff.double(); + let diff4 = diff2.double(); + //println!("bp {:?}", bp); + //println!("bp_decaf {:?}", bp_decaf); + //println!("bp_recaf {:?}", bp_recaf); + //println!("diff {:?}", diff.compress()); + //println!("diff2 {:?}", diff2.compress()); + //println!("diff4 {:?}", diff4.compress()); + assert_eq!(diff4.compress(), ExtendedPoint::identity().compress()); + } + + #[test] + fn test_decaf_four_torsion_basepoint() { + //println!(""); + let bp = BASE_CMPRSSD.decompress().unwrap(); + let bp_decaf = bp.compress_decaf(); + //println!("orig, {:?}", bp.compress_decaf()); + for i in (0..8).filter(|x| x % 2 == 0) { + let Q = &bp + &constants::EIGHT_TORSION[i]; + //println!("{}, {:?}", i, Q.compress_decaf()); + assert_eq!(Q.compress_decaf(), bp_decaf); + } + } + + #[test] + fn test_decaf_four_torsion_random() { + //println!(""); + let mut rng = OsRng::new().unwrap(); + let s = Scalar::random(&mut rng); + let P = ExtendedPoint::basepoint_mult(&s); + let P_decaf = P.compress_decaf(); + //println!("orig, {:?}", P.compress_decaf()); + for i in (0..8).filter(|x| x % 2 == 0) { + let Q = &P + &constants::EIGHT_TORSION[i]; + //println!("{}, {:?}", i, Q.compress_decaf()); + assert_eq!(Q.compress_decaf(), P_decaf); + } + } + #[bench] fn bench_basepoint_mult(b: &mut Bencher) { b.iter(|| ExtendedPoint::basepoint_mult(&A_SCALAR)); From eec7747be71454beeff0893fc0e3070ec59eb701 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 19 Feb 2017 23:08:52 -0800 Subject: [PATCH 15/35] Move Decaf code to decaf.rs --- src/curve.rs | 178 --------------------------------------- src/decaf.rs | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +- 3 files changed, 236 insertions(+), 179 deletions(-) create mode 100644 src/decaf.rs diff --git a/src/curve.rs b/src/curve.rs index 7431d6e..755694b 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -169,48 +169,6 @@ impl CompressedEdwardsY { } } -/// A point serialized using Mike Hamburg's Decaf scheme. -/// -/// XXX think about how this API should work -#[derive(Copy, Clone, Eq, PartialEq)] -pub struct DecafPoint(pub [u8; 32]); - -impl DecafPoint { - /// View this `DecafPoint` as an array of bytes. - pub fn to_bytes(&self) -> [u8;32] { - self.0 - } - - /// Attempt to decompress to an `ExtendedPoint`. - pub fn decompress(&self) -> Option { - // XXX should decoding be CT ? - // XXX should reject unless s = |s| - // XXX need to check that xy is nonnegative and reject otherwise - let s = FieldElement::from_bytes(&self.0); - let ss = s.square(); - let X = &s + &s; // X = 2s - let Z = &FieldElement::one() - &ss; // Z = 1+as^2 - let u = &(&Z * &Z) - &(&constants::d4 * &ss); // u = Z^2 - 4ds^2 - let uss = &u * &ss; - let mut v = match uss.invsqrt() { - Some(v) => v, - None => return None, - }; - // Now v = 1/sqrt(us^2) if us^2 is a nonzero square, 0 if us^2 is zero. - let uv = &v * &u; - if uv.is_negative_decaf() == 1u8 { - v.negate(); - } - let mut two_minus_Z = -&Z; two_minus_Z[0] += 2; - let mut w = &v * &(&s * &two_minus_Z); - w.conditional_assign(&FieldElement::one(), s.is_zero()); - let Y = &w * &Z; - let T = &w * &X; - - Some(ExtendedPoint{ X: X, Y: Y, Z: Z, T: T }) - } -} - // ------------------------------------------------------------------------ // Internal point representations // ------------------------------------------------------------------------ @@ -468,75 +426,6 @@ impl ExtendedPoint { self.to_projective().compress() } - /// Compress in Decaf format. - pub fn compress_decaf(&self) -> DecafPoint { - // Q: Do we want to encode twisted or untwisted? - // - // Notes: - // Recall that the twisted Edwards curve E_{a,d} is of the form - // - // ax^2 + y^2 = 1 + dx^2y^2. - // - // Internally, we operate on the curve with a = -1, d = - // -121665/121666, a.k.a., the twist. But maybe we would like - // to use Decaf on the untwisted curve with a = 1, d = - // 121665/121666. (why? interop?) - // - // Fix i, a square root of -1 (mod p). - // - // The map x -> ix is an isomorphism from E_{a,d} to E_{-a,-d}. - // Its inverse is x -> -ix. - // let untwisted_X = &self.X * &constants::MSQRT_M1; - // etc. - - // Step 0: pre-rotation, needed for Decaf with E[8] = Z/8 - - let mut X = self.X; - let mut Y = self.Y; - let mut XY = self.T; - - // If y nonzero and xy nonnegative, continue. - // Otherwise, add Q_6 = (i,0) = constants::EIGHT_TORSION[6] - // (x,y) + Q_6 = (iy,ix) - // (X:Y:Z:T) + Q_6 = (iY:iX:Z:-T) - - // XXX it should be possible to avoid this inversion, but - // let's make sure the code is correct first - let xy = &XY * &self.Z.invert(); - let is_neg_mask = 1u8 & !(Y.is_nonzero() & xy.is_nonnegative_decaf()); - let iX = &X * &constants::SQRT_M1; - let iY = &Y * &constants::SQRT_M1; - X.conditional_assign(&iY, is_neg_mask); - Y.conditional_assign(&iX, is_neg_mask); - let minus_XY = -&XY; - XY.conditional_assign(&minus_XY, is_neg_mask); - - // Step 1: Compute r = 1/sqrt((a-d)(Z+Y)(Z-Y)) - let Z_plus_Y = &self.Z + &Y; - let Z_minus_Y = &self.Z - &Y; - let t = &constants::a_minus_d * &(&Z_plus_Y * &Z_minus_Y); - // t should always be square (why?) - // XXX is it safe to use option types here? - let mut r = t.invsqrt().unwrap(); - - // Step 2: Compute u = (a-d)r - let u = &constants::a_minus_d * &r; - - // Step 3: Negate r if -2uZ is negative. - let uZ = &u * &self.Z; - let minus_r = -&r; - let m2uZ = -&(&uZ + &uZ); - let mask = m2uZ.is_negative_decaf(); - r.conditional_assign(&minus_r, mask); - - // Step 4: Compute s = |u(r(aZX - dYT)+Y)/a| - let minus_ZX = -&(&self.Z * &X); - let dYT = &constants::d * &(&Y * &XY); - let mut s = &u * &(&(&r * &(&minus_ZX - &dYT)) + &Y); - s.negate(); - DecafPoint(s.abs_decaf().to_bytes()) - } - /// Dehomogenize to a PreComputedPoint. /// Mainly for testing. pub fn to_precomputed(&self) -> PreComputedPoint { @@ -968,12 +857,6 @@ impl ExtendedPoint { // Debug traits // ------------------------------------------------------------------------ -impl Debug for DecafPoint { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - write!(f, "DecafPoint: {:?}", &self.0[..]) - } -} - impl Debug for ExtendedPoint { fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { write!(f, "ExtendedPoint(\n\tX: {:?},\n\tY: {:?},\n\tZ: {:?},\n\tT: {:?}\n)", @@ -1282,67 +1165,6 @@ mod test { assert!(ExtendedPoint::identity().is_identity()); } - #[test] - fn test_decaf_decompress_id() { - let compressed_id = DecafPoint([0u8; 32]); - let id = compressed_id.decompress().unwrap(); - // This should compress (as ed25519) to the following: - let mut bytes = [0u8; 32]; bytes[0] = 1; - assert_eq!(id.compress(), CompressedEdwardsY(bytes)); - } - - #[test] - fn test_decaf_compress_id() { - let id = ExtendedPoint::identity(); - assert_eq!(id.compress_decaf(), DecafPoint([0u8; 32])); - } - - #[test] - fn test_decaf_basepoint_roundtrip() { - // XXX fix up this test - let bp = BASE_CMPRSSD.decompress().unwrap(); - let bp_decaf = bp.compress_decaf(); - let bp_recaf = bp_decaf.decompress().unwrap(); - let diff = &bp - &bp_recaf; - let diff2 = diff.double(); - let diff4 = diff2.double(); - //println!("bp {:?}", bp); - //println!("bp_decaf {:?}", bp_decaf); - //println!("bp_recaf {:?}", bp_recaf); - //println!("diff {:?}", diff.compress()); - //println!("diff2 {:?}", diff2.compress()); - //println!("diff4 {:?}", diff4.compress()); - assert_eq!(diff4.compress(), ExtendedPoint::identity().compress()); - } - - #[test] - fn test_decaf_four_torsion_basepoint() { - //println!(""); - let bp = BASE_CMPRSSD.decompress().unwrap(); - let bp_decaf = bp.compress_decaf(); - //println!("orig, {:?}", bp.compress_decaf()); - for i in (0..8).filter(|x| x % 2 == 0) { - let Q = &bp + &constants::EIGHT_TORSION[i]; - //println!("{}, {:?}", i, Q.compress_decaf()); - assert_eq!(Q.compress_decaf(), bp_decaf); - } - } - - #[test] - fn test_decaf_four_torsion_random() { - //println!(""); - let mut rng = OsRng::new().unwrap(); - let s = Scalar::random(&mut rng); - let P = ExtendedPoint::basepoint_mult(&s); - let P_decaf = P.compress_decaf(); - //println!("orig, {:?}", P.compress_decaf()); - for i in (0..8).filter(|x| x % 2 == 0) { - let Q = &P + &constants::EIGHT_TORSION[i]; - //println!("{}, {:?}", i, Q.compress_decaf()); - assert_eq!(Q.compress_decaf(), P_decaf); - } - } - #[bench] fn bench_basepoint_mult(b: &mut Bencher) { b.iter(|| ExtendedPoint::basepoint_mult(&A_SCALAR)); diff --git a/src/decaf.rs b/src/decaf.rs new file mode 100644 index 0000000..85fb26c --- /dev/null +++ b/src/decaf.rs @@ -0,0 +1,234 @@ +// -*- mode: rust; -*- +// +// To the extent possible under law, the authors have waived all copyright and +// related or neighboring rights to curve25519-dalek, using the Creative +// Commons "CC0" public domain dedication. See +// for full details. +// +// Authors: +// - Isis Agora Lovecruft +// - Henry de Valence + +//! An implementation of Mike Hamburg's Decaf point-compression scheme, +//! providing a prime-order group. + +// We allow non snake_case names because coordinates in projective space are +// traditionally denoted by the capitalisation of their respective +// counterparts in affine space. Yeah, you heard me, rustc, I'm gonna have my +// affine and projective cakes and eat both of them too. +#![allow(non_snake_case)] + +use core::fmt::Debug; + +use constants; +use field::FieldElement; +use subtle::CTAssignable; + +use curve::ExtendedPoint; + +// ------------------------------------------------------------------------ +// Compressed points +// ------------------------------------------------------------------------ + +/// A point serialized using Mike Hamburg's Decaf scheme. +/// +/// XXX think about how this API should work +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct DecafPoint(pub [u8; 32]); + +impl DecafPoint { + /// View this `DecafPoint` as an array of bytes. + pub fn to_bytes(&self) -> [u8;32] { + self.0 + } + + /// Attempt to decompress to an `ExtendedPoint`. + pub fn decompress(&self) -> Option { + // XXX should decoding be CT ? + // XXX should reject unless s = |s| + // XXX need to check that xy is nonnegative and reject otherwise + let s = FieldElement::from_bytes(&self.0); + let ss = s.square(); + let X = &s + &s; // X = 2s + let Z = &FieldElement::one() - &ss; // Z = 1+as^2 + let u = &(&Z * &Z) - &(&constants::d4 * &ss); // u = Z^2 - 4ds^2 + let uss = &u * &ss; + let mut v = match uss.invsqrt() { + Some(v) => v, + None => return None, + }; + // Now v = 1/sqrt(us^2) if us^2 is a nonzero square, 0 if us^2 is zero. + let uv = &v * &u; + if uv.is_negative_decaf() == 1u8 { + v.negate(); + } + let mut two_minus_Z = -&Z; two_minus_Z[0] += 2; + let mut w = &v * &(&s * &two_minus_Z); + w.conditional_assign(&FieldElement::one(), s.is_zero()); + let Y = &w * &Z; + let T = &w * &X; + + Some(ExtendedPoint{ X: X, Y: Y, Z: Z, T: T }) + } +} + +impl ExtendedPoint { + /// Compress in Decaf format. + pub fn compress_decaf(&self) -> DecafPoint { + // Q: Do we want to encode twisted or untwisted? + // + // Notes: + // Recall that the twisted Edwards curve E_{a,d} is of the form + // + // ax^2 + y^2 = 1 + dx^2y^2. + // + // Internally, we operate on the curve with a = -1, d = + // -121665/121666, a.k.a., the twist. But maybe we would like + // to use Decaf on the untwisted curve with a = 1, d = + // 121665/121666. (why? interop?) + // + // Fix i, a square root of -1 (mod p). + // + // The map x -> ix is an isomorphism from E_{a,d} to E_{-a,-d}. + // Its inverse is x -> -ix. + // let untwisted_X = &self.X * &constants::MSQRT_M1; + // etc. + + // Step 0: pre-rotation, needed for Decaf with E[8] = Z/8 + + let mut X = self.X; + let mut Y = self.Y; + let mut XY = self.T; + + // If y nonzero and xy nonnegative, continue. + // Otherwise, add Q_6 = (i,0) = constants::EIGHT_TORSION[6] + // (x,y) + Q_6 = (iy,ix) + // (X:Y:Z:T) + Q_6 = (iY:iX:Z:-T) + + // XXX it should be possible to avoid this inversion, but + // let's make sure the code is correct first + let xy = &XY * &self.Z.invert(); + let is_neg_mask = 1u8 & !(Y.is_nonzero() & xy.is_nonnegative_decaf()); + let iX = &X * &constants::SQRT_M1; + let iY = &Y * &constants::SQRT_M1; + X.conditional_assign(&iY, is_neg_mask); + Y.conditional_assign(&iX, is_neg_mask); + let minus_XY = -&XY; + XY.conditional_assign(&minus_XY, is_neg_mask); + + // Step 1: Compute r = 1/sqrt((a-d)(Z+Y)(Z-Y)) + let Z_plus_Y = &self.Z + &Y; + let Z_minus_Y = &self.Z - &Y; + let t = &constants::a_minus_d * &(&Z_plus_Y * &Z_minus_Y); + // t should always be square (why?) + // XXX is it safe to use option types here? + let mut r = t.invsqrt().unwrap(); + + // Step 2: Compute u = (a-d)r + let u = &constants::a_minus_d * &r; + + // Step 3: Negate r if -2uZ is negative. + let uZ = &u * &self.Z; + let minus_r = -&r; + let m2uZ = -&(&uZ + &uZ); + let mask = m2uZ.is_negative_decaf(); + r.conditional_assign(&minus_r, mask); + + // Step 4: Compute s = |u(r(aZX - dYT)+Y)/a| + let minus_ZX = -&(&self.Z * &X); + let dYT = &constants::d * &(&Y * &XY); + let mut s = &u * &(&(&r * &(&minus_ZX - &dYT)) + &Y); + s.negate(); + DecafPoint(s.abs_decaf().to_bytes()) + } +} + + +// ------------------------------------------------------------------------ +// Debug traits +// ------------------------------------------------------------------------ + +impl Debug for DecafPoint { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "DecafPoint: {:?}", &self.0[..]) + } +} + + +// ------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------ + +#[cfg(test)] +mod test { + use rand::OsRng; + + use scalar::Scalar; + use constants; + use constants::BASE_CMPRSSD; + use curve::CompressedEdwardsY; + use curve::ExtendedPoint; + use curve::Identity; + use super::*; + + #[test] + fn test_decaf_decompress_id() { + let compressed_id = DecafPoint([0u8; 32]); + let id = compressed_id.decompress().unwrap(); + // This should compress (as ed25519) to the following: + let mut bytes = [0u8; 32]; bytes[0] = 1; + assert_eq!(id.compress(), CompressedEdwardsY(bytes)); + } + + #[test] + fn test_decaf_compress_id() { + let id = ExtendedPoint::identity(); + assert_eq!(id.compress_decaf(), DecafPoint([0u8; 32])); + } + + #[test] + fn test_decaf_basepoint_roundtrip() { + // XXX fix up this test + let bp = BASE_CMPRSSD.decompress().unwrap(); + let bp_decaf = bp.compress_decaf(); + let bp_recaf = bp_decaf.decompress().unwrap(); + let diff = &bp - &bp_recaf; + let diff2 = diff.double(); + let diff4 = diff2.double(); + //println!("bp {:?}", bp); + //println!("bp_decaf {:?}", bp_decaf); + //println!("bp_recaf {:?}", bp_recaf); + //println!("diff {:?}", diff.compress()); + //println!("diff2 {:?}", diff2.compress()); + //println!("diff4 {:?}", diff4.compress()); + assert_eq!(diff4.compress(), ExtendedPoint::identity().compress()); + } + + #[test] + fn test_decaf_four_torsion_basepoint() { + //println!(""); + let bp = BASE_CMPRSSD.decompress().unwrap(); + let bp_decaf = bp.compress_decaf(); + //println!("orig, {:?}", bp.compress_decaf()); + for i in (0..8).filter(|x| x % 2 == 0) { + let Q = &bp + &constants::EIGHT_TORSION[i]; + //println!("{}, {:?}", i, Q.compress_decaf()); + assert_eq!(Q.compress_decaf(), bp_decaf); + } + } + + #[test] + fn test_decaf_four_torsion_random() { + //println!(""); + let mut rng = OsRng::new().unwrap(); + let s = Scalar::random(&mut rng); + let P = ExtendedPoint::basepoint_mult(&s); + let P_decaf = P.compress_decaf(); + //println!("orig, {:?}", P.compress_decaf()); + for i in (0..8).filter(|x| x % 2 == 0) { + let Q = &P + &constants::EIGHT_TORSION[i]; + //println!("{}, {:?}", i, Q.compress_decaf()); + assert_eq!(Q.compress_decaf(), P_decaf); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 7855923..bd62628 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,8 +44,9 @@ extern crate rand; // Modules for low-level operations directly on field elements and curve points. pub mod field; -pub mod curve; pub mod scalar; +pub mod curve; +pub mod decaf; // Constant-time functions and other miscelaneous utilities. From 08fc10f356f18c5d158fd843325de87043802c60 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 19 Feb 2017 23:10:44 -0800 Subject: [PATCH 16/35] Rename DecafPoint -> CompressedDecaf --- src/decaf.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/decaf.rs b/src/decaf.rs index 85fb26c..80e7db9 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -34,10 +34,10 @@ use curve::ExtendedPoint; /// /// XXX think about how this API should work #[derive(Copy, Clone, Eq, PartialEq)] -pub struct DecafPoint(pub [u8; 32]); +pub struct CompressedDecaf(pub [u8; 32]); -impl DecafPoint { - /// View this `DecafPoint` as an array of bytes. +impl CompressedDecaf { + /// View this `CompressedDecaf` as an array of bytes. pub fn to_bytes(&self) -> [u8;32] { self.0 } @@ -74,7 +74,7 @@ impl DecafPoint { impl ExtendedPoint { /// Compress in Decaf format. - pub fn compress_decaf(&self) -> DecafPoint { + pub fn compress_decaf(&self) -> CompressedDecaf { // Q: Do we want to encode twisted or untwisted? // // Notes: @@ -139,7 +139,7 @@ impl ExtendedPoint { let dYT = &constants::d * &(&Y * &XY); let mut s = &u * &(&(&r * &(&minus_ZX - &dYT)) + &Y); s.negate(); - DecafPoint(s.abs_decaf().to_bytes()) + CompressedDecaf(s.abs_decaf().to_bytes()) } } @@ -148,9 +148,9 @@ impl ExtendedPoint { // Debug traits // ------------------------------------------------------------------------ -impl Debug for DecafPoint { +impl Debug for CompressedDecaf { fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - write!(f, "DecafPoint: {:?}", &self.0[..]) + write!(f, "CompressedDecaf: {:?}", &self.0[..]) } } @@ -173,7 +173,7 @@ mod test { #[test] fn test_decaf_decompress_id() { - let compressed_id = DecafPoint([0u8; 32]); + let compressed_id = CompressedDecaf([0u8; 32]); let id = compressed_id.decompress().unwrap(); // This should compress (as ed25519) to the following: let mut bytes = [0u8; 32]; bytes[0] = 1; @@ -183,7 +183,7 @@ mod test { #[test] fn test_decaf_compress_id() { let id = ExtendedPoint::identity(); - assert_eq!(id.compress_decaf(), DecafPoint([0u8; 32])); + assert_eq!(id.compress_decaf(), CompressedDecaf([0u8; 32])); } #[test] From ec82e7a4f489f44a569729c76eb957ad27b7cf6c Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 19 Feb 2017 23:25:04 -0800 Subject: [PATCH 17/35] Create a DecafPoint struct, wrapping an ExtendedPoint --- src/decaf.rs | 63 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/src/decaf.rs b/src/decaf.rs index 80e7db9..139f6fc 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -42,8 +42,8 @@ impl CompressedDecaf { self.0 } - /// Attempt to decompress to an `ExtendedPoint`. - pub fn decompress(&self) -> Option { + /// Attempt to decompress to an `DecafPoint`. + pub fn decompress(&self) -> Option { // XXX should decoding be CT ? // XXX should reject unless s = |s| // XXX need to check that xy is nonnegative and reject otherwise @@ -68,13 +68,19 @@ impl CompressedDecaf { let Y = &w * &Z; let T = &w * &X; - Some(ExtendedPoint{ X: X, Y: Y, Z: Z, T: T }) + Some(DecafPoint(ExtendedPoint{ X: X, Y: Y, Z: Z, T: T })) } } -impl ExtendedPoint { +/// A point in a prime-order group. +/// +/// XXX think about how this API should work +#[derive(Copy, Clone)] +pub struct DecafPoint(pub ExtendedPoint); + +impl DecafPoint { /// Compress in Decaf format. - pub fn compress_decaf(&self) -> CompressedDecaf { + pub fn compress(&self) -> CompressedDecaf { // Q: Do we want to encode twisted or untwisted? // // Notes: @@ -96,9 +102,9 @@ impl ExtendedPoint { // Step 0: pre-rotation, needed for Decaf with E[8] = Z/8 - let mut X = self.X; - let mut Y = self.Y; - let mut XY = self.T; + let mut X = self.0.X; + let mut Y = self.0.Y; + let mut XY = self.0.T; // If y nonzero and xy nonnegative, continue. // Otherwise, add Q_6 = (i,0) = constants::EIGHT_TORSION[6] @@ -107,7 +113,7 @@ impl ExtendedPoint { // XXX it should be possible to avoid this inversion, but // let's make sure the code is correct first - let xy = &XY * &self.Z.invert(); + let xy = &XY * &self.0.Z.invert(); let is_neg_mask = 1u8 & !(Y.is_nonzero() & xy.is_nonnegative_decaf()); let iX = &X * &constants::SQRT_M1; let iY = &Y * &constants::SQRT_M1; @@ -117,8 +123,8 @@ impl ExtendedPoint { XY.conditional_assign(&minus_XY, is_neg_mask); // Step 1: Compute r = 1/sqrt((a-d)(Z+Y)(Z-Y)) - let Z_plus_Y = &self.Z + &Y; - let Z_minus_Y = &self.Z - &Y; + let Z_plus_Y = &self.0.Z + &Y; + let Z_minus_Y = &self.0.Z - &Y; let t = &constants::a_minus_d * &(&Z_plus_Y * &Z_minus_Y); // t should always be square (why?) // XXX is it safe to use option types here? @@ -128,14 +134,14 @@ impl ExtendedPoint { let u = &constants::a_minus_d * &r; // Step 3: Negate r if -2uZ is negative. - let uZ = &u * &self.Z; + let uZ = &u * &self.0.Z; let minus_r = -&r; let m2uZ = -&(&uZ + &uZ); let mask = m2uZ.is_negative_decaf(); r.conditional_assign(&minus_r, mask); // Step 4: Compute s = |u(r(aZX - dYT)+Y)/a| - let minus_ZX = -&(&self.Z * &X); + let minus_ZX = -&(&self.0.Z * &X); let dYT = &constants::d * &(&Y * &XY); let mut s = &u * &(&(&r * &(&minus_ZX - &dYT)) + &Y); s.negate(); @@ -154,6 +160,11 @@ impl Debug for CompressedDecaf { } } +impl Debug for DecafPoint { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "DecafPoint: {:?}", &self.0) + } +} // ------------------------------------------------------------------------ // Tests @@ -177,21 +188,21 @@ mod test { let id = compressed_id.decompress().unwrap(); // This should compress (as ed25519) to the following: let mut bytes = [0u8; 32]; bytes[0] = 1; - assert_eq!(id.compress(), CompressedEdwardsY(bytes)); + assert_eq!(id.0.compress(), CompressedEdwardsY(bytes)); } #[test] fn test_decaf_compress_id() { - let id = ExtendedPoint::identity(); - assert_eq!(id.compress_decaf(), CompressedDecaf([0u8; 32])); + let id = DecafPoint(ExtendedPoint::identity()); + assert_eq!(id.compress(), CompressedDecaf([0u8; 32])); } #[test] fn test_decaf_basepoint_roundtrip() { // XXX fix up this test let bp = BASE_CMPRSSD.decompress().unwrap(); - let bp_decaf = bp.compress_decaf(); - let bp_recaf = bp_decaf.decompress().unwrap(); + let bp_decaf = DecafPoint(bp).compress(); + let bp_recaf = bp_decaf.decompress().unwrap().0; let diff = &bp - &bp_recaf; let diff2 = diff.double(); let diff4 = diff2.double(); @@ -208,12 +219,12 @@ mod test { fn test_decaf_four_torsion_basepoint() { //println!(""); let bp = BASE_CMPRSSD.decompress().unwrap(); - let bp_decaf = bp.compress_decaf(); - //println!("orig, {:?}", bp.compress_decaf()); + let bp_decaf = DecafPoint(bp).compress(); + //println!("orig, {:?}", bp.compress()); for i in (0..8).filter(|x| x % 2 == 0) { let Q = &bp + &constants::EIGHT_TORSION[i]; - //println!("{}, {:?}", i, Q.compress_decaf()); - assert_eq!(Q.compress_decaf(), bp_decaf); + //println!("{}, {:?}", i, Q.compress()); + assert_eq!(DecafPoint(Q).compress(), bp_decaf); } } @@ -223,12 +234,12 @@ mod test { let mut rng = OsRng::new().unwrap(); let s = Scalar::random(&mut rng); let P = ExtendedPoint::basepoint_mult(&s); - let P_decaf = P.compress_decaf(); - //println!("orig, {:?}", P.compress_decaf()); + let P_decaf = DecafPoint(P).compress(); + //println!("orig, {:?}", P.compress()); for i in (0..8).filter(|x| x % 2 == 0) { let Q = &P + &constants::EIGHT_TORSION[i]; - //println!("{}, {:?}", i, Q.compress_decaf()); - assert_eq!(Q.compress_decaf(), P_decaf); + //println!("{}, {:?}", i, Q.compress()); + assert_eq!(DecafPoint(Q).compress(), P_decaf); } } } From 0992237233fddb156428a46895ebd0e4dd90aa91 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 19 Feb 2017 23:51:03 -0800 Subject: [PATCH 18/35] XY is a misleading variable name, use T instead --- src/decaf.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/decaf.rs b/src/decaf.rs index 139f6fc..4acce55 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -104,7 +104,7 @@ impl DecafPoint { let mut X = self.0.X; let mut Y = self.0.Y; - let mut XY = self.0.T; + let mut T = self.0.T; // If y nonzero and xy nonnegative, continue. // Otherwise, add Q_6 = (i,0) = constants::EIGHT_TORSION[6] @@ -113,14 +113,14 @@ impl DecafPoint { // XXX it should be possible to avoid this inversion, but // let's make sure the code is correct first - let xy = &XY * &self.0.Z.invert(); + let xy = &T * &self.0.Z.invert(); let is_neg_mask = 1u8 & !(Y.is_nonzero() & xy.is_nonnegative_decaf()); let iX = &X * &constants::SQRT_M1; let iY = &Y * &constants::SQRT_M1; X.conditional_assign(&iY, is_neg_mask); Y.conditional_assign(&iX, is_neg_mask); - let minus_XY = -&XY; - XY.conditional_assign(&minus_XY, is_neg_mask); + let minus_T = -&T; + T.conditional_assign(&minus_T, is_neg_mask); // Step 1: Compute r = 1/sqrt((a-d)(Z+Y)(Z-Y)) let Z_plus_Y = &self.0.Z + &Y; @@ -142,7 +142,7 @@ impl DecafPoint { // Step 4: Compute s = |u(r(aZX - dYT)+Y)/a| let minus_ZX = -&(&self.0.Z * &X); - let dYT = &constants::d * &(&Y * &XY); + let dYT = &constants::d * &(&Y * &T); let mut s = &u * &(&(&r * &(&minus_ZX - &dYT)) + &Y); s.negate(); CompressedDecaf(s.abs_decaf().to_bytes()) From 66e598a47c5ffceb6f4efaeeaa504c69d5a8cd89 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Mon, 20 Feb 2017 00:40:41 -0800 Subject: [PATCH 19/35] Minor doc tweaks --- src/decaf.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/decaf.rs b/src/decaf.rs index 4acce55..ade009a 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -9,8 +9,9 @@ // - Isis Agora Lovecruft // - Henry de Valence -//! An implementation of Mike Hamburg's Decaf point-compression scheme, -//! providing a prime-order group. +//! An implementation of Mike Hamburg's Decaf cofactor-eliminating +//! point-compression scheme, providing a prime-order group on top of +//! a non-prime-order elliptic curve. // We allow non snake_case names because coordinates in projective space are // traditionally denoted by the capitalisation of their respective @@ -36,6 +37,7 @@ use curve::ExtendedPoint; #[derive(Copy, Clone, Eq, PartialEq)] pub struct CompressedDecaf(pub [u8; 32]); +/// The result of compressing a `DecafPoint`. impl CompressedDecaf { /// View this `CompressedDecaf` as an array of bytes. pub fn to_bytes(&self) -> [u8;32] { From f52e2ee37e8a46fe1b50600b620919e69bb50f47 Mon Sep 17 00:00:00 2001 From: Henry & Isis Date: Mon, 20 Feb 2017 02:04:59 -0800 Subject: [PATCH 20/35] Create `BasepointMult` and `ScalarMult` traits. --- src/constants.rs | 8 ++++++++ src/curve.rs | 36 +++++++++++++++++++++++++++++------- src/decaf.rs | 1 + 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 46d4094..f12861a 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -94,6 +94,14 @@ pub const BASE_CMPRSSD: CompressedEdwardsY = 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66]); +/// Basepoint has y = 4/5. +pub const BASEPOINT: ExtendedPoint = ExtendedPoint{ + X: FieldElement([-14297830, -7645148, 16144683, -16471763, 27570974, -2696100, -26142465, 8378389, 20764389, 8758491]), + Y: FieldElement([-26843541, -6710886, 13421773, -13421773, 26843546, 6710886, -13421773, 13421773, -26843546, -6710886]), + Z: FieldElement([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement([28827062, -6116119, -27349572, 244363, 8635006, 11264893, 19351346, 13413597, 16611511, -6414980]), +}; + /// `l` is the order of base point, i.e. 2^252 + /// 27742317777372353535851937790883648493, in little-endian form pub const l: Scalar = Scalar([ 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, diff --git a/src/curve.rs b/src/curve.rs index 755694b..5d770d1 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -636,18 +636,24 @@ impl<'a> Neg for &'a PreComputedPoint { // Scalar multiplication // ------------------------------------------------------------------------ -impl ExtendedPoint { - /// Scalar multiplication: compute `a * self`. +/// Trait for scalar multiplication of an arbitrary point. +pub trait ScalarMult { + /// Compute `scalar * self`. + fn scalar_mult(&self, scalar: &S) -> Self; +} + +impl ScalarMult for ExtendedPoint { + /// Scalar multiplication: compute `scalar * self`. /// /// Uses a window of size 4. Note: for scalar multiplication of /// the basepoint, `basepoint_mult` is approximately 4x faster. - pub fn scalar_mult(&self, a: &Scalar) -> ExtendedPoint { + fn scalar_mult(&self, scalar: &Scalar) -> ExtendedPoint { let A = self.to_cached(); let mut As: [CachedPoint; 8] = [A; 8]; for i in 0..7 { As[i+1] = (self + &As[i]).to_extended().to_cached(); } - let e = a.to_radix_16(); + let e = scalar.to_radix_16(); let mut h = ExtendedPoint::identity(); let mut t: CompletedPoint; for i in (0..64).rev() { @@ -657,8 +663,22 @@ impl ExtendedPoint { } h } +} - /// Construct an `ExtendedPoint` from a `Scalar`, `a`, by +/// Trait for scalar multiplication of a distinguished basepoint. +pub trait BasepointMult { + /// Return the basepoint `B`. + fn basepoint() -> Self; + /// Compute `scalar * B`. + fn basepoint_mult(scalar: &S) -> Self; +} + +impl BasepointMult for ExtendedPoint { + fn basepoint() -> ExtendedPoint { + constants::BASEPOINT + } + + /// Construct an `ExtendedPoint` from a `Scalar`, `scalar`, by /// computing the multiple `aB` of the basepoint `B`. /// /// Precondition: the scalar must be reduced. @@ -683,8 +703,8 @@ impl ExtendedPoint { /// We then use the `select_precomputed_point` function, which /// takes `-8 ≤ x < 8` and `[16^2i * B, ..., 8 * 16^2i * B]`, /// and returns `x * 16^2i * B` in constant time. - pub fn basepoint_mult(a: &Scalar) -> ExtendedPoint { //GeScalarMultBase - let e = a.to_radix_16(); + fn basepoint_mult(scalar: &Scalar) -> ExtendedPoint { //GeScalarMultBase + let e = scalar.to_radix_16(); let mut h = ExtendedPoint::identity(); let mut t: CompletedPoint; @@ -702,7 +722,9 @@ impl ExtendedPoint { h } +} +impl ExtendedPoint { /// Multiply by the cofactor: compute `8 * self`. /// /// Convenience wrapper around `mult_by_pow_2`. diff --git a/src/decaf.rs b/src/decaf.rs index ade009a..42d2721 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -181,6 +181,7 @@ mod test { use constants::BASE_CMPRSSD; use curve::CompressedEdwardsY; use curve::ExtendedPoint; + use curve::BasepointMult; use curve::Identity; use super::*; From 084c29c4ed6acb9bf3314420ff590056b4ea52f8 Mon Sep 17 00:00:00 2001 From: Henry & Isis Date: Mon, 20 Feb 2017 02:37:12 -0800 Subject: [PATCH 21/35] Implement addition and subtraction for DecafPoint. --- src/decaf.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/decaf.rs b/src/decaf.rs index 42d2721..d092d9d 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -25,6 +25,8 @@ use constants; use field::FieldElement; use subtle::CTAssignable; +use core::ops::{Add, Sub, Neg}; + use curve::ExtendedPoint; // ------------------------------------------------------------------------ @@ -151,6 +153,33 @@ impl DecafPoint { } } +// ------------------------------------------------------------------------ +// Arithmetic +// ------------------------------------------------------------------------ + +impl<'a, 'b> Add<&'b DecafPoint> for &'a DecafPoint { + type Output = DecafPoint; + + fn add(self, other: &'b DecafPoint) -> DecafPoint { + DecafPoint(&self.0 + &other.0) + } +} + +impl<'a, 'b> Sub<&'b DecafPoint> for &'a DecafPoint { + type Output = DecafPoint; + + fn sub(self, other: &'b DecafPoint) -> DecafPoint { + DecafPoint(&self.0 - &other.0) + } +} + +impl<'a> Neg for &'a DecafPoint { + type Output = DecafPoint; + + fn neg(self) -> DecafPoint { + DecafPoint(-&self.0) + } +} // ------------------------------------------------------------------------ // Debug traits From bf19df64f072c6dfb8886bcd9fe967162f667d15 Mon Sep 17 00:00:00 2001 From: Henry & Isis Date: Mon, 20 Feb 2017 03:06:16 -0800 Subject: [PATCH 22/35] Add ScalarMult and BasepointMult impls for DecafPoint --- src/decaf.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/decaf.rs b/src/decaf.rs index d092d9d..f549a78 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -28,6 +28,9 @@ use subtle::CTAssignable; use core::ops::{Add, Sub, Neg}; use curve::ExtendedPoint; +use curve::BasepointMult; +use curve::ScalarMult; +use scalar::Scalar; // ------------------------------------------------------------------------ // Compressed points @@ -181,6 +184,22 @@ impl<'a> Neg for &'a DecafPoint { } } +impl ScalarMult for DecafPoint { + fn scalar_mult(&self, scalar: &Scalar) -> DecafPoint { + DecafPoint(self.0.scalar_mult(scalar)) + } +} + +impl BasepointMult for DecafPoint { + fn basepoint() -> DecafPoint { + DecafPoint(constants::BASEPOINT) + } + + fn basepoint_mult(scalar: &Scalar) -> DecafPoint { + DecafPoint(ExtendedPoint::basepoint_mult(scalar)) + } +} + // ------------------------------------------------------------------------ // Debug traits // ------------------------------------------------------------------------ From db17dc4b786d02917022174ef9dbe52d3cd9b3fc Mon Sep 17 00:00:00 2001 From: Henry & Isis Date: Mon, 20 Feb 2017 03:14:25 -0800 Subject: [PATCH 23/35] Add Eq implementation for DecafPoint --- src/decaf.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/decaf.rs b/src/decaf.rs index f549a78..f46e53d 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -156,6 +156,22 @@ impl DecafPoint { } } +// ------------------------------------------------------------------------ +// Equality +// ------------------------------------------------------------------------ + +/// XXX check whether there's a simple way to do equality checking +/// with cofactor 8, not just cofactor 4, and add a CT equality function? +impl PartialEq for DecafPoint { + fn eq(&self, other: &DecafPoint) -> bool { + let self_compressed = self.compress(); + let other_compressed = other.compress(); + self_compressed == other_compressed + } +} + +impl Eq for DecafPoint {} + // ------------------------------------------------------------------------ // Arithmetic // ------------------------------------------------------------------------ From 375d326e75863421ad42483be1bde524d2ec1049 Mon Sep 17 00:00:00 2001 From: Henry & Isis Date: Mon, 20 Feb 2017 03:15:08 -0800 Subject: [PATCH 24/35] Change some tests to use new API --- src/decaf.rs | 38 +++++++++++--------------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/src/decaf.rs b/src/decaf.rs index f46e53d..0a5eea6 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -266,47 +266,31 @@ mod test { #[test] fn test_decaf_basepoint_roundtrip() { - // XXX fix up this test - let bp = BASE_CMPRSSD.decompress().unwrap(); - let bp_decaf = DecafPoint(bp).compress(); - let bp_recaf = bp_decaf.decompress().unwrap().0; - let diff = &bp - &bp_recaf; - let diff2 = diff.double(); - let diff4 = diff2.double(); - //println!("bp {:?}", bp); - //println!("bp_decaf {:?}", bp_decaf); - //println!("bp_recaf {:?}", bp_recaf); - //println!("diff {:?}", diff.compress()); - //println!("diff2 {:?}", diff2.compress()); - //println!("diff4 {:?}", diff4.compress()); + let bp_compressed_decaf = DecafPoint::basepoint().compress(); + let bp_recaf = bp_compressed_decaf.decompress().unwrap().0; + // Check that bp_recaf differs from bp by a point of order 4 + let diff = &ExtendedPoint::basepoint() - &bp_recaf; + let diff4 = diff.mult_by_pow_2(4); assert_eq!(diff4.compress(), ExtendedPoint::identity().compress()); } #[test] fn test_decaf_four_torsion_basepoint() { - //println!(""); - let bp = BASE_CMPRSSD.decompress().unwrap(); - let bp_decaf = DecafPoint(bp).compress(); - //println!("orig, {:?}", bp.compress()); + let bp = DecafPoint::basepoint(); for i in (0..8).filter(|x| x % 2 == 0) { - let Q = &bp + &constants::EIGHT_TORSION[i]; - //println!("{}, {:?}", i, Q.compress()); - assert_eq!(DecafPoint(Q).compress(), bp_decaf); + let Q = &bp + &DecafPoint(constants::EIGHT_TORSION[i]); + assert_eq!(Q, bp); } } #[test] fn test_decaf_four_torsion_random() { - //println!(""); let mut rng = OsRng::new().unwrap(); let s = Scalar::random(&mut rng); - let P = ExtendedPoint::basepoint_mult(&s); - let P_decaf = DecafPoint(P).compress(); - //println!("orig, {:?}", P.compress()); + let P = DecafPoint::basepoint_mult(&s); for i in (0..8).filter(|x| x % 2 == 0) { - let Q = &P + &constants::EIGHT_TORSION[i]; - //println!("{}, {:?}", i, Q.compress()); - assert_eq!(DecafPoint(Q).compress(), P_decaf); + let Q = &P + &DecafPoint(constants::EIGHT_TORSION[i]); + assert_eq!(Q, P); } } } From f06afbc4933c6e4217e88ce6bc9dcc60798af686 Mon Sep 17 00:00:00 2001 From: Henry & Isis Date: Mon, 20 Feb 2017 03:38:31 -0800 Subject: [PATCH 25/35] Add a coset4 function for debugging --- src/decaf.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/decaf.rs b/src/decaf.rs index 0a5eea6..61d926c 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -154,6 +154,15 @@ impl DecafPoint { s.negate(); CompressedDecaf(s.abs_decaf().to_bytes()) } + + /// Return the coset self + E[4], for debugging. + fn coset4(&self) -> [ExtendedPoint; 4] { + [ self.0 + , &self.0 + &constants::EIGHT_TORSION[2] + , &self.0 + &constants::EIGHT_TORSION[4] + , &self.0 + &constants::EIGHT_TORSION[6] + ] + } } // ------------------------------------------------------------------------ @@ -228,7 +237,9 @@ impl Debug for CompressedDecaf { impl Debug for DecafPoint { fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - write!(f, "DecafPoint: {:?}", &self.0) + let coset = self.coset4(); + write!(f, "DecafPoint: coset \n{:?}\n{:?}\n{:?}\n{:?}", + coset[0], coset[1], coset[2], coset[3]) } } @@ -277,9 +288,9 @@ mod test { #[test] fn test_decaf_four_torsion_basepoint() { let bp = DecafPoint::basepoint(); - for i in (0..8).filter(|x| x % 2 == 0) { - let Q = &bp + &DecafPoint(constants::EIGHT_TORSION[i]); - assert_eq!(Q, bp); + let bp_coset = bp.coset4(); + for i in 0..4 { + assert_eq!(bp, DecafPoint(bp_coset[i])); } } @@ -288,9 +299,9 @@ mod test { let mut rng = OsRng::new().unwrap(); let s = Scalar::random(&mut rng); let P = DecafPoint::basepoint_mult(&s); - for i in (0..8).filter(|x| x % 2 == 0) { - let Q = &P + &DecafPoint(constants::EIGHT_TORSION[i]); - assert_eq!(Q, P); + let P_coset = P.coset4(); + for i in 0..4 { + assert_eq!(P, DecafPoint(P_coset[i])); } } } From ea5efcdc29eda2bf43b13841e1bfe0ad1e132b60 Mon Sep 17 00:00:00 2001 From: Henry & Isis Date: Mon, 20 Feb 2017 03:45:49 -0800 Subject: [PATCH 26/35] Add impl of Identity for DecafPoint and CompressedDecaf --- src/decaf.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/decaf.rs b/src/decaf.rs index 61d926c..06cf656 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -30,6 +30,7 @@ use core::ops::{Add, Sub, Neg}; use curve::ExtendedPoint; use curve::BasepointMult; use curve::ScalarMult; +use curve::Identity; use scalar::Scalar; // ------------------------------------------------------------------------ @@ -79,6 +80,12 @@ impl CompressedDecaf { } } +impl Identity for CompressedDecaf { + fn identity() -> CompressedDecaf { + CompressedDecaf([0u8;32]) + } +} + /// A point in a prime-order group. /// /// XXX think about how this API should work @@ -165,6 +172,12 @@ impl DecafPoint { } } +impl Identity for DecafPoint { + fn identity() -> DecafPoint { + DecafPoint(ExtendedPoint::identity()) + } +} + // ------------------------------------------------------------------------ // Equality // ------------------------------------------------------------------------ @@ -262,7 +275,7 @@ mod test { #[test] fn test_decaf_decompress_id() { - let compressed_id = CompressedDecaf([0u8; 32]); + let compressed_id = CompressedDecaf::identity(); let id = compressed_id.decompress().unwrap(); // This should compress (as ed25519) to the following: let mut bytes = [0u8; 32]; bytes[0] = 1; @@ -271,8 +284,8 @@ mod test { #[test] fn test_decaf_compress_id() { - let id = DecafPoint(ExtendedPoint::identity()); - assert_eq!(id.compress(), CompressedDecaf([0u8; 32])); + let id = DecafPoint::identity(); + assert_eq!(id.compress(), CompressedDecaf::identity()); } #[test] From c2bee88c0f591700cbc1e8aaad1d350828776254 Mon Sep 17 00:00:00 2001 From: Henry & Isis Date: Mon, 20 Feb 2017 04:04:12 -0800 Subject: [PATCH 27/35] Add a feature gate for unfinished implementations. --- Cargo.toml | 1 + src/lib.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 2733c45..cb59405 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ version = "0.3" [features] default = ["std"] std = ["rand"] +yolocrypto = [] # The development profile, used for `cargo build`. [profile.dev] diff --git a/src/lib.rs b/src/lib.rs index bd62628..1b4e73d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,9 @@ extern crate rand; pub mod field; pub mod scalar; pub mod curve; + +// Feature gate decaf while our implementation is unfinished and probably incorrect. +#[cfg(feature = "yolocrypto")] pub mod decaf; // Constant-time functions and other miscelaneous utilities. From 9cb48c44ba623c7f17eb0096d964d6a9b837efc3 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Mon, 20 Feb 2017 15:17:29 -0800 Subject: [PATCH 28/35] Use conditional_negate in Decaf --- src/decaf.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/decaf.rs b/src/decaf.rs index 06cf656..6395610 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -24,6 +24,7 @@ use core::fmt::Debug; use constants; use field::FieldElement; use subtle::CTAssignable; +use subtle::CTNegatable; use core::ops::{Add, Sub, Neg}; @@ -133,8 +134,7 @@ impl DecafPoint { let iY = &Y * &constants::SQRT_M1; X.conditional_assign(&iY, is_neg_mask); Y.conditional_assign(&iX, is_neg_mask); - let minus_T = -&T; - T.conditional_assign(&minus_T, is_neg_mask); + T.conditional_negate(is_neg_mask); // Step 1: Compute r = 1/sqrt((a-d)(Z+Y)(Z-Y)) let Z_plus_Y = &self.0.Z + &Y; @@ -149,17 +149,19 @@ impl DecafPoint { // Step 3: Negate r if -2uZ is negative. let uZ = &u * &self.0.Z; - let minus_r = -&r; let m2uZ = -&(&uZ + &uZ); - let mask = m2uZ.is_negative_decaf(); - r.conditional_assign(&minus_r, mask); + r.conditional_negate(m2uZ.is_negative_decaf()); - // Step 4: Compute s = |u(r(aZX - dYT)+Y)/a| + // Step 4: Compute s = | u(r(aZX - dYT)+Y)/a| + // = |-u(r(aZX - dYT)+Y)| let minus_ZX = -&(&self.0.Z * &X); let dYT = &constants::d * &(&Y * &T); + // Compute s = u(r(aZX - dYT)+Y) let mut s = &u * &(&(&r * &(&minus_ZX - &dYT)) + &Y); - s.negate(); - CompressedDecaf(s.abs_decaf().to_bytes()) + // Set s <- |-s| + let neg = s.is_nonnegative_decaf(); + s.conditional_negate(neg); + CompressedDecaf(s.to_bytes()) } /// Return the coset self + E[4], for debugging. From 93be83c6df1332877c0cc0112c7f572cbd209b1c Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Mon, 20 Feb 2017 16:58:01 -0800 Subject: [PATCH 29/35] Check that s = |s| in decompression. --- src/decaf.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/decaf.rs b/src/decaf.rs index 6395610..4683072 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -54,9 +54,15 @@ impl CompressedDecaf { /// Attempt to decompress to an `DecafPoint`. pub fn decompress(&self) -> Option { // XXX should decoding be CT ? - // XXX should reject unless s = |s| // XXX need to check that xy is nonnegative and reject otherwise let s = FieldElement::from_bytes(&self.0); + + // Check that s = |s| and reject otherwise. + let mut abs_s = s; + let neg = abs_s.is_negative_decaf(); + abs_s.conditional_negate(neg); + if abs_s != s { return None; } + let ss = s.square(); let X = &s + &s; // X = 2s let Z = &FieldElement::one() - &ss; // Z = 1+as^2 @@ -275,6 +281,14 @@ mod test { use curve::Identity; use super::*; + #[test] + #[should_panic] + fn test_decaf_decompress_negative_s_fails() { + // constants::d is neg, so decompression should fail as |d| != d. + let bad_compressed = CompressedDecaf(constants::d.to_bytes()); + bad_compressed.decompress().unwrap(); + } + #[test] fn test_decaf_decompress_id() { let compressed_id = CompressedDecaf::identity(); From 6b3dbf6870703f0371640ccbb6b1fb7a45d16018 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Mon, 20 Feb 2017 18:22:58 -0800 Subject: [PATCH 30/35] Add another test and some notes --- src/decaf.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/decaf.rs b/src/decaf.rs index 4683072..1fd70ec 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -165,6 +165,8 @@ impl DecafPoint { // Compute s = u(r(aZX - dYT)+Y) let mut s = &u * &(&(&r * &(&minus_ZX - &dYT)) + &Y); // Set s <- |-s| + // XXX I think there's a sign error somewhere? + // flipping the sign here makes the tests pass let neg = s.is_nonnegative_decaf(); s.conditional_negate(neg); CompressedDecaf(s.to_bytes()) @@ -237,6 +239,8 @@ impl ScalarMult for DecafPoint { } impl BasepointMult for DecafPoint { + // XXX is this actually in the image of the isogeny, + // or do we need a different basepoint? fn basepoint() -> DecafPoint { DecafPoint(constants::BASEPOINT) } @@ -333,4 +337,18 @@ mod test { assert_eq!(P, DecafPoint(P_coset[i])); } } + + #[test] + fn test_decaf_random_roundtrip() { + let mut rng = OsRng::new().unwrap(); + for j in 0..100 { + let s = Scalar::random(&mut rng); + let P = DecafPoint::basepoint_mult(&s); + let compressed_P = P.compress(); + let Q = compressed_P.decompress().unwrap(); + for i in 0..4 { + assert_eq!(P, Q); + } + } + } } From 5daa217b1896d34621612d117e7a0a32adf868f7 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 21 Feb 2017 11:39:07 -0800 Subject: [PATCH 31/35] Fix sign error --- src/decaf.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/decaf.rs b/src/decaf.rs index 1fd70ec..83a417f 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -159,15 +159,12 @@ impl DecafPoint { r.conditional_negate(m2uZ.is_negative_decaf()); // Step 4: Compute s = | u(r(aZX - dYT)+Y)/a| - // = |-u(r(aZX - dYT)+Y)| + // = |u(r(-ZX - dYT)+Y)| since a = -1 let minus_ZX = -&(&self.0.Z * &X); let dYT = &constants::d * &(&Y * &T); - // Compute s = u(r(aZX - dYT)+Y) + // Compute s = u(r(aZX - dYT)+Y) and cnegate for abs let mut s = &u * &(&(&r * &(&minus_ZX - &dYT)) + &Y); - // Set s <- |-s| - // XXX I think there's a sign error somewhere? - // flipping the sign here makes the tests pass - let neg = s.is_nonnegative_decaf(); + let neg = s.is_negative_decaf(); s.conditional_negate(neg); CompressedDecaf(s.to_bytes()) } From 95bf09a335cb6de47964f064979dcab32a9e9a89 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 21 Feb 2017 12:33:57 -0800 Subject: [PATCH 32/35] Check xy is nonnegative, y nonzero in decoding. --- src/decaf.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/decaf.rs b/src/decaf.rs index 83a417f..98e9386 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -83,7 +83,16 @@ impl CompressedDecaf { let Y = &w * &Z; let T = &w * &X; - Some(DecafPoint(ExtendedPoint{ X: X, Y: Y, Z: Z, T: T })) + // "To decode the point, one must decode it to affine form + // instead of projective, and check that xy is non-negative." + // + // XXX can we merge this inversion with the one above? + let xy = &T * &Z.invert(); + if (Y.is_nonzero() & xy.is_nonnegative_decaf()) == 1u8 { + Some(DecafPoint(ExtendedPoint{ X: X, Y: Y, Z: Z, T: T })) + } else { + None + } } } From dd8f17dfb3f0683b5a3c2b236bbb9958cca3dc3e Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 21 Feb 2017 12:51:27 -0800 Subject: [PATCH 33/35] Add note on feature-gating --- src/decaf.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/decaf.rs b/src/decaf.rs index 98e9386..0d7bbad 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -12,6 +12,9 @@ //! An implementation of Mike Hamburg's Decaf cofactor-eliminating //! point-compression scheme, providing a prime-order group on top of //! a non-prime-order elliptic curve. +//! +//! Note: this code is currently feature-gated with the `yolocrypto` +//! feature flag, because our implementation is still unfinished. // We allow non snake_case names because coordinates in projective space are // traditionally denoted by the capitalisation of their respective From 9473299d048a1f9e9ced7aac18291273b8425bf3 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 21 Feb 2017 14:58:41 -0800 Subject: [PATCH 34/35] Add benchmarks for Decaf [de]compression --- src/decaf.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/decaf.rs b/src/decaf.rs index 0d7bbad..715190e 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -361,3 +361,29 @@ mod test { } } } + +#[cfg(test)] +mod bench { + use rand::OsRng; + use test::Bencher; + + use super::*; + + #[bench] + fn decompression(b: &mut Bencher) { + let mut rng = OsRng::new().unwrap(); + let s = Scalar::random(&mut rng); + let P = DecafPoint::basepoint_mult(&s); + let P_compressed = P.compress(); + b.iter(|| P_compressed.decompress().unwrap()); + } + + #[bench] + fn compression(b: &mut Bencher) { + let mut rng = OsRng::new().unwrap(); + let s = Scalar::random(&mut rng); + let P = DecafPoint::basepoint_mult(&s); + b.iter(|| P.compress()); + } +} + From f93dd4ccf5980e58109849788f955e1522dab909 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 21 Feb 2017 15:11:24 -0800 Subject: [PATCH 35/35] Use is_identity() in the torsion subgroup tests. ht @dconnolly for pointing this out --- src/constants.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index f12861a..2f57e0e 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1573,6 +1573,7 @@ mod test { use curve::CompressedEdwardsY; use curve::ExtendedPoint; use curve::Identity; + use curve::IsIdentity; use curve::ValidityCheck; use constants; @@ -1584,7 +1585,7 @@ mod test { for i in 0..8 { let Q = constants::EIGHT_TORSION[i].mult_by_pow_2(3); assert!(Q.is_valid()); - assert!(Q.compress() == compressed_id); + assert!(Q.is_identity()); } } @@ -1596,7 +1597,7 @@ mod test { for i in (0..8).filter(|i| i % 2 == 0) { let Q = constants::EIGHT_TORSION[i].mult_by_pow_2(2); assert!(Q.is_valid()); - assert!(Q.compress() == compressed_id); + assert!(Q.is_identity()); } } @@ -1608,7 +1609,7 @@ mod test { for i in (0..8).filter(|i| i % 4 == 0) { let Q = constants::EIGHT_TORSION[i].mult_by_pow_2(1); assert!(Q.is_valid()); - assert!(Q.compress() == compressed_id); + assert!(Q.is_identity()); } }