From ae2d2b59b90e0bfd82ae0abe2433b82a884e36c8 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 21 May 2020 11:48:52 +1200 Subject: [PATCH] group: Move uncompressed encodings to an UncompressedEncoding trait Specifications of deployed elliptic curves fall into one of two categories: - They specify both compressed and uncompressed encodings, allowing implementations to use either depending on performance vs data size considerations. - They specify a single point encoding format using point compression. I am unaware of any elliptic curve specification that explicitly forbids compressed encodings. To support both categories of elliptic curves, we provide the CurveAffine::Compressed associated type which all curves must define, and then curves that additionally specify an uncompressed encoding may implement the UncompressedEncoding trait and its Uncompressed associated type. pairing::PairingCurveAffine continues to require that its groups provide uncompressed encodings, because this is relied upon by bellman::groth16. We can revisit this restriction when that module is refactored as a separate crate. --- bellman/src/groth16/mod.rs | 10 +++--- bellman/src/groth16/tests/dummy_engine.rs | 7 +++-- group/src/lib.rs | 9 ++++-- group/src/tests/mod.rs | 38 ++++++++++++++++------- pairing/src/bls12_381/ec.rs | 15 ++++++--- pairing/src/bls12_381/tests/mod.rs | 11 ++++--- pairing/src/lib.rs | 7 +++-- 7 files changed, 65 insertions(+), 32 deletions(-) diff --git a/bellman/src/groth16/mod.rs b/bellman/src/groth16/mod.rs index e86b4f597..e4285ce77 100644 --- a/bellman/src/groth16/mod.rs +++ b/bellman/src/groth16/mod.rs @@ -2,7 +2,7 @@ //! //! [Groth16]: https://eprint.iacr.org/2016/260 -use group::CurveAffine; +use group::{CurveAffine, UncompressedEncoding}; use pairing::{Engine, MultiMillerLoop}; use crate::SynthesisError; @@ -158,7 +158,7 @@ impl VerifyingKey { pub fn read(mut reader: R) -> io::Result { let read_g1 = |reader: &mut R| -> io::Result { - let mut g1_repr = ::Uncompressed::default(); + let mut g1_repr = ::Uncompressed::default(); reader.read_exact(g1_repr.as_mut())?; let affine = E::G1Affine::from_uncompressed(&g1_repr); @@ -170,7 +170,7 @@ impl VerifyingKey { }; let read_g2 = |reader: &mut R| -> io::Result { - let mut g2_repr = ::Uncompressed::default(); + let mut g2_repr = ::Uncompressed::default(); reader.read_exact(g2_repr.as_mut())?; let affine = E::G2Affine::from_uncompressed(&g2_repr); @@ -289,7 +289,7 @@ impl Parameters { pub fn read(mut reader: R, checked: bool) -> io::Result { let read_g1 = |reader: &mut R| -> io::Result { - let mut repr = ::Uncompressed::default(); + let mut repr = ::Uncompressed::default(); reader.read_exact(repr.as_mut())?; let affine = if checked { @@ -317,7 +317,7 @@ impl Parameters { }; let read_g2 = |reader: &mut R| -> io::Result { - let mut repr = ::Uncompressed::default(); + let mut repr = ::Uncompressed::default(); reader.read_exact(repr.as_mut())?; let affine = if checked { diff --git a/bellman/src/groth16/tests/dummy_engine.rs b/bellman/src/groth16/tests/dummy_engine.rs index 2b1c25857..e9dcb3a8f 100644 --- a/bellman/src/groth16/tests/dummy_engine.rs +++ b/bellman/src/groth16/tests/dummy_engine.rs @@ -1,5 +1,5 @@ use ff::{Field, PrimeField}; -use group::{CurveAffine, CurveProjective, Group, PrimeGroup}; +use group::{CurveAffine, CurveProjective, Group, PrimeGroup, UncompressedEncoding}; use pairing::{Engine, MillerLoopResult, MultiMillerLoop, PairingCurveAffine}; use rand_core::RngCore; @@ -434,7 +434,6 @@ impl AsRef<[u8]> for FakePoint { impl CurveAffine for Fr { type Compressed = FakePoint; - type Uncompressed = FakePoint; type Projective = Fr; type Scalar = Fr; @@ -465,6 +464,10 @@ impl CurveAffine for Fr { fn to_compressed(&self) -> Self::Compressed { unimplemented!() } +} + +impl UncompressedEncoding for Fr { + type Uncompressed = FakePoint; fn from_uncompressed(_bytes: &Self::Uncompressed) -> CtOption { unimplemented!() diff --git a/group/src/lib.rs b/group/src/lib.rs index d88e320c6..e9b396bab 100644 --- a/group/src/lib.rs +++ b/group/src/lib.rs @@ -132,7 +132,6 @@ pub trait CurveAffine: { type Scalar: PrimeField; type Projective: CurveProjective; - type Uncompressed: Default + AsRef<[u8]> + AsMut<[u8]>; type Compressed: Default + AsRef<[u8]> + AsMut<[u8]>; /// Returns the additive identity. @@ -162,6 +161,12 @@ pub trait CurveAffine: /// Converts this element into its compressed encoding, so long as it's not /// the point at infinity. fn to_compressed(&self) -> Self::Compressed; +} + +/// Affine representation of a point on an elliptic curve that has a defined uncompressed +/// encoding. +pub trait UncompressedEncoding: CurveAffine { + type Uncompressed: Default + AsRef<[u8]> + AsMut<[u8]>; /// Attempts to deserialize an element from its uncompressed encoding. fn from_uncompressed(bytes: &Self::Uncompressed) -> CtOption; @@ -171,7 +176,7 @@ pub trait CurveAffine: /// /// **This is dangerous to call unless you trust the bytes you are reading; otherwise, /// API invariants may be broken.** Please consider using - /// [`CurveAffine::from_uncompressed`] instead. + /// [`UncompressedEncoding::from_uncompressed`] instead. fn from_uncompressed_unchecked(bytes: &Self::Uncompressed) -> CtOption; /// Converts this element into its uncompressed encoding, so long as it's not diff --git a/group/src/tests/mod.rs b/group/src/tests/mod.rs index 406ae6e3b..1862df8fa 100644 --- a/group/src/tests/mod.rs +++ b/group/src/tests/mod.rs @@ -3,7 +3,7 @@ use rand::SeedableRng; use rand_xorshift::XorShiftRng; use std::ops::{Mul, Neg}; -use crate::{CurveAffine, CurveProjective}; +use crate::{CurveAffine, CurveProjective, UncompressedEncoding}; pub fn curve_tests() { let mut rng = XorShiftRng::from_seed([ @@ -62,7 +62,7 @@ pub fn curve_tests() { random_negation_tests::(); random_transformation_tests::(); random_wnaf_tests::(); - random_encoding_tests::(); + random_compressed_encoding_tests::(); } fn random_wnaf_tests() { @@ -394,17 +394,12 @@ fn random_transformation_tests() { } } -fn random_encoding_tests() { +fn random_compressed_encoding_tests() { let mut rng = XorShiftRng::from_seed([ 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, 0xe5, ]); - assert_eq!( - G::Affine::from_uncompressed(&G::Affine::identity().to_uncompressed()).unwrap(), - G::Affine::identity() - ); - assert_eq!( G::Affine::from_compressed(&G::Affine::identity().to_compressed()).unwrap(), G::Affine::identity() @@ -413,10 +408,6 @@ fn random_encoding_tests() { for _ in 0..1000 { let mut r = G::random(&mut rng).to_affine(); - let uncompressed = r.to_uncompressed(); - let de_uncompressed = G::Affine::from_uncompressed(&uncompressed).unwrap(); - assert_eq!(de_uncompressed, r); - let compressed = r.to_compressed(); let de_compressed = G::Affine::from_compressed(&compressed).unwrap(); assert_eq!(de_compressed, r); @@ -428,3 +419,26 @@ fn random_encoding_tests() { assert_eq!(de_compressed, r); } } + +pub fn random_uncompressed_encoding_tests() +where + G::Affine: UncompressedEncoding, +{ + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, + 0xe5, + ]); + + assert_eq!( + G::Affine::from_uncompressed(&G::Affine::identity().to_uncompressed()).unwrap(), + G::Affine::identity() + ); + + for _ in 0..1000 { + let r = G::random(&mut rng).to_affine(); + + let uncompressed = r.to_uncompressed(); + let de_uncompressed = G::Affine::from_uncompressed(&uncompressed).unwrap(); + assert_eq!(de_uncompressed, r); + } +} diff --git a/pairing/src/bls12_381/ec.rs b/pairing/src/bls12_381/ec.rs index 2654afcf6..e0d59d183 100644 --- a/pairing/src/bls12_381/ec.rs +++ b/pairing/src/bls12_381/ec.rs @@ -200,7 +200,6 @@ macro_rules! curve_impl { impl CurveAffine for $affine { type Scalar = $scalarfield; type Projective = $projective; - type Uncompressed = $uncompressed; type Compressed = $compressed; fn identity() -> Self { @@ -248,6 +247,10 @@ macro_rules! curve_impl { fn to_compressed(&self) -> Self::Compressed { $compressed::from_affine(*self) } + } + + impl UncompressedEncoding for $affine { + type Uncompressed = $uncompressed; fn from_uncompressed(bytes: &Self::Uncompressed) -> CtOption { Self::from_uncompressed_unchecked(bytes).and_then(|affine| { @@ -901,7 +904,7 @@ pub mod g1 { use super::{g2::G2Affine, GroupDecodingError}; use crate::{Engine, PairingCurveAffine}; use ff::{BitIterator, Field, PrimeField}; - use group::{CurveAffine, CurveProjective, Group, PrimeGroup}; + use group::{CurveAffine, CurveProjective, Group, PrimeGroup, UncompressedEncoding}; use rand_core::RngCore; use std::fmt; use std::ops::{AddAssign, MulAssign, Neg, SubAssign}; @@ -1467,8 +1470,9 @@ pub mod g1 { #[test] fn g1_curve_tests() { - use group::tests::curve_tests; + use group::tests::{curve_tests, random_uncompressed_encoding_tests}; curve_tests::(); + random_uncompressed_encoding_tests::(); } } @@ -1477,7 +1481,7 @@ pub mod g2 { use super::{g1::G1Affine, GroupDecodingError}; use crate::{Engine, PairingCurveAffine}; use ff::{BitIterator, Field, PrimeField}; - use group::{CurveAffine, CurveProjective, Group, PrimeGroup}; + use group::{CurveAffine, CurveProjective, Group, PrimeGroup, UncompressedEncoding}; use rand_core::RngCore; use std::fmt; use std::ops::{AddAssign, MulAssign, Neg, SubAssign}; @@ -2167,8 +2171,9 @@ pub mod g2 { #[test] fn g2_curve_tests() { - use group::tests::curve_tests; + use group::tests::{curve_tests, random_uncompressed_encoding_tests}; curve_tests::(); + random_uncompressed_encoding_tests::(); } } diff --git a/pairing/src/bls12_381/tests/mod.rs b/pairing/src/bls12_381/tests/mod.rs index dd7a21bb6..30be7edbb 100644 --- a/pairing/src/bls12_381/tests/mod.rs +++ b/pairing/src/bls12_381/tests/mod.rs @@ -1,5 +1,5 @@ use ff::PrimeField; -use group::{CurveAffine, CurveProjective}; +use group::{CurveAffine, CurveProjective, UncompressedEncoding}; use super::*; use crate::*; @@ -55,9 +55,12 @@ fn test_pairing_result_against_relic() { }); } -fn uncompressed_test_vectors(expected: &[u8]) { +fn uncompressed_test_vectors(expected: &[u8]) +where + G::Affine: UncompressedEncoding, +{ let mut e = G::identity(); - let encoded_len = ::Uncompressed::default() + let encoded_len = ::Uncompressed::default() .as_ref() .len(); @@ -69,7 +72,7 @@ fn uncompressed_test_vectors(expected: &[u8]) { let encoded = e_affine.to_uncompressed(); v.extend_from_slice(encoded.as_ref()); - let mut decoded = ::Uncompressed::default(); + let mut decoded = ::Uncompressed::default(); decoded.as_mut().copy_from_slice(&expected[0..encoded_len]); expected = &expected[encoded_len..]; let decoded = G::Affine::from_uncompressed(&decoded).unwrap(); diff --git a/pairing/src/lib.rs b/pairing/src/lib.rs index 62af20dc1..6c3e2b47f 100644 --- a/pairing/src/lib.rs +++ b/pairing/src/lib.rs @@ -22,7 +22,10 @@ pub mod bls12_381; use core::ops::Mul; use ff::{Field, PrimeField}; -use group::{CurveAffine, CurveProjective, GroupOps, GroupOpsOwned, ScalarMul, ScalarMulOwned}; +use group::{ + CurveAffine, CurveProjective, GroupOps, GroupOpsOwned, ScalarMul, ScalarMulOwned, + UncompressedEncoding, +}; /// An "engine" is a collection of types (fields, elliptic curve groups, etc.) /// with well-defined relationships. In particular, the G1/G2 curve groups are @@ -77,7 +80,7 @@ pub trait Engine: Sized + 'static + Clone { /// Affine representation of an elliptic curve point that can be used /// to perform pairings. -pub trait PairingCurveAffine: CurveAffine { +pub trait PairingCurveAffine: CurveAffine + UncompressedEncoding { type Pair: PairingCurveAffine; type PairingResult: Field;