From 957ee492d2e306d25c934c861b785c3fdc004bd1 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Mon, 25 Mar 2024 11:52:46 +0100 Subject: [PATCH 1/2] Add `PreferCompact` --- generic-ec/src/serde.rs | 216 ++++++++++++++++++++++++---- tests/tests/serde.rs | 308 +++++++++++++++++++++++++++++++++++----- 2 files changed, 459 insertions(+), 65 deletions(-) diff --git a/generic-ec/src/serde.rs b/generic-ec/src/serde.rs index cce2700..ca39648 100644 --- a/generic-ec/src/serde.rs +++ b/generic-ec/src/serde.rs @@ -166,11 +166,11 @@ impl<'de, E: Curve> serde::Deserialize<'de> for CurveName { pub use optional::*; #[cfg(feature = "serde")] mod optional { - use crate::core::Curve; + use crate::{core::Curve, Point, Scalar, SecretScalar}; use super::CurveName; - impl serde::Serialize for crate::Point { + impl serde::Serialize for Point { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -179,7 +179,7 @@ mod optional { } } - impl<'de, E: Curve> serde::Deserialize<'de> for crate::Point { + impl<'de, E: Curve> serde::Deserialize<'de> for Point { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -190,7 +190,7 @@ mod optional { } } - impl serde::Serialize for crate::Scalar { + impl serde::Serialize for Scalar { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -199,7 +199,7 @@ mod optional { } } - impl<'de, E: Curve> serde::Deserialize<'de> for crate::Scalar { + impl<'de, E: Curve> serde::Deserialize<'de> for Scalar { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -210,7 +210,7 @@ mod optional { } } - impl serde::Serialize for crate::SecretScalar { + impl serde::Serialize for SecretScalar { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -219,22 +219,20 @@ mod optional { } } - impl<'de, E: Curve> serde::Deserialize<'de> for crate::SecretScalar { + impl<'de, E: Curve> serde::Deserialize<'de> for SecretScalar { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - Ok(crate::SecretScalar::new(&mut crate::Scalar::deserialize( - deserializer, - )?)) + Ok(SecretScalar::new(&mut Scalar::deserialize(deserializer)?)) } } /// Compact serialization format pub struct Compact; - impl serde_with::SerializeAs> for Compact { - fn serialize_as(source: &crate::Point, serializer: S) -> Result + impl serde_with::SerializeAs> for Compact { + fn serialize_as(source: &Point, serializer: S) -> Result where S: serde::Serializer, { @@ -243,8 +241,8 @@ mod optional { } } - impl<'de, E: Curve> serde_with::DeserializeAs<'de, crate::Point> for Compact { - fn deserialize_as(deserializer: D) -> Result, D::Error> + impl<'de, E: Curve> serde_with::DeserializeAs<'de, Point> for Compact { + fn deserialize_as(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { @@ -255,8 +253,8 @@ mod optional { } } - impl serde_with::SerializeAs> for Compact { - fn serialize_as(source: &crate::Scalar, serializer: S) -> Result + impl serde_with::SerializeAs> for Compact { + fn serialize_as(source: &Scalar, serializer: S) -> Result where S: serde::Serializer, { @@ -265,8 +263,8 @@ mod optional { } } - impl<'de, E: Curve> serde_with::DeserializeAs<'de, crate::Scalar> for Compact { - fn deserialize_as(deserializer: D) -> Result, D::Error> + impl<'de, E: Curve> serde_with::DeserializeAs<'de, Scalar> for Compact { + fn deserialize_as(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { @@ -277,11 +275,8 @@ mod optional { } } - impl serde_with::SerializeAs> for Compact { - fn serialize_as( - source: &crate::SecretScalar, - serializer: S, - ) -> Result + impl serde_with::SerializeAs> for Compact { + fn serialize_as(source: &SecretScalar, serializer: S) -> Result where S: serde::Serializer, { @@ -290,16 +285,16 @@ mod optional { } } - impl<'de, E: Curve> serde_with::DeserializeAs<'de, crate::SecretScalar> for Compact { - fn deserialize_as(deserializer: D) -> Result, D::Error> + impl<'de, E: Curve> serde_with::DeserializeAs<'de, SecretScalar> for Compact { + fn deserialize_as(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { let mut scalar = - >>::deserialize_as( + >>::deserialize_as( deserializer, )?; - Ok(crate::SecretScalar::new(&mut scalar)) + Ok(SecretScalar::new(&mut scalar)) } } @@ -342,6 +337,173 @@ mod optional { } } + /// Serializes point/scalar compactly. Deserializes both compact + /// and non-compact points/scalars. + /// + /// It can be used when some data used to be serialized in default serialization + /// format, and at some point you decided to use a compact serialization format. + /// `PreferCompact` serializes points/scalar in compact format, but at deserialization + /// it accepts both compact and non-compact forms. + /// + /// `PreferCompact` does not work on `serde` backends which serialize structs as + /// lists, such as `bincode`. Notably, (de)serialization of points/scalars in compact + /// format will still work, but deserialization from non-compact form will produce + /// an error. + pub struct PreferCompact; + + impl serde_with::SerializeAs for PreferCompact + where + Compact: serde_with::SerializeAs, + { + fn serialize_as(source: &T, serializer: S) -> Result + where + S: serde::Serializer, + { + >::serialize_as(source, serializer) + } + } + + impl<'de, T> serde_with::DeserializeAs<'de, T> for PreferCompact + where + T: serde::Deserialize<'de>, + Compact: serde_with::DeserializeAs<'de, T>, + { + fn deserialize_as(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde_with::DeserializeAs; + + struct Visitor { + is_human_readable: bool, + _out: core::marker::PhantomData, + } + impl<'de, T> serde::de::Visitor<'de> for Visitor + where + T: serde::Deserialize<'de>, + Compact: serde_with::DeserializeAs<'de, T>, + { + type Value = T; + fn expecting(&self, f: &mut alloc::fmt::Formatter) -> alloc::fmt::Result { + f.write_str("preferably compact point/scalar") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + Err: serde::de::Error, + { + Compact::deserialize_as(NewTypeDeserializer::new(OverrideHumanReadable { + deserializer: serde::de::value::BytesDeserializer::::new(v), + is_human_readable: self.is_human_readable, + })) + } + fn visit_str(self, v: &str) -> Result + where + Err: serde::de::Error, + { + Compact::deserialize_as(NewTypeDeserializer::new(OverrideHumanReadable { + deserializer: serde::de::value::StrDeserializer::::new(v), + is_human_readable: self.is_human_readable, + })) + } + + fn visit_seq(self, _seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + Err(::custom( + "cannot deserialize in `PreferCompact` mode \ + from sequence: it's ambiguous", + )) + } + fn visit_map(self, map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + T::deserialize(OverrideHumanReadable { + deserializer: serde::de::value::MapAccessDeserializer::new(map), + is_human_readable: self.is_human_readable, + }) + } + + fn visit_newtype_struct(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Compact::deserialize_as(NewTypeDeserializer::new(OverrideHumanReadable { + deserializer, + is_human_readable: self.is_human_readable, + })) + } + } + + let is_human_readable = deserializer.is_human_readable(); + deserializer.deserialize_any(Visitor { + is_human_readable, + _out: core::marker::PhantomData::, + }) + } + } + + /// Wraps a [`serde::Deserializer`] and overrides `fn is_human_readable()` + struct OverrideHumanReadable { + is_human_readable: bool, + deserializer: D, + } + impl<'de, D> serde::Deserializer<'de> for OverrideHumanReadable + where + D: serde::Deserializer<'de>, + { + type Error = >::Error; + + fn is_human_readable(&self) -> bool { + self.is_human_readable + } + + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserializer.deserialize_any(visitor) + } + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string + bytes byte_buf option unit unit_struct newtype_struct seq tuple + tuple_struct map struct enum identifier ignored_any + } + } + + /// See [`serde::de::value`]. New type deserializer is missing in the `serde` crate. + struct NewTypeDeserializer { + deserializer: D, + } + impl NewTypeDeserializer { + pub fn new(deserializer: D) -> Self { + Self { deserializer } + } + } + impl<'de, D> serde::Deserializer<'de> for NewTypeDeserializer + where + D: serde::Deserializer<'de>, + { + type Error = D::Error; + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + visitor.visit_newtype_struct(self.deserializer) + } + fn is_human_readable(&self) -> bool { + self.deserializer.is_human_readable() + } + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string + bytes byte_buf option unit unit_struct newtype_struct seq tuple + tuple_struct map struct enum identifier ignored_any + } + } + mod models { use core::convert::TryFrom; diff --git a/tests/tests/serde.rs b/tests/tests/serde.rs index 3455f32..e5b9070 100644 --- a/tests/tests/serde.rs +++ b/tests/tests/serde.rs @@ -14,7 +14,7 @@ mod tests { let point_compressed = point.to_bytes(true).to_vec().leak(); let point_compressed_hex = hex::encode(&point_compressed).leak(); - // Human-readable, uncompressed + // Human-readable serde_test::assert_ser_tokens( &point.readable(), &[ @@ -30,18 +30,19 @@ mod tests { ], ); - // Human-readable, compressed - serde_test::assert_ser_tokens( - &Compressed(point).readable(), - &[ + // Human-readable, compact + { + let tokens = &[ Token::NewtypeStruct { name: "PointCompact", }, Token::Str(point_compressed_hex), - ], - ); + ]; + serde_test::assert_ser_tokens(&Compact(point).readable(), tokens); + serde_test::assert_ser_tokens(&PreferCompact(point).readable(), tokens); + } - // Binary, uncompressed + // Binary serde_test::assert_ser_tokens( &point.compact(), &[ @@ -57,16 +58,17 @@ mod tests { ], ); - // Binary, compressed - serde_test::assert_ser_tokens( - &Compressed(point).compact(), - &[ + // Binary, compact + { + let tokens = &[ Token::NewtypeStruct { name: "PointCompact", }, Token::Bytes(point_compressed), - ], - ); + ]; + serde_test::assert_ser_tokens(&Compact(point).compact(), tokens); + serde_test::assert_ser_tokens(&PreferCompact(point).compact(), tokens); + } } } @@ -81,10 +83,9 @@ mod tests { let point_compressed = point.to_bytes(true).to_vec().leak(); let point_compressed_hex = hex::encode(&point_compressed).leak(); - // Uncompressed, hex-encoding - serde_test::assert_de_tokens( - &point.readable(), - &[ + // Uncompressed, human-readable (hex-encoding) + { + let tokens = &[ Token::Struct { name: "PointUncompressed", len: 2, @@ -94,9 +95,12 @@ mod tests { Token::Str("point"), Token::Str(point_uncompressed_hex), Token::StructEnd, - ], - ); - // Uncompressed, seq-encoded + ]; + serde_test::assert_de_tokens(&point.readable(), tokens); + serde_test::assert_de_tokens(&PreferCompact(point).readable(), tokens); + } + + // Uncompressed, binary (seq-encoded) { let mut tokens = vec![ Token::Struct { @@ -111,11 +115,12 @@ mod tests { tokens.extend(point_uncompressed.iter().copied().map(Token::U8)); tokens.extend([Token::SeqEnd, Token::StructEnd]); serde_test::assert_de_tokens(&point.readable(), &tokens); + serde_test::assert_de_tokens(&PreferCompact(point).readable(), &tokens); } - // Uncompressed, bytes-encoded - serde_test::assert_de_tokens( - &point.readable(), - &[ + + // Uncompressed, binary (bytes-encoded) + { + let tokens = &[ Token::Struct { name: "PointUncompressed", len: 2, @@ -125,20 +130,24 @@ mod tests { Token::Str("point"), Token::Bytes(point_uncompressed), Token::StructEnd, - ], - ); + ]; + serde_test::assert_de_tokens(&point.readable(), tokens); + serde_test::assert_de_tokens(&PreferCompact(point).readable(), tokens); + } - // Compressed, hex-encoding - serde_test::assert_de_tokens( - &Compressed(point).readable(), - &[ + // Compact, human-readable (hex-encoding) + { + let tokens = &[ Token::NewtypeStruct { name: "PointCompact", }, Token::Str(point_compressed_hex), - ], - ); - // Compressed, seq-encoded + ]; + serde_test::assert_de_tokens(&Compact(point).readable(), tokens); + serde_test::assert_de_tokens(&PreferCompact(point).readable(), tokens); + } + + // Compact, binary (seq-encoded) { let mut tokens = vec![ Token::NewtypeStruct { @@ -148,14 +157,210 @@ mod tests { ]; tokens.extend(point_compressed.iter().copied().map(Token::U8)); tokens.push(Token::SeqEnd); - serde_test::assert_de_tokens(&Compressed(point).readable(), &tokens); + serde_test::assert_de_tokens(&Compact(point).readable(), &tokens); + serde_test::assert_de_tokens(&PreferCompact(point).readable(), &tokens); + } + + // Seq-encoded struct, human-readable (hex-encoded) + serde_test::assert_de_tokens( + &point.readable(), + &[ + Token::Seq { len: Some(2) }, + Token::Str(E::CURVE_NAME), + Token::Str(point_uncompressed_hex), + Token::SeqEnd, + ], + ); + // Seq-encoded struct, binary (bytes-encoded) + serde_test::assert_de_tokens( + &point.readable(), + &[ + Token::Seq { len: Some(2) }, + Token::Str(E::CURVE_NAME), + Token::Bytes(point_uncompressed), + Token::SeqEnd, + ], + ); + // Seq-encoded struct, binary (seq-encoded) + { + let mut tokens = vec![ + Token::Seq { len: Some(2) }, + Token::Str(E::CURVE_NAME), + Token::Seq { len: None }, + ]; + tokens.extend(point_uncompressed.iter().copied().map(Token::U8)); + tokens.extend([Token::SeqEnd, Token::SeqEnd]); + + serde_test::assert_de_tokens(&point.readable(), &tokens); + } + + // PreferCompact doesn't support seq-encoded structs + serde_test::assert_de_tokens_error::>>>( + &[Token::Seq { len: Some(2) }], + "cannot deserialize in `PreferCompact` mode from sequence: it's ambiguous", + ); + } + } + + #[test] + fn serialize_scalar() { + let mut rng = rand_dev::DevRng::new(); + + let random_scalar = Scalar::::random(&mut rng); + for scalar in [Scalar::zero(), Scalar::one(), -Scalar::one(), random_scalar] { + let scalar_bytes = scalar.to_be_bytes().to_vec().leak(); + let scalar_hex = hex::encode(&scalar_bytes).leak(); + + // Human-readable + serde_test::assert_ser_tokens( + &scalar.readable(), + &[ + Token::Struct { + name: "ScalarUncompressed", + len: 2, + }, + Token::Str("curve"), + Token::Str(E::CURVE_NAME), + Token::Str("scalar"), + Token::Str(scalar_hex), + Token::StructEnd, + ], + ); + + // Human-readable, compact + { + let tokens = &[ + Token::NewtypeStruct { + name: "ScalarCompact", + }, + Token::Str(scalar_hex), + ]; + serde_test::assert_ser_tokens(&Compact(scalar).readable(), tokens); + serde_test::assert_ser_tokens(&PreferCompact(scalar).readable(), tokens); + } + + // Binary + serde_test::assert_ser_tokens( + &scalar.compact(), + &[ + Token::Struct { + name: "ScalarUncompressed", + len: 2, + }, + Token::Str("curve"), + Token::Str(E::CURVE_NAME), + Token::Str("scalar"), + Token::Bytes(scalar_bytes), + Token::StructEnd, + ], + ); + + // Binary, compact + { + let tokens = &[ + Token::NewtypeStruct { + name: "ScalarCompact", + }, + Token::Bytes(scalar_bytes), + ]; + serde_test::assert_ser_tokens(&Compact(scalar).compact(), tokens); + serde_test::assert_ser_tokens(&PreferCompact(scalar).compact(), tokens); + } + } + } + + #[test] + fn deserialize_scalar() { + let mut rng = rand_dev::DevRng::new(); + + let random_scalar = Scalar::::random(&mut rng); + for scalar in [Scalar::zero(), Scalar::one(), -Scalar::one(), random_scalar] { + let scalar_bytes = scalar.to_be_bytes().to_vec().leak(); + let scalar_hex = hex::encode(&scalar_bytes).leak(); + + // Uncompressed, hex-encoding + { + let tokens = &[ + Token::Struct { + name: "ScalarUncompressed", + len: 2, + }, + Token::Str("curve"), + Token::Str(E::CURVE_NAME), + Token::Str("scalar"), + Token::Str(scalar_hex), + Token::StructEnd, + ]; + serde_test::assert_de_tokens(&scalar.readable(), tokens); + serde_test::assert_de_tokens(&PreferCompact(scalar).readable(), tokens); + } + + // Uncompressed, seq-encoded + { + let mut tokens = vec![ + Token::Struct { + name: "ScalarUncompressed", + len: 2, + }, + Token::Str("curve"), + Token::Str(E::CURVE_NAME), + Token::Str("scalar"), + Token::Seq { len: None }, + ]; + tokens.extend(scalar_bytes.iter().copied().map(Token::U8)); + tokens.extend([Token::SeqEnd, Token::StructEnd]); + serde_test::assert_de_tokens(&scalar.readable(), &tokens); + serde_test::assert_de_tokens(&PreferCompact(scalar).readable(), &tokens); + } + + // Uncompressed, bytes-encoded + { + let tokens = &[ + Token::Struct { + name: "ScalarUncompressed", + len: 2, + }, + Token::Str("curve"), + Token::Str(E::CURVE_NAME), + Token::Str("scalar"), + Token::Bytes(scalar_bytes), + Token::StructEnd, + ]; + serde_test::assert_de_tokens(&scalar.readable(), tokens); + serde_test::assert_de_tokens(&PreferCompact(scalar).readable(), tokens); + } + + // Compact, hex-encoded + { + let tokens = &[ + Token::NewtypeStruct { + name: "ScalarCompact", + }, + Token::Str(scalar_hex), + ]; + serde_test::assert_de_tokens(&Compact(scalar).readable(), tokens); + serde_test::assert_de_tokens(&PreferCompact(scalar).readable(), tokens); + } + + // Compact, seq-encoded + { + let mut tokens = vec![ + Token::NewtypeStruct { + name: "ScalarCompact", + }, + Token::Seq { len: None }, + ]; + tokens.extend(scalar_bytes.iter().copied().map(Token::U8)); + tokens.extend([Token::SeqEnd]); + serde_test::assert_de_tokens(&Compact(scalar).readable(), &tokens); + serde_test::assert_de_tokens(&PreferCompact(scalar).readable(), &tokens); } } } #[derive(PartialEq, Eq, Debug)] - struct Compressed(T); - impl serde::Serialize for Compressed + struct Compact(T); + impl serde::Serialize for Compact where generic_ec::serde::Compact: serde_with::SerializeAs, { @@ -167,7 +372,7 @@ mod tests { generic_ec::serde::Compact::serialize_as(&self.0, serializer) } } - impl<'de, T> serde::Deserialize<'de> for Compressed + impl<'de, T> serde::Deserialize<'de> for Compact where generic_ec::serde::Compact: serde_with::DeserializeAs<'de, T>, { @@ -180,6 +385,33 @@ mod tests { } } + #[derive(PartialEq, Eq, Debug)] + struct PreferCompact(T); + impl serde::Serialize for PreferCompact + where + generic_ec::serde::PreferCompact: serde_with::SerializeAs, + { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde_with::SerializeAs; + generic_ec::serde::PreferCompact::serialize_as(&self.0, serializer) + } + } + impl<'de, T> serde::Deserialize<'de> for PreferCompact + where + generic_ec::serde::PreferCompact: serde_with::DeserializeAs<'de, T>, + { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde_with::DeserializeAs; + generic_ec::serde::PreferCompact::deserialize_as(deserializer).map(Self) + } + } + #[instantiate_tests()] mod secp256k1 {} From 98b179e206ee68c4b245c6e13a41d867134a6fbc Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Mon, 25 Mar 2024 12:02:46 +0100 Subject: [PATCH 2/2] Replace `alloc::fmt` with `core::fmt` --- generic-ec/CHANGELOG.md | 6 ++++++ generic-ec/Cargo.toml | 2 +- generic-ec/src/serde.rs | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/generic-ec/CHANGELOG.md b/generic-ec/CHANGELOG.md index e4d29b9..9f26a9f 100644 --- a/generic-ec/CHANGELOG.md +++ b/generic-ec/CHANGELOG.md @@ -1,3 +1,9 @@ +## v0.2.3 +* Add `generic_ec::serde::PreferCompact` that serializes points/scalars in compact form, + but deserialization recognizes both compact and non-compact formats [#28] + +[#28]: https://github.com/dfns/generic-ec/pull/28 + ## v0.2.2 * Implement `serde_with::SerializeAs<&T>` for `generic_ec::serde::Compact` when `T` is serializable via `Compact` [#27] diff --git a/generic-ec/Cargo.toml b/generic-ec/Cargo.toml index 55334b4..3b40bf7 100644 --- a/generic-ec/Cargo.toml +++ b/generic-ec/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "generic-ec" -version = "0.2.2" +version = "0.2.3" edition = "2021" license = "MIT OR Apache-2.0" repository = "https://github.com/dfns/generic-ec" diff --git a/generic-ec/src/serde.rs b/generic-ec/src/serde.rs index ca39648..9f6aaf3 100644 --- a/generic-ec/src/serde.rs +++ b/generic-ec/src/serde.rs @@ -384,7 +384,7 @@ mod optional { Compact: serde_with::DeserializeAs<'de, T>, { type Value = T; - fn expecting(&self, f: &mut alloc::fmt::Formatter) -> alloc::fmt::Result { + fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.write_str("preferably compact point/scalar") }