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.