From b6a2827fe90fb7c6636a090c0845c350dcefdcbb Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Wed, 24 Jan 2024 14:17:58 -0800 Subject: [PATCH 1/5] Add basic representations of curve elements --- src/lib.rs | 1 + src/smol_curve/element.rs | 32 ++++++++++++++++++++++++++++++++ src/smol_curve/mod.rs | 1 + 3 files changed, 34 insertions(+) create mode 100644 src/smol_curve/element.rs create mode 100644 src/smol_curve/mod.rs diff --git a/src/lib.rs b/src/lib.rs index cf9c6e2..18000e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ //! use cfg_if::cfg_if; +pub mod smol_curve; pub mod fields; pub use fields::{fp::Fp, fq::Fq, fr::Fr}; diff --git a/src/smol_curve/element.rs b/src/smol_curve/element.rs new file mode 100644 index 0000000..a08cf6d --- /dev/null +++ b/src/smol_curve/element.rs @@ -0,0 +1,32 @@ +use crate::Fq; + +/// A point on an Edwards curve. +/// +/// There exist points which do not correspond to elements of the Decaf377 group. +/// +/// This curve can be equipped with an additive group structure. +/// +/// This is an internal implementation detail of how we've constructed this group, and should +/// only be used by consumers who are cursed with the knowledge of what an Elliptic Curve is. +#[derive(Debug, Clone, Copy)] +pub struct AffinePoint { + x: Fq, + y: Fq, +} + +impl AffinePoint { + /// The identity element for the group structure. + const IDENTITY: Self = Self { + x: Fq::zero(), + y: Fq::one(), + }; +} + +/// An element of the Decaf377 group. +#[derive(Debug, Clone, Copy)] +pub struct Element { + x: Fq, + y: Fq, + t: Fq, + z: Fq, +} diff --git a/src/smol_curve/mod.rs b/src/smol_curve/mod.rs new file mode 100644 index 0000000..a1b2323 --- /dev/null +++ b/src/smol_curve/mod.rs @@ -0,0 +1 @@ +pub mod element; From d3bd72f7d8365aebc8c1006d7333bd97182de16f Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Wed, 24 Jan 2024 14:36:39 -0800 Subject: [PATCH 2/5] Implement identity, generator, partial eq --- src/lib.rs | 2 +- src/smol_curve/element.rs | 57 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 18000e1..5c03b3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,8 +4,8 @@ //! use cfg_if::cfg_if; -pub mod smol_curve; pub mod fields; +pub mod smol_curve; pub use fields::{fp::Fp, fq::Fq, fr::Fr}; cfg_if! { diff --git a/src/smol_curve/element.rs b/src/smol_curve/element.rs index a08cf6d..b8b1551 100644 --- a/src/smol_curve/element.rs +++ b/src/smol_curve/element.rs @@ -25,8 +25,63 @@ impl AffinePoint { /// An element of the Decaf377 group. #[derive(Debug, Clone, Copy)] pub struct Element { + // These elements always satisfy the invariant that (x/z) * (y/z) = t. + // Furthermore, ((x/z), (y/z)) returns the affine point associated with this element. x: Fq, y: Fq, - t: Fq, z: Fq, + t: Fq, +} + +impl Element { + /// The identity element for the group structure. + pub const IDENTITY: Self = Self { + x: Fq::zero(), + y: Fq::one(), + z: Fq::one(), + t: Fq::zero(), + }; + + /// The generator element for the group structure. + pub const GENERATOR: Self = Self { + x: Fq::from_montgomery_limbs_64([ + 5825153684096051627, + 16988948339439369204, + 186539475124256708, + 1230075515893193738, + ]), + y: Fq::from_montgomery_limbs_64([ + 9786171649960077610, + 13527783345193426398, + 10983305067350511165, + 1251302644532346138, + ]), + z: Fq::one(), + t: Fq::from_montgomery_limbs_64([ + 7466800842436274004, + 14314110021432015475, + 14108125795146788134, + 1305086759679105397, + ]), + }; +} + +impl PartialEq for Element { + fn eq(&self, other: &Self) -> bool { + // This check is equivalent to checking that the ratio of each affine point matches. + // ((x1 / z1) / (y1 / z1)) == ((x2 / z2) / (y2 / z2)) <=> x1 * y2 == x2 * y1 + self.x * other.y == other.x * self.y + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_basic_equalities() { + assert_eq!(Element::GENERATOR, Element::GENERATOR); + assert_eq!(Element::IDENTITY, Element::IDENTITY); + assert_ne!(Element::IDENTITY, Element::GENERATOR); + } } From b6e328f766d173156a01bb15b82e68cd0f5134a8 Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Wed, 24 Jan 2024 15:11:29 -0800 Subject: [PATCH 3/5] Implement addition and doubling formulas --- src/smol_curve/element.rs | 100 +++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/src/smol_curve/element.rs b/src/smol_curve/element.rs index b8b1551..c2d6beb 100644 --- a/src/smol_curve/element.rs +++ b/src/smol_curve/element.rs @@ -1,5 +1,31 @@ +use core::ops::Add; + use crate::Fq; +/// COEFF_A = -1 +const COEFF_A: Fq = Fq::from_montgomery_limbs_64([ + 10157024534604021774, + 16668528035959406606, + 5322190058819395602, + 387181115924875961, +]); + +/// COEFF_D = 3021 +const COEFF_D: Fq = Fq::from_montgomery_limbs_64([ + 15008245758212136496, + 17341409599856531410, + 648869460136961410, + 719771289660577536, +]); + +/// -2 COEFF_D / COEFF_A = 6042 +const COEFF_K: Fq = Fq::from_montgomery_limbs_64([ + 10844245690243005535, + 9774967673803681700, + 12776203677742963460, + 94262208632981673, +]); + /// A point on an Edwards curve. /// /// There exist points which do not correspond to elements of the Decaf377 group. @@ -16,7 +42,7 @@ pub struct AffinePoint { impl AffinePoint { /// The identity element for the group structure. - const IDENTITY: Self = Self { + pub const IDENTITY: Self = Self { x: Fq::zero(), y: Fq::one(), }; @@ -25,7 +51,7 @@ impl AffinePoint { /// An element of the Decaf377 group. #[derive(Debug, Clone, Copy)] pub struct Element { - // These elements always satisfy the invariant that (x/z) * (y/z) = t. + // These elements always satisfy the invariant that x * y = t * z. // Furthermore, ((x/z), (y/z)) returns the affine point associated with this element. x: Fq, y: Fq, @@ -64,6 +90,68 @@ impl Element { 1305086759679105397, ]), }; + + pub fn double(self) -> Self { + // https://eprint.iacr.org/2008/522 Section 3.3 + let a = self.x.square(); + let b = self.y.square(); + let mut c = self.z.square(); + c += c; + // Since COEFF_A is -1 + let d = -a; + let e = (self.x + self.y).square() - a - b; + let g = d + b; + let f = g - c; + let h = d - b; + let x3 = e * f; + let y3 = g * h; + let t3 = e * h; + let z3 = f * g; + Self { + x: x3, + y: y3, + z: z3, + t: t3, + } + } +} + +impl Add for Element { + type Output = Self; + + fn add(self, other: Self) -> Self::Output { + // https://eprint.iacr.org/2008/522 Section 3.1, 8M + 1D algorithm + let Element { + x: x1, + y: y1, + z: z1, + t: t1, + } = self; + let Element { + x: x2, + y: y2, + z: z2, + t: t2, + } = other; + let a = (y1 - x1) * (y2 - x2); + let b = (y1 + x1) * (y2 + x2); + let c = COEFF_K * t1 * t2; + let d = (z1 + z1) * z2; + let e = b - a; + let f = d - c; + let g = d + c; + let h = b + a; + let x3 = e * f; + let y3 = g * h; + let t3 = e * h; + let z3 = f * g; + Self { + x: x3, + y: y3, + z: z3, + t: t3, + } + } } impl PartialEq for Element { @@ -84,4 +172,12 @@ mod test { assert_eq!(Element::IDENTITY, Element::IDENTITY); assert_ne!(Element::IDENTITY, Element::GENERATOR); } + + #[test] + fn test_adding_is_doubling_on_generator() { + assert_eq!( + Element::GENERATOR + Element::GENERATOR, + Element::GENERATOR.double() + ); + } } From 2522013cd0240e75a560e72ae83d1f133040f5b2 Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Wed, 24 Jan 2024 15:39:32 -0800 Subject: [PATCH 4/5] Implement scalar multiplication --- Cargo.toml | 1 + src/fields/fq/u32/wrapper.rs | 12 +++++ src/fields/fq/u64/wrapper.rs | 12 +++++ src/smol_curve/element.rs | 95 +++++++++++++++++++++++++++++++++++- 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 28cf3b0..d340f9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ license = "MIT OR Apache-2.0" cfg-if = "1.0" hex = { version ="=0.4.2", default-features = false } once_cell = { version= "1.8", default-features = false } +subtle = "2.5" tracing-subscriber = { version = "0.3", default-features = false } zeroize = { version = "1.7", default-features = false } # no-std diff --git a/src/fields/fq/u32/wrapper.rs b/src/fields/fq/u32/wrapper.rs index 51cd8da..9534264 100644 --- a/src/fields/fq/u32/wrapper.rs +++ b/src/fields/fq/u32/wrapper.rs @@ -1,3 +1,5 @@ +use subtle::ConditionallySelectable; + use super::fiat; const B: usize = 253; @@ -206,3 +208,13 @@ impl Fq { Fq(result) } } + +impl ConditionallySelectable for Fq { + fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self { + let mut out = [0u32; 8]; + for i in 0..8 { + out[i] = u32::conditional_select(&a.0 .0[i], &b.0 .0[i], choice); + } + Self(fiat::FqMontgomeryDomainFieldElement(out)) + } +} diff --git a/src/fields/fq/u64/wrapper.rs b/src/fields/fq/u64/wrapper.rs index 368a678..c46cf68 100644 --- a/src/fields/fq/u64/wrapper.rs +++ b/src/fields/fq/u64/wrapper.rs @@ -1,3 +1,5 @@ +use subtle::ConditionallySelectable; + use super::fiat; const B: usize = 253; @@ -186,3 +188,13 @@ impl Fq { Fq(result) } } + +impl ConditionallySelectable for Fq { + fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self { + let mut out = [0u64; 4]; + for i in 0..4 { + out[i] = u64::conditional_select(&a.0 .0[i], &b.0 .0[i], choice); + } + Self(fiat::FqMontgomeryDomainFieldElement(out)) + } +} diff --git a/src/smol_curve/element.rs b/src/smol_curve/element.rs index c2d6beb..1fc51d5 100644 --- a/src/smol_curve/element.rs +++ b/src/smol_curve/element.rs @@ -1,6 +1,7 @@ -use core::ops::Add; +use core::ops::{Add, Mul}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; -use crate::Fq; +use crate::{Fq, Fr}; /// COEFF_A = -1 const COEFF_A: Fq = Fq::from_montgomery_limbs_64([ @@ -59,6 +60,17 @@ pub struct Element { t: Fq, } +impl ConditionallySelectable for Element { + fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self { + Self { + x: Fq::conditional_select(&a.x, &b.x, choice), + y: Fq::conditional_select(&a.y, &b.y, choice), + z: Fq::conditional_select(&a.z, &b.z, choice), + t: Fq::conditional_select(&a.t, &b.t, choice), + } + } +} + impl Element { /// The identity element for the group structure. pub const IDENTITY: Self = Self { @@ -114,6 +126,31 @@ impl Element { t: t3, } } + + fn scalar_mul_both(self, le_bits: &[u64]) -> Self { + let mut acc = Self::IDENTITY; + let mut insert = self; + for limb in le_bits { + for i in 0..64 { + let flag = ((limb >> i) & 1) as u8; + if CT { + acc = Self::conditional_select(&acc, &(acc + insert), Choice::from(flag)) + } else if flag == 1 { + acc = acc + insert; + } + insert = insert.double(); + } + } + acc + } + + pub fn scalar_mul_vartime(self, le_bits: &[u64]) -> Self { + Self::scalar_mul_both::(self, le_bits) + } + + pub fn scalar_mul(self, le_bits: &[u64]) -> Self { + Self::scalar_mul_both::(self, le_bits) + } } impl Add for Element { @@ -154,6 +191,14 @@ impl Add for Element { } } +impl Mul for Element { + type Output = Self; + + fn mul(self, rhs: Fr) -> Self::Output { + Self::scalar_mul_vartime(self, &rhs.to_le_limbs()) + } +} + impl PartialEq for Element { fn eq(&self, other: &Self) -> bool { // This check is equivalent to checking that the ratio of each affine point matches. @@ -180,4 +225,50 @@ mod test { Element::GENERATOR.double() ); } + + #[test] + fn test_g_times_one() { + assert_eq!(Element::GENERATOR * Fr::one(), Element::GENERATOR); + } + + #[test] + fn test_g_times_zero() { + assert_eq!(Element::GENERATOR * Fr::zero(), Element::IDENTITY); + } +} + +#[cfg(all(test, feature = "arkworks"))] +mod proptests { + use super::*; + use ark_ff::{BigInt, PrimeField}; + use proptest::prelude::*; + + prop_compose! { + // Technically this might overflow, but we won't miss any values, + // just return 0 if you overflow when consuming. + fn arb_fr_limbs()( + z0 in 0..u64::MAX, + z1 in 0..u64::MAX, + z2 in 0..u64::MAX, + z3 in 0..336320092672043349u64 + ) -> [u64; 4] { + [z0, z1, z2, z3] + } + } + + prop_compose! { + fn arb_fr()(a in arb_fr_limbs()) -> Fr { + // Will be fine because of the bounds in the arb_fr_limbs + Fr::from_bigint(BigInt(a)).unwrap_or(Fr::zero()) + } + } + + proptest! { + fn test_is_fq_module(a in arb_fr(), b in arb_fr()) { + const G: Element = Element::GENERATOR; + + assert_eq!(G * (a + b), G * a + G * b); + assert_eq!(G * (a * b), (G * a) * b); + } + } } From c7c83db8e2c01f40f6f550b56afb2195cc5e572f Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Wed, 24 Jan 2024 15:41:32 -0800 Subject: [PATCH 5/5] Implement negation --- src/smol_curve/element.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/smol_curve/element.rs b/src/smol_curve/element.rs index 1fc51d5..ec20e8c 100644 --- a/src/smol_curve/element.rs +++ b/src/smol_curve/element.rs @@ -1,4 +1,4 @@ -use core::ops::{Add, Mul}; +use core::ops::{Add, Mul, Neg}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use crate::{Fq, Fr}; @@ -199,6 +199,16 @@ impl Mul for Element { } } +impl Neg for Element { + type Output = Self; + + fn neg(mut self) -> Self::Output { + self.x = -self.x; + self.t = -self.t; + self + } +} + impl PartialEq for Element { fn eq(&self, other: &Self) -> bool { // This check is equivalent to checking that the ratio of each affine point matches. @@ -235,6 +245,19 @@ mod test { fn test_g_times_zero() { assert_eq!(Element::GENERATOR * Fr::zero(), Element::IDENTITY); } + + #[test] + fn test_g_times_minus_one() { + assert_eq!(Element::GENERATOR * (-Fr::one()), -Element::GENERATOR); + } + + #[test] + fn test_g_plus_minus_g() { + assert_eq!( + Element::GENERATOR + (-Element::GENERATOR), + Element::IDENTITY + ); + } } #[cfg(all(test, feature = "arkworks"))]