浏览代码

Add serde abilities to credentials, pubkeys, and privkeys

Ian Goldberg 6 月之前
父节点
当前提交
9e8430e2a8
共有 5 个文件被更改,包括 371 次插入15 次删除
  1. 5 1
      Cargo.toml
  2. 136 0
      src/group_serde.rs
  3. 78 13
      src/lib.rs
  4. 136 0
      src/primefield_serde.rs
  5. 16 1
      tests/basic.rs

+ 5 - 1
Cargo.toml

@@ -7,11 +7,15 @@ edition = "2021"
 cmzcred_derive = { path = "cmzcred_derive" }
 curve25519-dalek = { version = "4", features = [ "group", "rand_core", "digest" ] }
 ff = "0.13"
+generic_static = "0.2"
 group = "0.13"
+hex = { version = "0.4", features = [ "serde" ] }
 lazy_static = "1"
-generic_static = "0.2"
 rand_core = "0.6"
+serde = { version = "1", features = [ "derive" ] }
+serde_with = "3"
 
 [dev-dependencies]
+bincode = "1"
 rand = "0.8"
 sha2 = "0.10"

+ 136 - 0
src/group_serde.rs

@@ -0,0 +1,136 @@
+//! serde adaptors for Group + GroupEncoding
+//!
+//! Adapted from elliptic_curve_tools v0.1.2 by Michael Lodder:
+//! https://crates.io/crates/elliptic-curve-tools
+//!
+//! Lodder's original can be adapted under the terms of the MIT license:
+//!
+//! [No explicit copyright line was present in that package's
+//! LICENSE-MIT file.]
+//!
+//! Permission is hereby granted, free of charge, to any
+//! person obtaining a copy of this software and associated
+//! documentation files (the "Software"), to deal in the
+//! Software without restriction, including without
+//! limitation the rights to use, copy, modify, merge,
+//! publish, distribute, sublicense, and/or sell copies of
+//! the Software, and to permit persons to whom the Software
+//! is furnished to do so, subject to the following
+//! conditions:
+//!
+//! The above copyright notice and this permission notice
+//! shall be included in all copies or substantial portions
+//! of the Software.
+//!
+//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+//! ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+//! TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+//! PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+//! SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+//! CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+//! OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+//! IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+//! DEALINGS IN THE SOFTWARE.
+
+use core::{
+    fmt::{self, Formatter},
+    marker::PhantomData,
+};
+use group::{Group, GroupEncoding};
+use serde::{
+    self,
+    de::{Error as DError, Visitor},
+    Deserializer, Serializer,
+};
+
+/// Serialize a group element.
+pub fn serialize<G, S>(g: &G, s: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+    G: Group + GroupEncoding,
+{
+    serialize_(g.to_bytes(), s)
+}
+
+/// Deserialize a group element.
+pub fn deserialize<'de, G, D>(d: D) -> Result<G, D::Error>
+where
+    D: Deserializer<'de>,
+    G: Group + GroupEncoding,
+{
+    let bytes = deserialize_(d)?;
+    Option::from(G::from_bytes(&bytes)).ok_or(DError::custom("invalid group element"))
+}
+
+fn serialize_<B, S>(bytes: B, s: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+    B: AsRef<[u8]> + AsMut<[u8]> + Default,
+{
+    if s.is_human_readable() {
+        s.serialize_str(&hex::encode(bytes.as_ref()))
+    } else {
+        s.serialize_bytes(bytes.as_ref())
+    }
+}
+
+fn deserialize_<'de, B: AsRef<[u8]> + AsMut<[u8]> + Default, D: Deserializer<'de>>(
+    d: D,
+) -> Result<B, D::Error> {
+    if d.is_human_readable() {
+        struct StrVisitor<B: AsRef<[u8]> + AsMut<[u8]> + Default>(PhantomData<B>);
+
+        impl<'de, B> Visitor<'de> for StrVisitor<B>
+        where
+            B: AsRef<[u8]> + AsMut<[u8]> + Default,
+        {
+            type Value = B;
+
+            fn expecting(&self, f: &mut Formatter) -> fmt::Result {
+                write!(f, "a {} length hex string", B::default().as_ref().len() * 2)
+            }
+
+            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+            where
+                E: DError,
+            {
+                let mut repr = B::default();
+                let length = repr.as_ref().len();
+                if v.len() != length * 2 {
+                    return Err(DError::custom("invalid length"));
+                }
+                hex::decode_to_slice(v, repr.as_mut())
+                    .map_err(|_| DError::custom("invalid input"))?;
+                Ok(repr)
+            }
+        }
+        d.deserialize_str(StrVisitor(PhantomData))
+    } else {
+        struct ByteVisitor<B: AsRef<[u8]> + AsMut<[u8]> + Default>(PhantomData<B>);
+
+        impl<'de, B> Visitor<'de> for ByteVisitor<B>
+        where
+            B: AsRef<[u8]> + AsMut<[u8]> + Default,
+        {
+            type Value = B;
+
+            fn expecting(&self, f: &mut Formatter) -> fmt::Result {
+                write!(f, "a {} byte", B::default().as_ref().len())
+            }
+
+            fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
+            where
+                E: serde::de::Error,
+            {
+                let mut repr = B::default();
+                if v.len() != repr.as_ref().len() {
+                    return Err(serde::de::Error::custom("invalid length"));
+                }
+                repr.as_mut().copy_from_slice(v);
+                Ok(repr)
+            }
+        }
+
+        d.deserialize_bytes(ByteVisitor(PhantomData))
+    }
+}

+ 78 - 13
src/lib.rs

@@ -7,31 +7,92 @@ use core::any::Any;
 use ff::PrimeField;
 use generic_static::StaticTypeMap;
 use group::prime::PrimeGroup;
-use group::{Group, WnafBase, WnafScalar};
+use group::{Group, GroupEncoding, WnafBase, WnafScalar};
 use lazy_static::lazy_static;
 use rand_core::RngCore;
+pub use serde::{Deserialize, Deserializer, Serialize, Serializer};
+pub use serde_with::{serde_as, DeserializeAs, SerializeAs};
+
+// We need wrappers for group::Group and ff::PrimeField elements to be
+// handled by serde
+//
+// Pattern from https://docs.rs/serde_with/3.12.0/serde_with/guide/serde_as/index.html
+
+mod primefield_serde;
+
+pub struct SerdeScalar;
+
+impl<F: PrimeField> SerializeAs<F> for SerdeScalar {
+    fn serialize_as<S>(value: &F, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        primefield_serde::serialize(value, serializer)
+    }
+}
+
+impl<'de, F: PrimeField> DeserializeAs<'de, F> for SerdeScalar {
+    fn deserialize_as<D>(deserializer: D) -> Result<F, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        primefield_serde::deserialize(deserializer)
+    }
+}
+
+mod group_serde;
+
+pub struct SerdePoint;
+
+impl<G: Group + GroupEncoding> SerializeAs<G> for SerdePoint {
+    fn serialize_as<S>(value: &G, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        group_serde::serialize(value, serializer)
+    }
+}
+
+impl<'de, G: Group + GroupEncoding> DeserializeAs<'de, G> for SerdePoint {
+    fn deserialize_as<D>(deserializer: D) -> Result<G, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        group_serde::deserialize(deserializer)
+    }
+}
 
 /// The CMZMac struct represents a MAC on a CMZ credential.
-#[derive(Copy, Clone, Debug, Default)]
+#[serde_as]
+#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
 pub struct CMZMac<G: PrimeGroup> {
+    #[serde_as(as = "SerdePoint")]
     pub P: G,
+    #[serde_as(as = "SerdePoint")]
     pub Q: G,
 }
 
 /// The CMZPrivkey struct represents a CMZ private key
-#[derive(Clone, Debug, Default)]
-pub struct CMZPrivkey<G: PrimeGroup> {
+#[serde_as]
+#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
+pub struct CMZPrivkey<G: PrimeGroup + GroupEncoding> {
+    #[serde_as(as = "SerdeScalar")]
     pub x0tilde: <G as Group>::Scalar,
+    #[serde_as(as = "SerdeScalar")]
     pub x0: <G as Group>::Scalar,
     // The elements of x correspond to the attributes of the credential
+    #[serde_as(as = "Vec<SerdeScalar>")]
     pub x: Vec<<G as Group>::Scalar>,
 }
 
 /// The CMZPubkey struct represents a CMZ public key
-#[derive(Clone, Debug, Default)]
-pub struct CMZPubkey<G: PrimeGroup> {
+#[serde_as]
+#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
+pub struct CMZPubkey<G: PrimeGroup + GroupEncoding> {
+    #[serde_as(as = "Option<SerdePoint>")]
     pub X0: Option<G>,
     // The elements of X correspond to the attributes of the credential
+    #[serde_as(as = "Vec<SerdePoint>")]
     pub X: Vec<G>,
 }
 
@@ -177,7 +238,7 @@ where
     type Scalar: PrimeField;
 
     /// The type of the coordinates of the MAC for this credential
-    type Point: PrimeGroup;
+    type Point: PrimeGroup + GroupEncoding;
 
     /// Produce a vector of strings containing the names of the
     /// attributes of this credential.  (The MAC is not included.)
@@ -245,9 +306,8 @@ of type `CMZMac`, and an implementation (via the `CMZCred` derive) of
 the `CMZCredential` trait.  The mathematical group used (the field for
 the values of the attributes and the private key elements, and the group
 elements for the commitments, MAC components, and public key elements)
-is Group (which must satisfy the group::Group trait).  If "<Group>" is
-omitted, the macro will default to using a group called "G", which you
-can define, for example, as:
+is Group.  If "<Group>" is omitted, the macro will default to using a
+group called "G", which you can define, for example, as:
 
 use curve25519_dalek::ristretto::RistrettoPoint as G;
 
@@ -256,16 +316,19 @@ or:
 use curve25519_dalek::ristretto::RistrettoPoint;
 type G = RistrettoPoint;
 
-The group must implement the trait group::prime::PrimeGroup.
+The group must implement the traits group::prime::PrimeGroup and
+group::GroupEncoding.
 
 */
 #[macro_export]
 macro_rules! CMZ {
     ( $name: ident < $G: ident > : $( $id: ident ),+ ) => {
-        #[derive(CMZCred,Clone,Debug,Default)]
+        #[serde_as]
+        #[derive(CMZCred,Clone,Debug,Default,Serialize,Deserialize)]
         #[cmzcred_group(group = $G)]
         pub struct $name {
         $(
+            #[serde_as(as="Option<SerdeScalar>")]
             pub $id: Option<<$G as Group>::Scalar>,
         )+
             pub MAC: CMZMac<$G>,
@@ -274,10 +337,12 @@ macro_rules! CMZ {
         }
     };
     ( $name: ident : $( $id: ident ),+ ) => {
-        #[derive(CMZCred,Clone,Debug,Default)]
+        #[serde_as]
+        #[derive(CMZCred,Clone,Debug,Default,Serialize,Deserialize)]
         #[cmzcred_group(group = G)]
         pub struct $name {
         $(
+            #[serde_as(as="Option<SerdeScalar>")]
             pub $id: Option<<G as Group>::Scalar>,
         )+
             pub MAC: CMZMac<G>,

+ 136 - 0
src/primefield_serde.rs

@@ -0,0 +1,136 @@
+//! serde adaptors for PrimeField
+//!
+//! Adapted from elliptic_curve_tools v0.1.2 by Michael Lodder:
+//! https://crates.io/crates/elliptic-curve-tools
+//!
+//! Lodder's original can be adapted under the terms of the MIT license:
+//!
+//! [No explicit copyright line was present in that package's
+//! LICENSE-MIT file.]
+//!
+//! Permission is hereby granted, free of charge, to any
+//! person obtaining a copy of this software and associated
+//! documentation files (the "Software"), to deal in the
+//! Software without restriction, including without
+//! limitation the rights to use, copy, modify, merge,
+//! publish, distribute, sublicense, and/or sell copies of
+//! the Software, and to permit persons to whom the Software
+//! is furnished to do so, subject to the following
+//! conditions:
+//!
+//! The above copyright notice and this permission notice
+//! shall be included in all copies or substantial portions
+//! of the Software.
+//!
+//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+//! ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+//! TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+//! PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+//! SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+//! CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+//! OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+//! IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+//! DEALINGS IN THE SOFTWARE.
+
+use core::{
+    fmt::{self, Formatter},
+    marker::PhantomData,
+};
+use ff::PrimeField;
+use serde::{
+    self,
+    de::{Error as DError, Visitor},
+    Deserializer, Serializer,
+};
+
+/// Serialize a prime field element.
+pub fn serialize<F, S>(f: &F, s: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+    F: PrimeField,
+{
+    serialize_(f.to_repr(), s)
+}
+
+/// Deserialize a prime field element.
+pub fn deserialize<'de, F, D>(d: D) -> Result<F, D::Error>
+where
+    D: Deserializer<'de>,
+    F: PrimeField,
+{
+    let repr = deserialize_(d)?;
+    Option::from(F::from_repr(repr)).ok_or(DError::custom("invalid prime field element"))
+}
+
+fn serialize_<B, S>(bytes: B, s: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+    B: AsRef<[u8]> + AsMut<[u8]> + Default,
+{
+    if s.is_human_readable() {
+        s.serialize_str(&hex::encode(bytes.as_ref()))
+    } else {
+        s.serialize_bytes(bytes.as_ref())
+    }
+}
+
+fn deserialize_<'de, B: AsRef<[u8]> + AsMut<[u8]> + Default, D: Deserializer<'de>>(
+    d: D,
+) -> Result<B, D::Error> {
+    if d.is_human_readable() {
+        struct StrVisitor<B: AsRef<[u8]> + AsMut<[u8]> + Default>(PhantomData<B>);
+
+        impl<'de, B> Visitor<'de> for StrVisitor<B>
+        where
+            B: AsRef<[u8]> + AsMut<[u8]> + Default,
+        {
+            type Value = B;
+
+            fn expecting(&self, f: &mut Formatter) -> fmt::Result {
+                write!(f, "a {} length hex string", B::default().as_ref().len() * 2)
+            }
+
+            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+            where
+                E: DError,
+            {
+                let mut repr = B::default();
+                let length = repr.as_ref().len();
+                if v.len() != length * 2 {
+                    return Err(DError::custom("invalid length"));
+                }
+                hex::decode_to_slice(v, repr.as_mut())
+                    .map_err(|_| DError::custom("invalid input"))?;
+                Ok(repr)
+            }
+        }
+        d.deserialize_str(StrVisitor(PhantomData))
+    } else {
+        struct ByteVisitor<B: AsRef<[u8]> + AsMut<[u8]> + Default>(PhantomData<B>);
+
+        impl<'de, B> Visitor<'de> for ByteVisitor<B>
+        where
+            B: AsRef<[u8]> + AsMut<[u8]> + Default,
+        {
+            type Value = B;
+
+            fn expecting(&self, f: &mut Formatter) -> fmt::Result {
+                write!(f, "a {} byte", B::default().as_ref().len())
+            }
+
+            fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
+            where
+                E: serde::de::Error,
+            {
+                let mut repr = B::default();
+                if v.len() != repr.as_ref().len() {
+                    return Err(serde::de::Error::custom("invalid length"));
+                }
+                repr.as_mut().copy_from_slice(v);
+                Ok(repr)
+            }
+        }
+
+        d.deserialize_bytes(ByteVisitor(PhantomData))
+    }
+}

+ 16 - 1
tests/basic.rs

@@ -17,7 +17,22 @@ fn test_basic() {
     ));
 
     let (privkey, pubkey) = Basic::gen_keys(&mut rng);
-    let basic_cred = Basic::using_privkey(&privkey);
+
+    // Serialize and deserialize
+    let privkey_bytes = bincode::serialize(&privkey).unwrap();
+    let pubkey_bytes = bincode::serialize(&pubkey).unwrap();
+
+    let privkey_serde = bincode::deserialize::<CMZPrivkey<RistrettoPoint>>(&privkey_bytes).unwrap();
+    let pubkey_serde = bincode::deserialize::<CMZPubkey<RistrettoPoint>>(&pubkey_bytes).unwrap();
+
+    assert!(privkey == privkey_serde);
+    assert!(pubkey == pubkey_serde);
+
+    let basic_cred = Basic::using_privkey(&privkey_serde);
+
+    let basic_cred_bytes = bincode::serialize(&basic_cred).unwrap();
 
     println!("{:#?}", basic_cred);
+
+    println!("{:#?}", basic_cred_bytes);
 }