From 1662f881cb1a19719f3a25edd373c15e0aff3274 Mon Sep 17 00:00:00 2001 From: David Nevado Date: Thu, 14 Mar 2024 18:36:53 +0100 Subject: [PATCH] EC point serialization (#141) * refactor!: Compressed format for EC points - The bit flags for sign and identity have been fliped for curves with 2 spare bits: Bn256, Pluto and Eris. * fix: bn test vectors * fix: update new_impl_curve calls * refactor! Uncompressed format for EC points * add: unchecked sered test * fix: review comments add: uncompressed identity test fix: uncompressed serialization fix: cleanup fix: review comments add: compute spare bits from NUM_BITS fix: strict flag decoding fix: imports add: check for the bits in 0 spare bits case * fix: fip FLAG_BITS * fix: make to_bytes constant time * add: Econding format doc * fix: typos * chore: update encoding spec with explicit byte/bit position and endianess * chore: Remove flags from uncompressed format Modified the docs accordingly and rearranged x,y order. --------- Co-authored-by: Eduard S --- src/bn256/curve.rs | 4 +- src/bn256/mod.rs | 10 +- src/derive/curve.rs | 334 +++++++++++++++++++++++++++++++--------- src/grumpkin/curve.rs | 3 +- src/pluto_eris/curve.rs | 5 +- src/secp256k1/curve.rs | 4 +- src/secp256r1/curve.rs | 3 +- src/secq256k1/curve.rs | 3 +- src/tests/curve.rs | 23 +++ 9 files changed, 303 insertions(+), 86 deletions(-) diff --git a/src/bn256/curve.rs b/src/bn256/curve.rs index 4549183b..5989c9f6 100644 --- a/src/bn256/curve.rs +++ b/src/bn256/curve.rs @@ -5,6 +5,7 @@ use crate::arithmetic::EndoParameters; use crate::bn256::Fq; use crate::bn256::Fq2; use crate::bn256::Fr; +use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT}; use crate::endo; use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; @@ -32,7 +33,6 @@ new_curve_impl!( (pub), G1, G1Affine, - false, Fq, Fr, (G1_GENERATOR_X,G1_GENERATOR_Y), @@ -46,7 +46,6 @@ new_curve_impl!( (pub), G2, G2Affine, - false, Fq2, Fr, (G2_GENERATOR_X, G2_GENERATOR_Y), @@ -201,6 +200,7 @@ impl G1 { #[cfg(test)] mod test { use super::*; + use group::UncompressedEncoding; crate::curve_testing_suite!(G1, G2); crate::curve_testing_suite!(G1, "hash_to_curve"); crate::curve_testing_suite!(G1, "endo_consistency"); diff --git a/src/bn256/mod.rs b/src/bn256/mod.rs index 53c78273..58186c15 100644 --- a/src/bn256/mod.rs +++ b/src/bn256/mod.rs @@ -34,13 +34,13 @@ mod test { let expected_results = [ "e0c5a6834e0329b4f8bdc91144b3e687ac9d810a8e899415267db9cfbf61e91e", "7052a20bee99cbe054fdd8b2e336db3ed3e9a265229e44ab8197c5eabdef2b0b", - "2f058acc133957074ac79e9b9b1867a0cf3d13df7aa7de7f48e9a6be7d96aa6d", + "2f058acc133957074ac79e9b9b1867a0cf3d13df7aa7de7f48e9a6be7d96aaad", "b2ff44a25693b811f35e33feb3e99ad9ba0d06425a3ffd5e79cef63d20143314", - "ab2f6d71d2fde51546d8a5782aa9f707e585b84644470f0c876784dbebd30c55", - "6a4e0e30f37a8d1b92b8cf08df3735a36b4937ee455a9dc5f9283a13530db144", - "f1c69be8c5f5f9e28b0e9f76ab77651a7dcaaae371fbba66450cbcee0ed5b16b", + "ab2f6d71d2fde51546d8a5782aa9f707e585b84644470f0c876784dbebd30c95", + "6a4e0e30f37a8d1b92b8cf08df3735a36b4937ee455a9dc5f9283a13530db184", + "f1c69be8c5f5f9e28b0e9f76ab77651a7dcaaae371fbba66450cbcee0ed5b1ab", "e86267c2e3355d7a6f664a0ea71374406337d452a3f9a294a0594df53c08df21", - "03cf55ca983ecd8a2e2baae18d979d97d688a978d829701c66a14d7c4da58e62", + "03cf55ca983ecd8a2e2baae18d979d97d688a978d829701c66a14d7c4da58ea2", "5302c2cfe3c909e9378d08c951bb33d0813818a1baf734379aac8aaa47f38f0d", ]; diff --git a/src/derive/curve.rs b/src/derive/curve.rs index 6aa3960c..2449752a 100644 --- a/src/derive/curve.rs +++ b/src/derive/curve.rs @@ -49,12 +49,18 @@ macro_rules! endo { }; } +// Sign mask for 0, 1 and 2 spare bits. +pub(crate) const SIGN_MASK: u8 = 0b1000_0000; +pub(crate) const SIGN_SHIFT: u8 = 7; +// Identity mask for 0 and 2 spare bits (1 spare bit does not use it). +pub(crate) const IDENTITY_MASK: u8 = 0b0100_0000; +pub(crate) const IDENTITY_SHIFT: u8 = 6; + #[macro_export] macro_rules! new_curve_impl { (($($privacy:tt)*), $name:ident, $name_affine:ident, - $flags_extra_byte:expr, $base:ident, $scalar:ident, $generator:expr, @@ -64,17 +70,70 @@ macro_rules! new_curve_impl { $hash_to_curve:expr, ) => { + + // **Compressed formats**: + // The encoding of the x-coordinate can be Little Endian or Big Endian (inherited from the + // field encoding). + // The bit flags appear in the MSB of the encoded x-coordinate in the 1 and 2 Spare bits + // case, and in an extra byte after the encoded x-coordinate in the 0 Spare bits case. + // `BS` is the base size: the number of bytes required to encode a coordinate. + // + // According to the number of spare bits. + // 1 Spare bit: + // + // | | sign | x-coordinate | + // | Byte pos. (LE) | BS-1 .. 0 | + // | Byte pos. (BE) | 0 .. BS-1 | + // | Bit pos. | 7 | | + // | ---------------- | -------- | ------------ | + // | Identity | 0 | 0 | + // | Non-identity $P$ | $sgn0(P)$ | $P.x$ | + // + // --- + // 2 Spare bits: + // | | sign | ident | x-coordinate | + // | Byte pos. (LE) | BS-1 .. 0 | + // | Byte pos. (BE) | 0 .. BS-1 | + // | Bit pos. | 7 | 6 | | + // | ---------------- | -------- | -------- | -------- | + // | Identity | 0 | 1 | 0 | + // | Non-identity $P$ | $sgn0(P)$ | 0 | $P.x$ | + // + // --- + // 0 Spare bits: + // Add an extra byte after the compressed x-coordinate to hold the flags. Then follow + // the 2 spare bit flag format. + // + // | | sign | ident | 000000 | x-coordinate | + // | Byte pos. (LE) | BS | BS-1 .. 0 | + // | Byte pos. (BE) | BS | 0 .. BS-1 | + // | Bit pos. | 7 | 6 | 5..0 | | + // | ---------------- | --------- | -------- | ------ | ------------ | + // | Identity | 0 | 1 | 000000 | 0 | + // | Non-identity $P$ | $sgn0(P)$ | 0 | 000000 | $P.x$ | + // macro_rules! impl_compressed { - () => { + ($spare_bits: expr) => { paste::paste! { + // The compressed size is the size of the x-coordinate (one base field element) + // when there is at least 1 spare bit. When there is no spare bits (secp256k1) + // the size is increased by 1 byte. #[allow(non_upper_case_globals)] - const [< $name _COMPRESSED_SIZE >]: usize = if $flags_extra_byte {$base::size() + 1} else {$base::size()}; + const [< $name _COMPRESSED_SIZE >]: usize = + if $spare_bits == 0 { + $base::size() + 1 + } else { + $base::size() + }; + #[derive(Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "derive_serde", derive(Serialize, Deserialize))] - pub struct [<$name Compressed >](#[cfg_attr(feature = "derive_serde", serde(with = "serde_arrays"))] [u8; [< $name _COMPRESSED_SIZE >]]); + pub struct [<$name Compressed >]( + #[cfg_attr(feature = "derive_serde", serde(with = "serde_arrays"))] + [u8; [< $name _COMPRESSED_SIZE >]] + ); - // Compressed impl std::fmt::Debug for [< $name Compressed >] { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0[..].fmt(f) @@ -99,32 +158,114 @@ macro_rules! new_curve_impl { } } + impl GroupEncoding for $name { + type Repr = [< $name Compressed >]; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + $name_affine::from_bytes(bytes).map(Self::from) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + $name_affine::from_bytes_unchecked(bytes).map(Self::from) + } + + fn to_bytes(&self) -> Self::Repr { + $name_affine::from(self).to_bytes() + } + } + + + // The flags are placed in the last byte (the most significant byte). + #[allow(non_upper_case_globals)] + const [< $name _FLAG_BYTE_INDEX>]: usize= [< $name _COMPRESSED_SIZE >]-1 ; + + #[allow(non_upper_case_globals)] + const [< $name _FLAG_BITS >]: u8 = + if $spare_bits == 1 { + 0b1000_0000 + } else if $spare_bits == 2 { + 0b1100_0000 + } else { + //$spare_bits == 0 + 0b1111_1111 + }; + impl group::GroupEncoding for $name_affine { type Repr = [< $name Compressed >]; fn from_bytes(bytes: &Self::Repr) -> CtOption { - let bytes = &bytes.0; - let mut tmp = *bytes; - let is_inf = Choice::from(tmp[[< $name _COMPRESSED_SIZE >] - 1] >> 7); - let ysign = Choice::from((tmp[[< $name _COMPRESSED_SIZE >] - 1] >> 6) & 1); - tmp[[< $name _COMPRESSED_SIZE >] - 1] &= 0b0011_1111; + let mut tmp = bytes.0; + + let flag_byte = tmp[[< $name _FLAG_BYTE_INDEX>]]; + // Get identity and sign flags. + let identity_flag = if $spare_bits == 0 || $spare_bits == 2 { + Choice::from((flag_byte & IDENTITY_MASK) >> IDENTITY_SHIFT ) + } else { + Choice::from(0u8) + }; + + let sign_flag = Choice::from( (flag_byte & SIGN_MASK) >> SIGN_SHIFT ); + + let extra_bits = if $spare_bits == 0 { + // In the case of 0 spare bits, an extra byte is added to hold the flags. + // In this byte, the 6 least significant bits must be set to 0. + Choice::from(u8::from((flag_byte & 0b0011_1111) !=0) ) + } else { + // There are no extra bits in the rest of cases. + Choice::from(0u8) + }; + + // Clear flag bits + tmp[[< $name _FLAG_BYTE_INDEX>]] &= ![< $name _FLAG_BITS >]; + + // Get x-coordinate let mut xbytes = [0u8; $base::size()]; - xbytes.copy_from_slice(&tmp[ ..$base::size()]); + xbytes.copy_from_slice(&tmp[..$base::size()]); + + $base::from_bytes(&xbytes).and_then(|x| { - CtOption::new(Self::identity(), x.is_zero() & (is_inf)).or_else(|| { - $name_affine::y2(x).sqrt().and_then(|y| { - let sign = Choice::from(y.to_bytes()[0] & 1); - let y = $base::conditional_select(&y, &-y, ysign ^ sign); + // Decide if the point is the identity and the validity of the encoding. + let (is_valid, is_identity) = + if $spare_bits == 0 || $spare_bits == 2 { + // Correct encoding follows one of the following: + // 1. Identity: + // identity_flag = 1, sign = 0, x = 0, extra_bits = 0 + // 2. Non-identity: + // identity_flag = 0, , extra_bits = 0 + // + // is_valid = !identity_flag \/ (!sign /\ x.is_zero()) /\ !extra_bits + ( (!identity_flag | (!sign_flag & x.is_zero())) & !extra_bits, identity_flag) + } else { + // Correct encoding follows one of the following: + // 1. Identity: + // sign = 0, x = 0 + // 2. Non-identity: + // x!=0 + // + // is_valid = !(x.is_zero() /\ sign_flag) + ( !(x.is_zero() & sign_flag), x.is_zero()) + }; + + CtOption::new( + Self::identity(), + is_identity) + .or_else(|| { + // Computes corresponding y coordinate. + $name_affine::y2(x).sqrt().and_then(|y| { + // Get sign of obtained solution. sign = y % 2. + let sign = Choice::from(y.to_bytes()[0] & 1); + // Adjust sign if necessary. + let y = $base::conditional_select(&y, &-y, sign_flag ^ sign); CtOption::new( $name_affine { x, y, }, - Choice::from(1u8), + is_valid, ) }) }) @@ -132,58 +273,77 @@ macro_rules! new_curve_impl { } fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + // In compressed form we cannot skip the curve check. Self::from_bytes(bytes) } fn to_bytes(&self) -> Self::Repr { - if bool::from(self.is_identity()) { - let mut bytes = [0; [< $name _COMPRESSED_SIZE >]]; - bytes[[< $name _COMPRESSED_SIZE >] - 1] |= 0b1000_0000; - [< $name Compressed >](bytes) - } else { - let (x, y) = (self.x, self.y); - let sign = (y.to_bytes()[0] & 1) << 6; - let mut xbytes = [0u8; [< $name _COMPRESSED_SIZE >]]; - xbytes[..$base::size()].copy_from_slice(&x.to_bytes()); - xbytes[[< $name _COMPRESSED_SIZE >] - 1] |= sign; - [< $name Compressed >](xbytes) - } - } - } - - impl GroupEncoding for $name { - type Repr = [< $name Compressed >]; + let mut res = [0; [< $name _COMPRESSED_SIZE >]]; - fn from_bytes(bytes: &Self::Repr) -> CtOption { - $name_affine::from_bytes(bytes).map(Self::from) - } + let x_bytes = $base::conditional_select(&self.x, &$base::zero(), self.is_identity()).to_bytes(); + res[..$base::size()].copy_from_slice(&x_bytes); - fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { - $name_affine::from_bytes(bytes).map(Self::from) - } + // Set identity flag if necessary. + res[ [< $name _FLAG_BYTE_INDEX>]] |= u8::conditional_select(&0u8, &IDENTITY_MASK, self.is_identity()); - fn to_bytes(&self) -> Self::Repr { - $name_affine::from(self).to_bytes() + // Set sign flag if point is not identity, and has negative sign. + res[ [< $name _FLAG_BYTE_INDEX>]] |= u8::conditional_select(&0u8, &SIGN_MASK, !self.is_identity() & Choice::from(self.y.to_bytes()[0] & 1)); + [< $name Compressed >](res) } } + } }; } - macro_rules! impl_uncompressed { - () => { + // **Uncompressed format** + // The encoding of the x-coordinate and y-coordinate can be Little Endian or Big Endian + // (inherited from the field encoding). + // + // There are no flag bits, the spare bits must be 0. + // `BS` is the base size: the number of bytes required to encode a coordinate. + // + // According to the number of spare bits: + // 1 Spare bit: + // + // | | 0 | x-coordinate | 0 | y-coordinate | + // | Byte pos. (LE) | BS-1 .. 0 | 2*BS-1 .. BS | + // | Byte pos. (BE) | 0 .. BS-1 | BS .. 2*BS-1 | + // | Bit pos. | 7 | | 7 | | + // | ---------------- | - | ------------ | - | ------------ | + // | Identity | 0 | 0 | 0 | 0 | + // | Non-identity $P$ | 0 | $P.x$ | 0 | $P.y$ | + // + // ---- + // 2 Spare bits: + // + // | | 0 | 0 | x-coordinate | 0 | 0 | y-coordinate | + // | Byte pos. (LE) | BS-1 .. 0 | 2*BS-1 .. BS | + // | Byte pos. (BE) | 0 .. BS-1 | BS .. 2*BS-1 | + // | Bit pos. | 7 | 6 | | 7 | 6 | | + // | ---------------- | - | - | ------------ | - | - | ------------ | + // | Identity | 0 | 0 | 0 | 0 | 0 | 0 | + // | Non-identity $P$ | 0 | 0 | $P.x$ | 0 | 0 | $P.y$ | + // + // ---- + // 0 Spare bits: + // + // | | x-coordinate | y-coordinate | + // | Byte pos. (LE) | BS-1 .. 0 | 2*BS-1 .. BS | + // | Byte pos. (BE) | 0 .. BS-1 | BS .. 2*BS-1 | + // | ---------------- | ------------ | ------------ | + // | Identity | 0 | 0 | + // | Non-identity $P$ | $P.x$ | $P.y$ | + // + + macro_rules! impl_uncompressed { + ($spare_bits: expr) => { paste::paste! { - #[allow(non_upper_case_globals)] - const [< $name _UNCOMPRESSED_SIZE >]: usize = if $flags_extra_byte { - 2 * $base::size() + 1 - } else{ - 2 *$base::size() - }; #[derive(Copy, Clone)] - pub struct [< $name Uncompressed >]([u8; [< $name _UNCOMPRESSED_SIZE >]]); + pub struct [< $name Uncompressed >]([u8; 2*$base::size()]); impl std::fmt::Debug for [< $name Uncompressed >] { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0[..].fmt(f) @@ -192,7 +352,7 @@ macro_rules! new_curve_impl { impl Default for [< $name Uncompressed >] { fn default() -> Self { - [< $name Uncompressed >]([0; [< $name _UNCOMPRESSED_SIZE >] ]) + [< $name Uncompressed >]([0; 2*$base::size() ]) } } @@ -231,45 +391,78 @@ macro_rules! new_curve_impl { } fn from_uncompressed_unchecked(bytes: &Self::Uncompressed) -> CtOption { - let bytes = &bytes.0; - let infinity_flag_set = Choice::from((bytes[[< $name _UNCOMPRESSED_SIZE >] - 1] >> 6) & 1); - // Attempt to obtain the x-coordinate + let mut bytes = bytes.0; + + let flag_idx_x = $base::size() -1; + let flag_idx_y = 2* $base::size() -1; + + // In the uncompressed format, the spare bits in both coordinates must be 0. + let mut any_flag_set = Choice::from(0u8); + + // Get sign flag to check they are set to 0. + if $spare_bits == 2 || $spare_bits == 1 { + any_flag_set |= Choice::from( (bytes[ flag_idx_x ] & SIGN_MASK) >> SIGN_SHIFT | + (bytes[ flag_idx_y ] & SIGN_MASK) >> SIGN_SHIFT ) + } + + // Get identity flag to check they are set to 0. + if $spare_bits == 2 { + any_flag_set |= Choice::from( (( bytes[ flag_idx_x ] & IDENTITY_MASK) >> IDENTITY_SHIFT) | (( bytes[ flag_idx_y ] & IDENTITY_MASK) >> IDENTITY_SHIFT) ); + } + + // Clear spare bits. + if $spare_bits == 2 || $spare_bits == 1 { + bytes[flag_idx_x] &= ![< $name _FLAG_BITS >]; + bytes[flag_idx_y] &= ![< $name _FLAG_BITS >]; + } + + + // Get x, y coordinates. + let mut repr = [0u8; $base::size()]; let x = { - let mut tmp = [0; $base::size()]; - tmp.copy_from_slice(&bytes[0..$base::size()]); - $base::from_bytes(&tmp) + repr.copy_from_slice(&bytes[0..$base::size()]); + $base::from_bytes(&repr) }; - // Attempt to obtain the y-coordinate let y = { - let mut tmp = [0; $base::size()]; - tmp.copy_from_slice(&bytes[$base::size()..2*$base::size()]); - $base::from_bytes(&tmp) + repr.copy_from_slice(&bytes[$base::size()..2*$base::size()]); + $base::from_bytes(&repr) }; + x.and_then(|x| { y.and_then(|y| { - // Create a point representing this value + let zero_coords = x.is_zero() & y.is_zero(); + + // Check identity condition and encoding validity: + // The point is the identity if both coordinates are zero. + // The encoding is valid if both coordinates represent valid field elements and + // the spare bits are all zero. + let (is_valid, is_identity) = + ( !any_flag_set, zero_coords); + + let p = $name_affine::conditional_select( &$name_affine{ x, y, }, &$name_affine::identity(), - infinity_flag_set, + is_identity, ); + eprintln!("Is the point valid? {:?}", is_valid); + CtOption::new( p, - // If the infinity flag is set, the x and y coordinates should have been zero. - ((!infinity_flag_set) | (x.is_zero() & y.is_zero())) + is_valid ) }) }) } fn to_uncompressed(&self) -> Self::Uncompressed { - let mut res = [0; [< $name _UNCOMPRESSED_SIZE >]]; + let mut res = [0; 2*$base::size()]; res[0..$base::size()].copy_from_slice( &$base::conditional_select(&self.x, &$base::zero(), self.is_identity()).to_bytes()[..], @@ -278,14 +471,11 @@ macro_rules! new_curve_impl { &$base::conditional_select(&self.y, &$base::zero(), self.is_identity()).to_bytes()[..], ); - res[[< $name _UNCOMPRESSED_SIZE >] - 1] |= u8::conditional_select(&0u8, &(1u8 << 6), self.is_identity()); - [< $name Uncompressed >](res) } } } }; - } /// A macro to help define point serialization using the [`group::GroupEncoding`] trait @@ -369,8 +559,10 @@ macro_rules! new_curve_impl { #[cfg(feature = "derive_serde")] serialize_deserialize_to_from_bytes!(); - impl_compressed!(); - impl_uncompressed!(); + // Base's num_bits is the number of bits for the base prime field, + // so the computation of spare bits is correct for extensions as well. + impl_compressed!((($base::NUM_BITS-1) / 8 +1) * 8 - $base::NUM_BITS); + impl_uncompressed!((($base::NUM_BITS-1) / 8 +1) * 8 - $base::NUM_BITS); diff --git a/src/grumpkin/curve.rs b/src/grumpkin/curve.rs index f0ddf86a..dad570da 100644 --- a/src/grumpkin/curve.rs +++ b/src/grumpkin/curve.rs @@ -2,6 +2,7 @@ use crate::arithmetic::mul_512; use crate::arithmetic::sbb; use crate::arithmetic::CurveEndo; use crate::arithmetic::EndoParameters; +use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT}; use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::Curve; @@ -29,7 +30,6 @@ new_curve_impl!( (pub), G1, G1Affine, - false, Fq, Fr, (G1_GENERATOR_X, G1_GENERATOR_Y), @@ -92,6 +92,7 @@ impl G1 { #[cfg(test)] mod test { use super::*; + use group::UncompressedEncoding; crate::curve_testing_suite!(G1); crate::curve_testing_suite!(G1, "endo_consistency"); crate::curve_testing_suite!(G1, "endo"); diff --git a/src/pluto_eris/curve.rs b/src/pluto_eris/curve.rs index 9b86c69c..7cd68205 100644 --- a/src/pluto_eris/curve.rs +++ b/src/pluto_eris/curve.rs @@ -1,4 +1,5 @@ use super::fields::{fp::Fp, fp2::Fp2, fq::Fq}; +use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT}; use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::{prime::PrimeCurveAffine, Curve, Group as _, GroupEncoding}; @@ -124,7 +125,6 @@ new_curve_impl!( (pub), G1, G1Affine, - false, Fp, Fq, (G1_GENERATOR_X,G1_GENERATOR_Y), @@ -160,7 +160,6 @@ new_curve_impl!( (pub), Eris, ErisAffine, - false, Fq, Fp, (ERIS_GENERATOR_X,ERIS_GENERATOR_Y), @@ -232,7 +231,6 @@ new_curve_impl!( (pub), G2, G2Affine, - false, Fp2, Fq, (G2_GENERATOR_X,G2_GENERATOR_Y), @@ -245,6 +243,7 @@ new_curve_impl!( #[cfg(test)] mod test { use super::*; + use group::UncompressedEncoding; crate::curve_testing_suite!(G1, Eris, G2); crate::curve_testing_suite!(G1, Eris, "hash_to_curve"); crate::curve_testing_suite!(G1, Eris, "endo_consistency"); diff --git a/src/secp256k1/curve.rs b/src/secp256k1/curve.rs index a8bf5bca..d4cdd659 100644 --- a/src/secp256k1/curve.rs +++ b/src/secp256k1/curve.rs @@ -1,3 +1,4 @@ +use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT}; use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::{prime::PrimeCurveAffine, Curve, Group as _, GroupEncoding}; @@ -58,7 +59,6 @@ new_curve_impl!( (pub), Secp256k1, Secp256k1Affine, - true, Fp, Fq, (SECP_GENERATOR_X,SECP_GENERATOR_Y), @@ -127,7 +127,6 @@ new_curve_impl!( (pub(crate)), IsoSecp256k1, IsoSecp256k1Affine, - true, Fp, Fq, (ISO_SECP_GENERATOR_X, ISO_SECP_GENERATOR_Y), @@ -271,6 +270,7 @@ pub(crate) fn iso_map_secp256k1(rp: IsoSecp256k1) -> Secp256k1 { #[cfg(test)] mod test { use super::*; + use group::UncompressedEncoding; crate::curve_testing_suite!(Secp256k1); crate::curve_testing_suite!(Secp256k1, "endo_consistency"); crate::curve_testing_suite!(Secp256k1, "ecdsa_example"); diff --git a/src/secp256r1/curve.rs b/src/secp256r1/curve.rs index fbd1c538..c1e2fb0f 100644 --- a/src/secp256r1/curve.rs +++ b/src/secp256r1/curve.rs @@ -1,3 +1,4 @@ +use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT}; use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::{prime::PrimeCurveAffine, Curve, Group as _, GroupEncoding}; @@ -69,7 +70,6 @@ new_curve_impl!( (pub), Secp256r1, Secp256r1Affine, - true, Fp, Fq, (SECP_GENERATOR_X,SECP_GENERATOR_Y), @@ -94,6 +94,7 @@ impl Secp256r1 { #[cfg(test)] mod test { use super::*; + use group::UncompressedEncoding; crate::curve_testing_suite!(Secp256r1); crate::curve_testing_suite!(Secp256r1, "ecdsa_example"); crate::curve_testing_suite!( diff --git a/src/secq256k1/curve.rs b/src/secq256k1/curve.rs index 726eae8a..6a7b0f33 100644 --- a/src/secq256k1/curve.rs +++ b/src/secq256k1/curve.rs @@ -1,3 +1,4 @@ +use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT}; use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::Curve; @@ -41,7 +42,6 @@ new_curve_impl!( (pub), Secq256k1, Secq256k1Affine, - true, Fq, Fp, (SECQ_GENERATOR_X, SECQ_GENERATOR_Y), @@ -74,6 +74,7 @@ impl Secq256k1 { #[cfg(test)] mod test { use super::*; + use group::UncompressedEncoding; crate::curve_testing_suite!(Secq256k1); crate::curve_testing_suite!(Secq256k1, "endo_consistency"); crate::curve_testing_suite!( diff --git a/src/tests/curve.rs b/src/tests/curve.rs index 390ea5de..b89863cf 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -238,18 +238,30 @@ macro_rules! curve_testing_suite { .unwrap() .is_identity() )); + + assert!(bool::from( + <$c as CurveExt>::AffineExt::from_uncompressed(&<$c as CurveExt>::AffineExt::identity().to_uncompressed()) + .unwrap() + .is_identity() + )); + assert!(bool::from( <$c as CurveExt>::AffineExt::from_bytes(&<$c as CurveExt>::AffineExt::identity().to_bytes()) .unwrap() .is_identity() )); + for _ in 0..100 { let projective_point = $c::random(OsRng); let affine_point: <$c as CurveExt>::AffineExt = projective_point.into(); + + // Compressed format tests let projective_repr = projective_point.to_bytes(); let affine_repr = affine_point.to_bytes(); + let projective_point_rec = $c::from_bytes(&projective_repr).unwrap(); let projective_point_rec_unchecked = $c::from_bytes(&projective_repr).unwrap(); + let affine_point_rec = <$c as CurveExt>::AffineExt::from_bytes(&affine_repr).unwrap(); let affine_point_rec_unchecked = <$c as CurveExt>::AffineExt::from_bytes(&affine_repr).unwrap(); @@ -257,10 +269,20 @@ macro_rules! curve_testing_suite { assert_eq!(projective_point, projective_point_rec_unchecked); assert_eq!(affine_point, affine_point_rec); assert_eq!(affine_point, affine_point_rec_unchecked); + + // Uncompressed format + let affine_repr = affine_point.to_uncompressed(); + + let affine_point_rec = <$c as CurveExt>::AffineExt::from_uncompressed_unchecked(&affine_repr).unwrap(); + let affine_point_rec_unchecked = <$c as CurveExt>::AffineExt::from_uncompressed_unchecked(&affine_repr).unwrap(); + + assert_eq!(affine_point, affine_point_rec); + assert_eq!(affine_point, affine_point_rec_unchecked); } } } + // TODO Change name macro_rules! random_serialization_test { ($c: ident) => { for _ in 0..100 { @@ -327,6 +349,7 @@ macro_rules! curve_testing_suite { use crate::{CurveAffine, CurveExt}; use rand_core::OsRng; + #[test] fn test_curve() { $(