浏览代码

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" }
 cmzcred_derive = { path = "cmzcred_derive" }
 curve25519-dalek = { version = "4", features = [ "group", "rand_core", "digest" ] }
 curve25519-dalek = { version = "4", features = [ "group", "rand_core", "digest" ] }
 ff = "0.13"
 ff = "0.13"
+generic_static = "0.2"
 group = "0.13"
 group = "0.13"
+hex = { version = "0.4", features = [ "serde" ] }
 lazy_static = "1"
 lazy_static = "1"
-generic_static = "0.2"
 rand_core = "0.6"
 rand_core = "0.6"
+serde = { version = "1", features = [ "derive" ] }
+serde_with = "3"
 
 
 [dev-dependencies]
 [dev-dependencies]
+bincode = "1"
 rand = "0.8"
 rand = "0.8"
 sha2 = "0.10"
 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 ff::PrimeField;
 use generic_static::StaticTypeMap;
 use generic_static::StaticTypeMap;
 use group::prime::PrimeGroup;
 use group::prime::PrimeGroup;
-use group::{Group, WnafBase, WnafScalar};
+use group::{Group, GroupEncoding, WnafBase, WnafScalar};
 use lazy_static::lazy_static;
 use lazy_static::lazy_static;
 use rand_core::RngCore;
 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.
 /// 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> {
 pub struct CMZMac<G: PrimeGroup> {
+    #[serde_as(as = "SerdePoint")]
     pub P: G,
     pub P: G,
+    #[serde_as(as = "SerdePoint")]
     pub Q: G,
     pub Q: G,
 }
 }
 
 
 /// The CMZPrivkey struct represents a CMZ private key
 /// 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,
     pub x0tilde: <G as Group>::Scalar,
+    #[serde_as(as = "SerdeScalar")]
     pub x0: <G as Group>::Scalar,
     pub x0: <G as Group>::Scalar,
     // The elements of x correspond to the attributes of the credential
     // The elements of x correspond to the attributes of the credential
+    #[serde_as(as = "Vec<SerdeScalar>")]
     pub x: Vec<<G as Group>::Scalar>,
     pub x: Vec<<G as Group>::Scalar>,
 }
 }
 
 
 /// The CMZPubkey struct represents a CMZ public key
 /// 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>,
     pub X0: Option<G>,
     // The elements of X correspond to the attributes of the credential
     // The elements of X correspond to the attributes of the credential
+    #[serde_as(as = "Vec<SerdePoint>")]
     pub X: Vec<G>,
     pub X: Vec<G>,
 }
 }
 
 
@@ -177,7 +238,7 @@ where
     type Scalar: PrimeField;
     type Scalar: PrimeField;
 
 
     /// The type of the coordinates of the MAC for this credential
     /// 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
     /// Produce a vector of strings containing the names of the
     /// attributes of this credential.  (The MAC is not included.)
     /// 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 `CMZCredential` trait.  The mathematical group used (the field for
 the values of the attributes and the private key elements, and the group
 the values of the attributes and the private key elements, and the group
 elements for the commitments, MAC components, and public key elements)
 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;
 use curve25519_dalek::ristretto::RistrettoPoint as G;
 
 
@@ -256,16 +316,19 @@ or:
 use curve25519_dalek::ristretto::RistrettoPoint;
 use curve25519_dalek::ristretto::RistrettoPoint;
 type G = 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_export]
 macro_rules! CMZ {
 macro_rules! CMZ {
     ( $name: ident < $G: ident > : $( $id: ident ),+ ) => {
     ( $name: ident < $G: ident > : $( $id: ident ),+ ) => {
-        #[derive(CMZCred,Clone,Debug,Default)]
+        #[serde_as]
+        #[derive(CMZCred,Clone,Debug,Default,Serialize,Deserialize)]
         #[cmzcred_group(group = $G)]
         #[cmzcred_group(group = $G)]
         pub struct $name {
         pub struct $name {
         $(
         $(
+            #[serde_as(as="Option<SerdeScalar>")]
             pub $id: Option<<$G as Group>::Scalar>,
             pub $id: Option<<$G as Group>::Scalar>,
         )+
         )+
             pub MAC: CMZMac<$G>,
             pub MAC: CMZMac<$G>,
@@ -274,10 +337,12 @@ macro_rules! CMZ {
         }
         }
     };
     };
     ( $name: ident : $( $id: ident ),+ ) => {
     ( $name: ident : $( $id: ident ),+ ) => {
-        #[derive(CMZCred,Clone,Debug,Default)]
+        #[serde_as]
+        #[derive(CMZCred,Clone,Debug,Default,Serialize,Deserialize)]
         #[cmzcred_group(group = G)]
         #[cmzcred_group(group = G)]
         pub struct $name {
         pub struct $name {
         $(
         $(
+            #[serde_as(as="Option<SerdeScalar>")]
             pub $id: Option<<G as Group>::Scalar>,
             pub $id: Option<<G as Group>::Scalar>,
         )+
         )+
             pub MAC: CMZMac<G>,
             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 (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);
+
+    println!("{:#?}", basic_cred_bytes);
 }
 }