From 21c588aba08e3c93d0a4bb2475bcf2b532fe748e Mon Sep 17 00:00:00 2001 From: Isis Lovecruft <4300572+plotskogwq@users.noreply.github.com> Date: Thu, 18 May 2017 23:00:55 +0000 Subject: [PATCH 01/18] Implement {AddAssign, SubAssign} for ExtendedPoint. --- src/curve.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/curve.rs b/src/curve.rs index a9387a6..9d34f6f 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -83,6 +83,7 @@ use collections::Vec; use core::fmt::Debug; use core::iter::Iterator; use core::ops::{Add, Sub, Neg}; +use core::ops::{AddAssign, SubAssign}; use core::ops::{Mul, MulAssign}; use core::ops::Index; @@ -802,6 +803,12 @@ impl<'a,'b> Add<&'b ExtendedPoint> for &'a ExtendedPoint { } } +impl<'b> AddAssign<&'b ExtendedPoint> for ExtendedPoint { + fn add_assign(&mut self, _rhs: &'b ExtendedPoint) { + *self = (self as &ExtendedPoint) + _rhs; + } +} + impl<'a,'b> Sub<&'b ExtendedPoint> for &'a ExtendedPoint { type Output = ExtendedPoint; fn sub(self, other: &'b ExtendedPoint) -> ExtendedPoint { @@ -809,6 +816,12 @@ impl<'a,'b> Sub<&'b ExtendedPoint> for &'a ExtendedPoint { } } +impl<'b> SubAssign<&'b ExtendedPoint> for ExtendedPoint { + fn sub_assign(&mut self, _rhs: &'b ExtendedPoint) { + *self = (self as &ExtendedPoint) - _rhs; + } +} + impl<'a> Neg for &'a ExtendedPoint { type Output = ExtendedPoint; From 8d175989b77cbb79d2fab2bf911ba047a0c17f25 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft <4300572+plotskogwq@users.noreply.github.com> Date: Thu, 18 May 2017 23:01:31 +0000 Subject: [PATCH 02/18] Add a divider to separate Neg code from Add/Sub. --- src/curve.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/curve.rs b/src/curve.rs index 9d34f6f..3656174 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -822,6 +822,10 @@ impl<'b> SubAssign<&'b ExtendedPoint> for ExtendedPoint { } } +// ------------------------------------------------------------------------ +// Negation +// ------------------------------------------------------------------------ + impl<'a> Neg for &'a ExtendedPoint { type Output = ExtendedPoint; From 05eb437b788724598b2f221a4bbb8f621ef9b98e Mon Sep 17 00:00:00 2001 From: Isis Lovecruft <4300572+plotskogwq@users.noreply.github.com> Date: Thu, 18 May 2017 23:02:39 +0000 Subject: [PATCH 03/18] Implement {AddAssign, SubAssign} for DecafPoint. --- src/decaf.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/decaf.rs b/src/decaf.rs index a97347f..4c4cd6c 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -36,6 +36,7 @@ use subtle::CTAssignable; use subtle::CTNegatable; use core::ops::{Add, Sub, Neg}; +use core::ops::{AddAssign, SubAssign}; use core::ops::{Mul, MulAssign}; use curve; @@ -427,6 +428,12 @@ impl<'a, 'b> Add<&'b DecafPoint> for &'a DecafPoint { } } +impl<'b> AddAssign<&'b DecafPoint> for DecafPoint { + fn add_assign(&mut self, _rhs: &DecafPoint) { + *self = (self as &DecafPoint) + _rhs; + } +} + impl<'a, 'b> Sub<&'b DecafPoint> for &'a DecafPoint { type Output = DecafPoint; @@ -435,6 +442,12 @@ impl<'a, 'b> Sub<&'b DecafPoint> for &'a DecafPoint { } } +impl<'b> SubAssign<&'b DecafPoint> for DecafPoint { + fn sub_assign(&mut self, _rhs: &DecafPoint) { + *self = (self as &DecafPoint) - _rhs; + } +} + impl<'a> Neg for &'a DecafPoint { type Output = DecafPoint; From 1fe88acbb42535b7cc161ba4f81b7e4aa238a66e Mon Sep 17 00:00:00 2001 From: Manish Goregaokar <4300572+plotskogwq@users.noreply.github.com> Date: Fri, 19 May 2017 16:44:05 -0700 Subject: [PATCH 04/18] Add cargo-fuzz --- fuzz/.gitignore | 4 ++++ fuzz/Cargo.toml | 24 ++++++++++++++++++++++++ fuzz/fuzzers/decaf.rs | 21 +++++++++++++++++++++ src/decaf.rs | 2 +- 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzzers/decaf.rs diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000..572e03b --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ + +target +corpus +artifacts diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..5245cbe --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,24 @@ + +[package] +name = "curve25519-dalek-fuzz" +version = "0.0.1" +authors = ["Automatically generated"] +publish = false + +[package.metadata] +cargo-fuzz = true + +[dependencies.curve25519-dalek] +path = ".." +features = ["yolocrypto"] + +[dependencies.libfuzzer-sys] +git = "https://github.com/rust-fuzz/libfuzzer-sys.git" + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "decaf" +path = "fuzzers/decaf.rs" diff --git a/fuzz/fuzzers/decaf.rs b/fuzz/fuzzers/decaf.rs new file mode 100644 index 0000000..5199183 --- /dev/null +++ b/fuzz/fuzzers/decaf.rs @@ -0,0 +1,21 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate curve25519_dalek; + +use curve25519_dalek::curve::ValidityCheck; +use curve25519_dalek::decaf::DecafPoint; +use curve25519_dalek::field::FieldElement; + +fuzz_target!(|data: &[u8]| { + if data.len() != 32 { + return; + } + let mut field_bytes = [0u8; 32]; + for (by, data) in field_bytes.iter_mut().zip(data.iter()) { + *by = *data; + } + let fe = FieldElement::from_bytes(&field_bytes); + let p = DecafPoint::elligator_decaf_flavour(&fe); + assert!(p.0.is_valid()); + p.compress(); +}); diff --git a/src/decaf.rs b/src/decaf.rs index a97347f..6a12816 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -264,7 +264,7 @@ impl DecafPoint { /// /// This method is not public because it's just used for hashing /// to a point -- proper elligator support is deferred for now. - fn elligator_decaf_flavour(r_0: &FieldElement) -> DecafPoint { + pub fn elligator_decaf_flavour(r_0: &FieldElement) -> DecafPoint { // Follows Appendix C of the Decaf paper. // Use n = 2 as the quadratic nonresidue so that n*x = x + x. From 7d35d3a3184bc34c45ac1d9f7e4fe80d354c3543 Mon Sep 17 00:00:00 2001 From: Henry de Valence <4300572+plotskogwq@users.noreply.github.com> Date: Sat, 20 May 2017 21:22:01 -0700 Subject: [PATCH 05/18] Revert to previous quoting scheme to fix broken Travis builds --- .travis.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index f9f46f3..af16309 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,12 +6,12 @@ rust: - nightly env: - - TEST_COMMAND=test FEATURES=--features="yolocrypto" - - TEST_COMMAND=test FEATURES=--features="yolocrypto serde" - - TEST_COMMAND=test FEATURES=--features="yolocrypto nightly" - - TEST_COMMAND=bench FEATURES=--features="yolocrypto bench" - - TEST_COMMAND=bench FEATURES=--features="yolocrypto nightly bench" - - TEST_COMMAND=build FEATURES=--no-default-features + - TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='yolocrypto' + - TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='yolocrypto serde' + - TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='yolocrypto nightly' + - TEST_COMMAND=bench EXTRA_FLAGS='' FEATURES='yolocrypto bench' + - TEST_COMMAND=bench EXTRA_FLAGS='' FEATURES='yolocrypto nightly bench' + - TEST_COMMAND=build EXTRA_FLAGS=--no-default-features FEATURES='' matrix: exclude: @@ -20,23 +20,23 @@ matrix: # run benchmarks, which causes dalek not to build on stable. See # https://github.com/isislovecruft/curve25519-dalek/pull/38#issuecomment-286027562 - rust: stable - env: TEST_COMMAND=bench FEATURES=--features="yolocrypto bench" + env: TEST_COMMAND=bench EXTRA_FLAGS='' FEATURES='yolocrypto bench' - rust: beta - env: TEST_COMMAND=bench FEATURES=--features="yolocrypto bench" + env: TEST_COMMAND=bench EXTRA_FLAGS='' FEATURES='yolocrypto bench' - rust: stable - env: TEST_COMMAND=bench FEATURES=--features="yolocrypto nightly bench" + env: TEST_COMMAND=bench EXTRA_FLAGS='' FEATURES='yolocrypto nightly bench' - rust: beta - env: TEST_COMMAND=bench FEATURES=--features="yolocrypto nightly bench" + env: TEST_COMMAND=bench EXTRA_FLAGS='' FEATURES='yolocrypto nightly bench' # Test nightly features, such as radix_51, only on nightly. - rust: stable - env: TEST_COMMAND=test FEATURES=--features="yolocrypto nightly" + env: TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='yolocrypto nightly' - rust: beta - env: TEST_COMMAND=test FEATURES=--features="yolocrypto nightly" + env: TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='yolocrypto nightly' # Test no_std only on nightly. - rust: stable - env: TEST_COMMAND=build FEATURES=--no-default-features + env: TEST_COMMAND=build EXTRA_FLAGS=--no-default-features FEATURES='' - rust: beta - env: TEST_COMMAND=build FEATURES=--no-default-features + env: TEST_COMMAND=build EXTRA_FLAGS=--no-default-features FEATURES='' script: - - cargo $TEST_COMMAND $FEATURES + - cargo $TEST_COMMAND --features="$FEATURES" $EXTRA_FLAGS From dd9f604a988f09506bc14682fd1111b86d086e1c Mon Sep 17 00:00:00 2001 From: Henry de Valence <4300572+plotskogwq@users.noreply.github.com> Date: Sat, 20 May 2017 02:34:43 -0700 Subject: [PATCH 06/18] Add a test against current encodings of small multiples of the ed25519 basepoint --- src/decaf.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/decaf.rs b/src/decaf.rs index e5a6c45..0457dd1 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -636,6 +636,34 @@ mod test { assert_eq!(diff4.compress_edwards(), CompressedEdwardsY::identity()); } + #[test] + fn encodings_of_small_multiples_of_basepoint() { + // Table of encodings of (1+i)*basepoint + let compressed = [ + CompressedDecaf([141, 190, 226, 107, 177, 201, 35, 118, 14, 55, 160, 165, 242, 207, 121, 161, 177, 80, 8, 132, 205, 254, 101, 169, 233, 65, 124, 96, 255, 182, 249, 40]), + CompressedDecaf([131, 57, 148, 16, 8, 196, 141, 82, 144, 220, 105, 112, 66, 33, 48, 16, 182, 198, 173, 35, 248, 181, 92, 231, 222, 35, 85, 56, 5, 252, 91, 40]), + CompressedDecaf([199, 132, 32, 144, 156, 143, 81, 170, 240, 56, 232, 6, 178, 37, 118, 190, 110, 201, 26, 173, 156, 97, 59, 162, 240, 247, 226, 107, 197, 111, 107, 26]), + CompressedDecaf([210, 120, 34, 214, 175, 27, 61, 6, 229, 181, 216, 36, 11, 245, 146, 232, 130, 215, 77, 29, 210, 30, 54, 155, 191, 81, 59, 124, 174, 3, 135, 36]), + CompressedDecaf([155, 52, 159, 52, 189, 27, 181, 0, 245, 131, 0, 197, 79, 208, 252, 122, 104, 161, 245, 143, 67, 94, 13, 129, 153, 173, 129, 179, 118, 231, 90, 52]), + CompressedDecaf([42, 117, 252, 118, 8, 1, 72, 25, 111, 246, 247, 103, 236, 86, 235, 29, 100, 156, 186, 209, 159, 21, 61, 26, 249, 25, 137, 228, 84, 23, 10, 27]), + CompressedDecaf([21, 126, 181, 117, 58, 90, 216, 28, 184, 57, 9, 23, 158, 68, 159, 171, 109, 150, 232, 140, 144, 73, 139, 122, 124, 105, 125, 160, 94, 185, 150, 52]), + CompressedDecaf([232, 167, 112, 233, 126, 33, 105, 63, 151, 6, 88, 225, 181, 17, 223, 12, 116, 138, 203, 47, 243, 225, 50, 171, 21, 220, 186, 179, 132, 20, 48, 6]), + CompressedDecaf([99, 44, 97, 48, 242, 174, 78, 198, 112, 154, 146, 36, 239, 34, 94, 4, 0, 244, 175, 34, 46, 0, 83, 187, 5, 163, 225, 63, 51, 237, 234, 22]), + CompressedDecaf([2, 33, 89, 176, 178, 123, 159, 75, 235, 172, 251, 11, 137, 177, 90, 122, 149, 186, 52, 243, 153, 190, 185, 202, 59, 137, 204, 160, 150, 152, 148, 55]), + CompressedDecaf([245, 79, 78, 226, 114, 69, 247, 112, 18, 54, 90, 225, 176, 77, 231, 235, 196, 123, 49, 221, 34, 205, 151, 228, 244, 112, 82, 58, 30, 31, 58, 12]), + CompressedDecaf([135, 53, 175, 167, 13, 94, 62, 31, 29, 248, 13, 132, 29, 69, 7, 188, 145, 49, 62, 55, 181, 109, 214, 11, 248, 162, 70, 15, 236, 126, 100, 60]), + CompressedDecaf([98, 150, 69, 229, 144, 122, 237, 107, 127, 177, 33, 64, 59, 173, 210, 102, 74, 34, 23, 16, 252, 117, 14, 97, 231, 178, 63, 193, 157, 28, 178, 17]), + CompressedDecaf([222, 104, 6, 1, 72, 12, 72, 178, 204, 238, 128, 70, 41, 150, 235, 96, 153, 150, 18, 4, 141, 206, 0, 38, 122, 112, 249, 51, 94, 251, 20, 57]), + CompressedDecaf([7, 221, 140, 57, 13, 146, 248, 27, 56, 4, 128, 23, 145, 120, 126, 4, 158, 173, 52, 213, 164, 250, 26, 55, 89, 96, 187, 111, 211, 18, 63, 19]), + CompressedDecaf([91, 213, 193, 10, 102, 92, 199, 124, 61, 176, 1, 47, 111, 59, 183, 91, 79, 56, 208, 109, 172, 209, 17, 167, 229, 216, 3, 236, 200, 208, 15, 20]), + ]; + let mut bp = constants::DECAF_ED25519_BASEPOINT; + for i in 0..16 { + assert_eq!(bp.compress(), compressed[i]); + bp = &bp + &constants::DECAF_ED25519_BASEPOINT; + } + } + #[test] fn decaf_four_torsion_basepoint() { let bp = constants::DECAF_ED25519_BASEPOINT; From f5b2707087f47f67f9997f53035fe867ad263a39 Mon Sep 17 00:00:00 2001 From: Henry de Valence <4300572+plotskogwq@users.noreply.github.com> Date: Sat, 20 May 2017 15:48:44 -0700 Subject: [PATCH 07/18] Benchmark Edwards decompression and compression --- src/curve.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/curve.rs b/src/curve.rs index a9387a6..f4c1b6e 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -1654,6 +1654,18 @@ mod bench { use super::*; use super::test::{A_SCALAR}; + #[bench] + fn edwards_decompress(b: &mut Bencher) { + let B = &constants::BASE_CMPRSSD; + b.iter(|| B.decompress().unwrap()); + } + + #[bench] + fn edwards_compress(b: &mut Bencher) { + let B = &constants::ED25519_BASEPOINT; + b.iter(|| B.compress_edwards()); + } + #[bench] fn basepoint_mult(b: &mut Bencher) { let B = &constants::ED25519_BASEPOINT_TABLE; From 08cf1a88b693060223aebf9241ff2b917a36dc8c Mon Sep 17 00:00:00 2001 From: Henry de Valence <4300572+plotskogwq@users.noreply.github.com> Date: Sat, 20 May 2017 15:52:00 -0700 Subject: [PATCH 08/18] Batch inversions in Decaf decompression. --- src/decaf.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/decaf.rs b/src/decaf.rs index 0457dd1..d118dac 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -78,14 +78,22 @@ impl CompressedDecaf { 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 ZZ = Z.square(); + let u = &ZZ- &(&constants::d4 * &ss); // u = Z^2 - 4ds^2 let uss = &u * &ss; + let ussZZ = &uss * &ZZ; - let (uss_is_nonzero_square, mut v) = uss.invsqrt(); - if (uss_is_nonzero_square | uss.is_zero()) == 0u8 { + if Z.is_zero() == 1u8 { return None; } + + // Batch inversion: set b = 1/sqrt(us^2 Z^2) + let (ussZZ_is_nonzero_square, b) = ussZZ.invsqrt(); + if (ussZZ_is_nonzero_square | uss.is_zero()) == 0u8 { return None; // us^2 is nonzero nonsquare } + let mut v = &b * &Z; // now v = 1/sqrt(us^2) + let Zinv = &b * &(&v * &uss); // now Zinv = b^2 Z us^2 = 1/Z + // 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 { @@ -99,9 +107,9 @@ impl CompressedDecaf { // "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(); + + // Use the value of 1/Z previously computed in the batch inversion + let xy = &T * &Zinv; if (Y.is_nonzero() & xy.is_nonnegative_decaf()) == 1u8 { Some(DecafPoint(ExtendedPoint{ X: X, Y: Y, Z: Z, T: T })) } else { From f2f95b90dc163b1dd2a1ca172be18b53c8e3cf0a Mon Sep 17 00:00:00 2001 From: Henry de Valence <4300572+plotskogwq@users.noreply.github.com> Date: Sat, 20 May 2017 16:17:14 -0700 Subject: [PATCH 09/18] Add constants for 1/sqrt(a-d) and 1/(a-d) --- src/constants.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/constants.rs b/src/constants.rs index 2d3c790..2d1062e 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -55,6 +55,23 @@ pub const a_minus_d: FieldElement = FieldElement([ 8787816, 6275908, 3247719, 18696448, 12055116, ]); #[cfg(feature="radix_51")] pub const a_minus_d: FieldElement = FieldElement([1321844580190025, 1785434093556034, 589740348686294, 217950738957124, 809005158844672]); +#[cfg(feature="radix_51")] +pub const invsqrt_a_minus_d: FieldElement = FieldElement([ + 278908739862762, 821645201101625, 8113234426968, 1777959178193151, 2118520810568447 +]); +#[cfg(not(feature="radix_51"))] +pub const invsqrt_a_minus_d: FieldElement = FieldElement([ + 6111485, 4156064, -27798727, 12243468, -25904040, + 120897, 20826367, -7060776, 6093568, -1986012 +]); +#[cfg(feature="radix_51")] +pub const inv_a_minus_d: FieldElement = FieldElement([ + 2251799813563563, 2251799813685247, 2251799813685247, 2251799813685247, 2251799813685247 +]); +#[cfg(not(feature="radix_51"))] +pub const inv_a_minus_d: FieldElement = FieldElement([ + -121666, 0, 0, 0, 0, 0, 0, 0, 0, 0 +]); /// (p-1)/2, in little-endian bytes. pub const HALF_P_MINUS_1_BYTES: [u8; 32] = @@ -3247,5 +3264,10 @@ mod test { let a = FieldElement::minus_one(); let a_minus_d = &a - &constants::d; assert_eq!(a_minus_d, constants::a_minus_d); + let (_, invsqrt_a_minus_d) = constants::a_minus_d.invsqrt(); + assert_eq!(invsqrt_a_minus_d, constants::invsqrt_a_minus_d); + let inv_a_minus_d = invsqrt_a_minus_d.square(); + assert_eq!(inv_a_minus_d, constants::inv_a_minus_d); + assert_eq!(&inv_a_minus_d * &a_minus_d, FieldElement::one()); } } From 0c89338f1845c2406b1f3a2ff1a5f5da703dc5b1 Mon Sep 17 00:00:00 2001 From: Henry de Valence <4300572+plotskogwq@users.noreply.github.com> Date: Sat, 20 May 2017 20:14:55 -0700 Subject: [PATCH 10/18] Use Mike Hamburg's trick for Decaf compression. --- src/decaf.rs | 126 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 104 insertions(+), 22 deletions(-) diff --git a/src/decaf.rs b/src/decaf.rs index d118dac..3ef2d7d 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -209,46 +209,127 @@ impl DecafPoint { // let untwisted_X = &self.X * &constants::MSQRT_M1; // etc. - // Step 0: pre-rotation, needed for Decaf with E[8] = Z/8 + // Step 0: pre-rotation, needed for Decaf with E[8] = Z/8. + // + // We want to select a point (x,y) in the coset P + E[4] with + // y nonzero and xy nonnegative. The naive approach is as + // follows. First, compute xy = T/Z and check that Y is + // nonzero and xy is nonnegative. If not, then "rotate" the + // original point by adding (x,y) + (i,0) = (iy,ix) = (x',y'): + // this rotated point has x'y' = -xy. Then perform the normal + // Decaf encoding, as described in Appendix A.1 of the Decaf + // paper, using the rotated point (x',y'). + // + // This is straightforward but requires an extra inversion. + // We would like to batch the inversion in xy = T/Z with the + // inverse square root in the computation of + // + // r = invsqrt((a-d)*(Z+Y)*(Z-Y)) + // = invsqrt(a-d)*invsqrt(Z^2-Y^2), + // + // but the X and Y we are trying to decode depend on whether + // we rotated the coset representative! + // + // However, it is possible to batch these inversions. Credit: + // the following explanation (and trick) is adapted from an + // email from Mike Hamburg, but of course any errors are ours. + // + // Let the initial point be ( X_0 : Y_0 : Z_0 : T_0). + // The rotated point is then (iY_0 : iX_0 : Z_0 : -T_0). + // + // We want to relate the computation of: + // + // invsqrt(Z^2 - Y^2) = invsqrt(Z_0^2 - Y_0^2) [non-rotated] + // invsqrt(Z^2 - Y^2) = invsqrt(Z_0^2 + X_0^2) [rotated] + // + // The curve equation in extended coordinates is + // + // 0 = (-X^2 + Y^2)*Z^2 - Z^4 - d*X^2*Y^2, + // + // so + // 0 = (-X^2 + Y^2)*Z^2 - Z^4 - d*T^2*Z^2 since XY=TZ + // = (-X^2 + Y^2 - Z^2 - d*T^2)*Z^2 + // = ( X^2 - Y^2 + Z^2 + d*T^2)*Z^2 mult by -1 + // -T^2*Z^2 = (X^2 - Y^2 + Z^2 + d*T^2)*Z^2 - T^2*Z^2 sub T^2*Z^2 + // -T^2*Z^2 = (X^2 - Y^2 + Z^2 - T^2)*Z^2 + d*T^2*Z^2 + // (-1-d)*T^2*Z^2 = (X^2 - Y^2 + Z^2 - T^2)*Z^2 + // + // for any point (X:Y:Z:T) in extended coordinates. Therefore, + // + // (Z^2 - Y^2)*(Z^2 + X^2) = Z^4 + Z^2*X^2 - Y^2*Z^2 - Y^2*X^2 + // = Z^4 + Z^2*X^2 - Y^2*Z^2 - T^2*Z^2 since XY=TZ + // = Z^2*(X^2 - Y^2 + Z^2 - T^2) + // = (-1-d)*T^2*Z^2. + // + // Taking square roots of both sides and rearranging, we get + // + // invsqrt(Z^2 - Y^2) = invsqrt(-1-d)*(Z^2+X^2)*(1/TZ)*invsqrt(Z^2+X^2) + // `-----------' `---------------------' + // curve constant batchable + // + // for any point (X:Y:Z:T) in extended coordinates. + // + // Therefore, we can do the computation with only one inverse + // square root like so: + // + // W <--- invsqrt((T_0 * Z_0)^2 * (Z_0^2+X_0^2)) + // = 1/(T_0 * Z_0 * sqrt(Z_0^2 + X_0^2)) + // + // xy <--- T_0^2 * W^2 * (T_0 * Z_0) * (Z_0^2 + X_0^2) + // = T_0 / Z_0 = xy + // + // if Y_0 nonzero and xy nonnegative: + // (X : Y : Z : T) <--- (X_0 : Y_0 : Z_0 : T_0) + // r <--- (1/(-1-d)) * (Z_0^2 + X_0^2) * W + // = invsqrt(a-d) * invsqrt(Z_0^2 - Y_0^2) since a = -1 + // = invsqrt(a-d) * invsqrt(Z^2 - Y^2) + // otherwise: + // (X : Y : Z : T) <--- (i*Y_0 : i*X_0 : Z_0 : -T_0) + // r <--- invsqrt(a-d) * (T_0 * Z_0) * W + // = invsqrt(a-d) * invsqrt(Z_0^2 + X_0^2) + // = invsqrt(a-d) * invsqrt(Z^2 - Y^2) + // + // The rest of the compression follows the steps in the + // appendix of the Decaf paper. let mut X = self.0.X; let mut Y = self.0.Y; let mut T = self.0.T; + let Z = &self.0.Z; - // 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) + let TZ = &T * Z; + let ZZ_plus_XX = &Z.square() + &X.square(); + let tmp = &TZ.square() * &ZZ_plus_XX; + let (tmp_is_nonzero_square, W) = tmp.invsqrt(); + // tmp should always be a square (why? related to being in the + // image of the isogeny?) + debug_assert_eq!( tmp_is_nonzero_square | tmp.is_zero(), 1u8 ); + + let xy = &T.square() * &(&W.square() * &(&TZ * &ZZ_plus_XX)); + let rotate = 1u8 & !(Y.is_nonzero() & xy.is_nonnegative_decaf()); + + let mut r = &W * &(&ZZ_plus_XX * &constants::inv_a_minus_d); + let r_rot = &W * &(&TZ * &constants::invsqrt_a_minus_d); - // XXX it should be possible to avoid this inversion, but - // let's make sure the code is correct first - 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); - 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; - let Z_minus_Y = &self.0.Z - &Y; - let t = &constants::a_minus_d * &(&Z_plus_Y * &Z_minus_Y); - let (t_is_nonzero_square, mut r) = t.invsqrt(); - // t should always be square (why?) - debug_assert_eq!( t_is_nonzero_square | t.is_zero(), 1u8 ); + + r.conditional_assign(&r_rot, rotate); + X.conditional_assign(&iY, rotate); + Y.conditional_assign(&iX, rotate); + T.conditional_negate(rotate); // 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.0.Z; + let uZ = &u * Z; let m2uZ = -&(&uZ + &uZ); r.conditional_negate(m2uZ.is_negative_decaf()); // Step 4: Compute s = | u(r(aZX - dYT)+Y)/a| // = |u(r(-ZX - dYT)+Y)| since a = -1 - let minus_ZX = -&(&self.0.Z * &X); + let minus_ZX = -&(Z * &X); let dYT = &constants::d * &(&Y * &T); // Compute s = u(r(aZX - dYT)+Y) and cnegate for abs let mut s = &u * &(&(&r * &(&minus_ZX - &dYT)) + &Y); @@ -647,6 +728,7 @@ mod test { #[test] fn encodings_of_small_multiples_of_basepoint() { // Table of encodings of (1+i)*basepoint + // Generated using the previous naive implementation. let compressed = [ CompressedDecaf([141, 190, 226, 107, 177, 201, 35, 118, 14, 55, 160, 165, 242, 207, 121, 161, 177, 80, 8, 132, 205, 254, 101, 169, 233, 65, 124, 96, 255, 182, 249, 40]), CompressedDecaf([131, 57, 148, 16, 8, 196, 141, 82, 144, 220, 105, 112, 66, 33, 48, 16, 182, 198, 173, 35, 248, 181, 92, 231, 222, 35, 85, 56, 5, 252, 91, 40]), From 42bfa615ddf5ecb07a3a496cf52ed0986de8462e Mon Sep 17 00:00:00 2001 From: Henry de Valence <4300572+plotskogwq@users.noreply.github.com> Date: Sat, 20 May 2017 20:16:11 -0700 Subject: [PATCH 11/18] Lower the trial number in decaf_random The radix_51 implementation has many more debug checks, so this takes quite long to run, and it's rude to run a fuzzer on the CI server. --- src/decaf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decaf.rs b/src/decaf.rs index 3ef2d7d..4b8e688 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -789,7 +789,7 @@ mod test { #[test] fn decaf_random_is_valid() { let mut rng = OsRng::new().unwrap(); - for _ in 0..10_000 { + for _ in 0..100 { let P = DecafPoint::random(&mut rng); // Check that P is on the curve assert!(P.0.is_valid()); From fbf1874058aeed478b250a92feb2df4a01388d9c Mon Sep 17 00:00:00 2001 From: Isis Lovecruft <4300572+plotskogwq@users.noreply.github.com> Date: Fri, 26 May 2017 20:09:58 +0000 Subject: [PATCH 12/18] Bump curve25519-dalek version to 0.9.0. --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3c10672..500dffb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "curve25519-dalek" -version = "0.8.1" +version = "0.9.0" authors = ["Isis Lovecruft ", "Henry de Valence "] readme = "README.md" diff --git a/README.md b/README.md index 7258558..7954ab1 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Extensive documentation is available [here](https://docs.rs/curve25519-dalek). To install, add the following to the dependencies section of your project's `Cargo.toml`: - curve25519-dalek = "^0.8" + curve25519-dalek = "^0.9" Then, in your library or executable source, add: From 1a56b443ca387634c2804bffb703f4e5b6a8d75a Mon Sep 17 00:00:00 2001 From: Isis Lovecruft <4300572+plotskogwq@users.noreply.github.com> Date: Fri, 26 May 2017 20:14:55 +0000 Subject: [PATCH 13/18] Make badges in README be links. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7954ab1..c1b783c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# curve25519-dalek ![](https://img.shields.io/crates/v/curve25519-dalek.svg) ![](https://docs.rs/curve25519-dalek/badge.svg) ![](https://travis-ci.org/isislovecruft/curve25519-dalek.svg?branch=master) +# curve25519-dalek [![](https://img.shields.io/crates/v/curve25519-dalek.svg)](https://crates.io/curve25519-dalek) [![](https://docs.rs/curve25519-dalek/badge.svg)](https://docs.rs/curve25519-dalek) [![](https://travis-ci.org/isislovecruft/curve25519-dalek.svg?branch=master)](https://travis-ci.org/isislovecruft/curve25519-dalek) **A low-level cryptographic library for point, group, field, and scalar operations on a curve isomorphic to the twisted Edwards curve defined by -x²+y² From d7cf27c264c8d13966e1db654659ebc5c158199c Mon Sep 17 00:00:00 2001 From: Isis Lovecruft <4300572+plotskogwq@users.noreply.github.com> Date: Fri, 26 May 2017 20:16:30 +0000 Subject: [PATCH 14/18] Remove the TODO for serde. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index c1b783c..c66e519 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,6 @@ fast. ## TODO * Implement hashing to a point on the curve (Elligator). -* Maybe use serde for serialization. * Make a new `mask` type in `subtle.rs` and return that instead of `u8`s. * Implement all utilities in Golang's `crypto/subtle` package, and move the module to its own crate. From 2224e09dd04a7b4501ada41cd1616ff8f84e6c21 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft <4300572+plotskogwq@users.noreply.github.com> Date: Fri, 26 May 2017 20:53:18 +0000 Subject: [PATCH 15/18] Fix the doctest for byte_is_nonzero. --- src/subtle.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/subtle.rs b/src/subtle.rs index 098733f..d1f3241 100644 --- a/src/subtle.rs +++ b/src/subtle.rs @@ -69,12 +69,16 @@ pub fn bytes_equal_ct(a: u8, b: u8) -> u8 { /// Test if a byte is non-zero in constant time. /// -/// ```rust,ignore +/// ``` +/// # extern crate curve25519_dalek; +/// # use curve25519_dalek::subtle::byte_is_nonzero; +/// # fn main() { /// let mut x: u8; /// x = 0; -/// assert!(byte_is_nonzero(x)); +/// assert!(byte_is_nonzero(x) == 0); /// x = 3; /// assert!(byte_is_nonzero(x) == 1); +/// # } /// ``` /// /// # Return From 350b9c3952f392b456e55cf43e1ed5a0c207ccae Mon Sep 17 00:00:00 2001 From: Isis Lovecruft <4300572+plotskogwq@users.noreply.github.com> Date: Fri, 26 May 2017 21:22:31 +0000 Subject: [PATCH 16/18] Better documentation for arrays_equal_ct. --- src/subtle.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/subtle.rs b/src/subtle.rs index d1f3241..203c1c5 100644 --- a/src/subtle.rs +++ b/src/subtle.rs @@ -96,6 +96,37 @@ pub fn byte_is_nonzero(b: u8) -> u8 { /// Check equality of two 32-byte arrays in constant time. /// +/// If the contents of the arrays do *not* match, +/// `0u8` will be returned: +/// +/// ``` +/// # extern crate curve25519_dalek; +/// # use curve25519_dalek::subtle::arrays_equal_ct; +/// # fn main() { +/// let a: [u8; 3] = [0, 1, 2]; +/// let b: [u8; 3] = [1, 2, 3]; +/// +/// assert!(arrays_equal_ct(&a, &b) == 0); +/// # } +/// ``` +/// +/// If the contents *do* match, `1u8` is returned: +/// +/// ``` +/// # extern crate curve25519_dalek; +/// # use curve25519_dalek::subtle::arrays_equal_ct; +/// # fn main() { +/// let a: [u8; 3] = [0, 1, 2]; +/// let b: [u8; 3] = [0, 1, 2]; +/// +/// assert!(arrays_equal_ct(&a, &b) == 1); +/// # } +/// ``` +/// +/// This function is commonly used in various cryptographic applications, such +/// as [signature verification](https://github.com/isislovecruft/ed25519-dalek/blob/0.3.2/src/ed25519.rs#L280), +/// among many other applications. +/// /// # Return /// /// Returns `1u8` if `a == b` and `0u8` otherwise. From 55bad336d0765334da1f7fa4bb03b82fb891b6df Mon Sep 17 00:00:00 2001 From: Isis Lovecruft <4300572+plotskogwq@users.noreply.github.com> Date: Fri, 26 May 2017 21:25:31 +0000 Subject: [PATCH 17/18] Rename subtle::arrays_equal_ct() to subtle::arrays_equal(). It's already obvious that it's constant-time because it's in the subtle module. --- src/curve.rs | 6 +++--- src/field.rs | 4 ++-- src/scalar.rs | 6 +++--- src/subtle.rs | 10 +++++----- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/curve.rs b/src/curve.rs index 9987f73..cc1cfc8 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -90,7 +90,7 @@ use core::ops::Index; use constants; use field::FieldElement; use scalar::Scalar; -use subtle::arrays_equal_ct; +use subtle::arrays_equal; use subtle::bytes_equal_ct; use subtle::CTAssignable; use subtle::CTEq; @@ -518,8 +518,8 @@ impl CTAssignable for ExtendedPoint { impl CTEq for ExtendedPoint { fn ct_eq(&self, other: &ExtendedPoint) -> u8 { - arrays_equal_ct( self.compress_edwards().as_bytes(), - other.compress_edwards().as_bytes()) + arrays_equal( self.compress_edwards().as_bytes(), + other.compress_edwards().as_bytes()) } } diff --git a/src/field.rs b/src/field.rs index 2d43545..ef36f28 100644 --- a/src/field.rs +++ b/src/field.rs @@ -23,7 +23,7 @@ use core::ops::{Index, IndexMut}; use core::cmp::{Eq, PartialEq}; use core::ops::Neg; -use subtle::arrays_equal_ct; +use subtle::arrays_equal; use subtle::byte_is_nonzero; use subtle::CTAssignable; use subtle::CTEq; @@ -96,7 +96,7 @@ impl CTEq for FieldElement { /// /// `1u8` if the two `FieldElement`s are equal, and `0u8` otherwise. fn ct_eq(&self, other: &FieldElement) -> u8 { - arrays_equal_ct(&self.to_bytes(), &other.to_bytes()) + arrays_equal(&self.to_bytes(), &other.to_bytes()) } } diff --git a/src/scalar.rs b/src/scalar.rs index 7f90624..3f4a6cc 100644 --- a/src/scalar.rs +++ b/src/scalar.rs @@ -47,7 +47,7 @@ use constants; use utils::{load3, load4}; use subtle::CTAssignable; use subtle::CTEq; -use subtle::arrays_equal_ct; +use subtle::arrays_equal; /// The `Scalar` struct represents an element in ℤ/lℤ, where /// @@ -76,7 +76,7 @@ impl PartialEq for Scalar { /// /// True if they are equal, and false otherwise. fn eq(&self, other: &Self) -> bool { - arrays_equal_ct(&self.0, &other.0) == 1u8 + arrays_equal(&self.0, &other.0) == 1u8 } } @@ -87,7 +87,7 @@ impl CTEq for Scalar { /// /// `1u8` if they are equal, and `0u8` otherwise. fn ct_eq(&self, other: &Self) -> u8 { - arrays_equal_ct(&self.0, &other.0) + arrays_equal(&self.0, &other.0) } } diff --git a/src/subtle.rs b/src/subtle.rs index 203c1c5..19eebb3 100644 --- a/src/subtle.rs +++ b/src/subtle.rs @@ -101,12 +101,12 @@ pub fn byte_is_nonzero(b: u8) -> u8 { /// /// ``` /// # extern crate curve25519_dalek; -/// # use curve25519_dalek::subtle::arrays_equal_ct; +/// # use curve25519_dalek::subtle::arrays_equal; /// # fn main() { /// let a: [u8; 3] = [0, 1, 2]; /// let b: [u8; 3] = [1, 2, 3]; /// -/// assert!(arrays_equal_ct(&a, &b) == 0); +/// assert!(arrays_equal(&a, &b) == 0); /// # } /// ``` /// @@ -114,12 +114,12 @@ pub fn byte_is_nonzero(b: u8) -> u8 { /// /// ``` /// # extern crate curve25519_dalek; -/// # use curve25519_dalek::subtle::arrays_equal_ct; +/// # use curve25519_dalek::subtle::arrays_equal; /// # fn main() { /// let a: [u8; 3] = [0, 1, 2]; /// let b: [u8; 3] = [0, 1, 2]; /// -/// assert!(arrays_equal_ct(&a, &b) == 1); +/// assert!(arrays_equal(&a, &b) == 1); /// # } /// ``` /// @@ -131,7 +131,7 @@ pub fn byte_is_nonzero(b: u8) -> u8 { /// /// Returns `1u8` if `a == b` and `0u8` otherwise. #[inline(always)] -pub fn arrays_equal_ct(a: &[u8; 32], b: &[u8; 32]) -> u8 { +pub fn arrays_equal(a: &[u8; 32], b: &[u8; 32]) -> u8 { let mut x: u8 = 0; for i in 0..32 { From b5f87b5e0b03d31f18debfe03f1acfac4987d3ae Mon Sep 17 00:00:00 2001 From: Isis Lovecruft <4300572+plotskogwq@users.noreply.github.com> Date: Fri, 26 May 2017 21:28:53 +0000 Subject: [PATCH 18/18] Make arrays_equal() work for any size &[u8], as long as sizes are equal. --- src/subtle.rs | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/subtle.rs b/src/subtle.rs index 19eebb3..1dd6ba4 100644 --- a/src/subtle.rs +++ b/src/subtle.rs @@ -94,9 +94,19 @@ pub fn byte_is_nonzero(b: u8) -> u8 { (x & 1) } -/// Check equality of two 32-byte arrays in constant time. +/// Check equality of two arrays, `a` and `b`, in constant time. /// -/// If the contents of the arrays do *not* match, +/// There is a `debug_assert!` that the two arrays are of equal length. For +/// example, the following code will panic: +/// +/// ```rust,ignore +/// let a: [u8; 3] = [0, 0, 0]; +/// let b: [u8; 4] = [0, 0, 0, 0]; +/// +/// assert!(arrays_equal(&a, &b) == 1); +/// ``` +/// +/// However, if the arrays are equal length, but their contents do *not* match, /// `0u8` will be returned: /// /// ``` @@ -110,7 +120,7 @@ pub fn byte_is_nonzero(b: u8) -> u8 { /// # } /// ``` /// -/// If the contents *do* match, `1u8` is returned: +/// And finally, if the contents *do* match, `1u8` is returned: /// /// ``` /// # extern crate curve25519_dalek; @@ -131,11 +141,27 @@ pub fn byte_is_nonzero(b: u8) -> u8 { /// /// Returns `1u8` if `a == b` and `0u8` otherwise. #[inline(always)] -pub fn arrays_equal(a: &[u8; 32], b: &[u8; 32]) -> u8 { +pub fn arrays_equal(a: &[u8], b: &[u8]) -> u8 { + debug_assert!(a.len() == b.len()); + let mut x: u8 = 0; - for i in 0..32 { + for i in 0 .. a.len() { x |= a[i] ^ b[i]; } bytes_equal_ct(x, 0) } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[should_panic] + fn arrays_equal_different_lengths() { + let a: [u8; 3] = [0, 0, 0]; + let b: [u8; 4] = [0, 0, 0, 0]; + + assert!(arrays_equal(&a, &b) == 1); + } +}