Selaa lähdekoodia

Create, store, and load private and public keys for credential types

Precomputed Wnaf tables are created and used for each mathematical group
used.
Ian Goldberg 6 kuukautta sitten
vanhempi
commit
987e1e5db6
4 muutettua tiedostoa jossa 274 lisäystä ja 19 poistoa
  1. 8 1
      Cargo.toml
  2. 46 7
      cmzcred_derive/src/lib.rs
  3. 194 11
      src/lib.rs
  4. 26 0
      tests/basic.rs

+ 8 - 1
Cargo.toml

@@ -5,6 +5,13 @@ edition = "2021"
 
 [dependencies]
 cmzcred_derive = { path = "cmzcred_derive" }
-curve25519-dalek = { version = "4", features = [ "group" ] }
+curve25519-dalek = { version = "4", features = [ "group", "rand_core", "digest" ] }
 ff = "0.13"
 group = "0.13"
+lazy_static = "1"
+generic_static = "0.2"
+rand_core = "0.6"
+
+[dev-dependencies]
+rand = "0.8"
+sha2 = "0.10"

+ 46 - 7
cmzcred_derive/src/lib.rs

@@ -13,7 +13,7 @@ the CMZCredential trait for the declared credential.
 
 use proc_macro::TokenStream;
 use quote::quote;
-use syn::{Data,DataStruct,DeriveInput,Fields,FieldsNamed,Ident};
+use syn::{Data,DataStruct,DeriveInput,Fields,FieldsNamed,Ident,Visibility};
 use darling::FromDeriveInput;
 
 fn impl_cmzcred_derive(ast: &syn::DeriveInput, group_ident: &Ident) -> TokenStream {
@@ -25,8 +25,9 @@ fn impl_cmzcred_derive(ast: &syn::DeriveInput, group_ident: &Ident) -> TokenStre
         panic!("CMZCred derived on a non-struct");
     };
     // attrs and idents are each vectors of the names of the attributes
-    // of the credential (not including the MAC).  attrs stores the
-    // names as Strings, while idents stores them as Idents.
+    // of the credential (not including the MAC and any non-public
+    // fields).  attrs stores the names as Strings, while idents stores
+    // them as Idents.
     let mut attrs = Vec::<String>::new();
     let mut idents = Vec::<&Ident>::new();
     for n in named {
@@ -34,9 +35,11 @@ fn impl_cmzcred_derive(ast: &syn::DeriveInput, group_ident: &Ident) -> TokenStre
             panic!("Missing attribute name in CMZCred");
         };
         let id_str = ident.to_string();
-        if id_str != String::from("MAC") {
-            attrs.push(id_str);
-            idents.push(ident);
+        if let Visibility::Public(_) = n.vis {
+            if id_str != String::from("MAC") {
+                attrs.push(id_str);
+                idents.push(ident);
+            }
         }
     }
     let num_attrs = attrs.len();
@@ -44,7 +47,7 @@ fn impl_cmzcred_derive(ast: &syn::DeriveInput, group_ident: &Ident) -> TokenStre
     let errmsg = format!("Invalid attribute name for {} CMZ credential",
         name);
 
-    // Output the CMZCredential trail implementation
+    // Output the CMZCredential trait implementation
     let gen = quote! {
         impl CMZCredential<#group_ident> for #name {
             type Scalar = <#group_ident as Group>::Scalar;
@@ -73,6 +76,42 @@ fn impl_cmzcred_derive(ast: &syn::DeriveInput, group_ident: &Ident) -> TokenStre
                     _ => panic!(#errmsg),
                 }
             }
+
+            fn set_pubkey(&mut self, pubkey: &CMZPubkey<Self::Point>) {
+                self.pubkey = pubkey.clone();
+            }
+
+            fn get_pubkey(&self) -> CMZPubkey<Self::Point> {
+                self.pubkey.clone()
+            }
+
+            fn set_privkey(&mut self, privkey: &CMZPrivkey<Self::Point>) {
+                self.pubkey = cmz_privkey_to_pubkey(&privkey);
+                self.privkey = privkey.clone();
+            }
+
+            fn get_privkey(&self) -> CMZPrivkey<Self::Point> {
+                self.privkey.clone()
+            }
+
+            fn gen_keys(rng: &mut impl RngCore) ->
+                    (CMZPrivkey<Self::Point>, CMZPubkey<Self::Point>) {
+                // Generate (num_attrs + 2) random scalars as the
+                // private key
+                let x0tilde: Self::Scalar =
+                    <Self::Scalar as ff::Field>::random(&mut *rng);
+                let x0: Self::Scalar =
+                    <Self::Scalar as ff::Field>::random(&mut *rng);
+                let x: Vec<Self::Scalar> = (0..Self::num_attrs())
+                    .map(|_| <Self::Scalar as ff::Field>::random(&mut *rng))
+                    .collect();
+                let privkey = CMZPrivkey { x0tilde, x0, x };
+
+                // Convert the private key to a public key
+                let pubkey = cmz_privkey_to_pubkey(&privkey);
+
+                (privkey, pubkey)
+            }
         }
     };
     gen.into()

+ 194 - 11
src/lib.rs

@@ -2,23 +2,179 @@
 // lowercase letters
 #![allow(non_snake_case)]
 
+pub use cmzcred_derive::CMZCred;
+use core::any::Any;
 use ff::PrimeField;
-use group::Group;
+use generic_static::StaticTypeMap;
+use group::prime::PrimeGroup;
+use group::{Group, WnafBase, WnafScalar};
+use lazy_static::lazy_static;
+use rand_core::RngCore;
 
 /// The CMZMac struct represents a MAC on a CMZ credential.
 #[derive(Copy, Clone, Debug, Default)]
-pub struct CMZMac<G: Group> {
+pub struct CMZMac<G: PrimeGroup> {
     pub P: G,
     pub Q: G,
 }
 
+/// The CMZPrivkey struct represents a CMZ private key
+#[derive(Clone, Debug, Default)]
+pub struct CMZPrivkey<G: PrimeGroup> {
+    pub x0tilde: <G as Group>::Scalar,
+    pub x0: <G as Group>::Scalar,
+    // The elements of x correspond to the attributes of the credential
+    pub x: Vec<<G as Group>::Scalar>,
+}
+
+/// The CMZPubkey struct represents a CMZ public key
+#[derive(Clone, Debug, Default)]
+pub struct CMZPubkey<G: PrimeGroup> {
+    pub X0: Option<G>,
+    // The elements of X correspond to the attributes of the credential
+    pub X: Vec<G>,
+}
+
+// The size of the WNAF windows.  Larger sizes take more memory, but
+// result in faster multiplications.
+const WNAF_SIZE: usize = 6;
+
+// A struct (generic over G) holding the two CMZ bases, and their Wnaf
+// basepoint tables
+#[derive(Clone)]
+struct CMZBasepoints<G: Group> {
+    A: G,
+    B: G,
+    A_TABLE: WnafBase<G, WNAF_SIZE>,
+    B_TABLE: WnafBase<G, WNAF_SIZE>,
+}
+
+impl<G: Group> CMZBasepoints<G> {
+    fn init(generator_A: G) -> Self {
+        let A = generator_A;
+        let B = G::generator();
+        let A_TABLE = WnafBase::new(A);
+        let B_TABLE = WnafBase::new(B);
+        CMZBasepoints {
+            A,
+            B,
+            A_TABLE,
+            B_TABLE,
+        }
+    }
+
+    fn mulA(&self, s: &G::Scalar) -> G {
+        let wnaf_s = WnafScalar::<G::Scalar, WNAF_SIZE>::new(&s);
+        &self.A_TABLE * &wnaf_s
+    }
+
+    fn mulB(&self, s: &G::Scalar) -> G {
+        let wnaf_s = WnafScalar::<G::Scalar, WNAF_SIZE>::new(&s);
+        &self.B_TABLE * &wnaf_s
+    }
+}
+
+// What's going on here needs some explanation.  For each group G, we
+// want to pre-compute the WnafBase tables in a CMZBasepoints<G> struct,
+// and we want that pre-computed struct to remain globally accessible.
+// So ideally, we'd just have a generic static CMZBasepoints<G> struct,
+// and instantiate it once for each G that we use.
+//
+// The tricky bit is that we don't know what group(s) G the programmer
+// (the person using this cmz crate) will end up using, and Rust doesn't
+// support generic statics.
+//
+// So what we'd like is a non-generic static _map_ that maps a group
+// type G to the precomputed CMZBasepoints<G> struct.  But types aren't
+// values that can be mapped by a normal HashMap.  Luckily, there's a
+// generic_static crate that provides a StaticTypeMap that has the
+// ability to map types to objects.
+//
+// However, all of those *mapped-to* objects have to all be of the same
+// type, whereas we want the type G to map to a struct of type
+// CMZBasepoints<G>, which is different for each value of G.
+//
+// So we make a non-generic trait CMZBP that all instantiations of
+// CMZBasepoints<G> implement (for all group types G), and have the
+// StaticTypeMap map each type G to a trait object Box<dyn CMZBP>.
+//
+// Then to read the CMZBasepoints<G> back out, we look up the trait
+// object in the StaticTypeMap, yielding a Box<dyn CMZBP>.  We now need
+// to downcast this trait object to the concrete type CMZBasepoints<G>,
+// for a _specific_ G.  Rust provides downcasting, but only from &dyn Any
+// to the original concrete type, not from other things like &dyn CMZBP.
+// So first we need to upcast the trait object to &dyn Any, which we do
+// with an "as_any()" function in the CMZBP trait, and then downcast the
+// result to a CMZBasepoints<G> struct.
+//
+// The up/down casting pattern is from
+// https://stackoverflow.com/questions/33687447/how-to-get-a-reference-to-a-concrete-type-from-a-trait-object
+
+// Static objects have to be Sync + Send, so enforce that as part of the
+// CMXBP trait
+trait CMZBP: Sync + Send {
+    fn as_any(&self) -> &dyn Any;
+}
+
+impl<G: Group> CMZBP for CMZBasepoints<G> {
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+}
+
+// The StaticTypeMap mapping group types G to trait objects Box<dyn CMZBP>
+lazy_static! {
+    static ref basepoints_map: StaticTypeMap<Box<dyn CMZBP>> = StaticTypeMap::new();
+}
+
+/// For a given group type G, if bp is Some(b), then load the mapping
+/// from G to b into the basepoints_map.  (If a mapping from G already
+/// exists, the old one will be kept and the new one ignored.)  Whether
+/// bp is Some(b) or None, this function returns the (possibly new)
+/// target of the basepoints_map, as a &'static CMZBasepoints<G>.
+fn load_bp<G: Group>(bp: Option<CMZBasepoints<G>>) -> &'static CMZBasepoints<G> {
+    match bp {
+        Some(b) => basepoints_map.call_once::<Box<dyn CMZBP>, _>(|| Box::new(b.clone())),
+        None => {
+            basepoints_map.call_once::<Box<dyn CMZBP>, _>(|| panic!("basepoints uninitialized"))
+        }
+    }
+    .as_any()
+    .downcast_ref::<CMZBasepoints<G>>()
+    .unwrap()
+}
+
+/// CMZ credentials require two generators, A and B.  B is the
+/// "standard" generator.  A can be any other generator (that is, any
+/// other non-identity point in a prime-order group), but it is required
+/// that no one know the discrete log between A and B.  So you can't
+/// generate A by multiplying B by some scalar, for example.  If your
+/// group has a hash_from_bytes function, then pass
+/// hash_from_bytes::<Sha512>(b"CMZ Generator A").  Otherwise, you're
+/// possibly on your own to generate an appropriate generator A.
+/// Everyone who uses a given credential type with a given group will
+/// need to use the same A.  You need to call this before doing any
+/// operations with a credential.
+pub fn cmz_group_init<G: PrimeGroup>(generator_A: G) {
+    let bp = CMZBasepoints::<G>::init(generator_A);
+    load_bp(Some(bp));
+}
+
+/// Compute a public key from a private key
+pub fn cmz_privkey_to_pubkey<G: PrimeGroup>(privkey: &CMZPrivkey<G>) -> CMZPubkey<G> {
+    let bp = load_bp::<G>(None);
+    let X0: Option<G> = Some(bp.mulA(&privkey.x0tilde) + bp.mulB(&privkey.x0));
+    let X: Vec<G> = privkey.x.iter().map(|x| bp.mulA(x)).collect();
+    CMZPubkey { X0, X }
+}
+
 /// The CMZCredential trait implemented by all CMZ credential struct types.
-pub trait CMZCredential<G: Group> {
+pub trait CMZCredential<G: PrimeGroup> {
     /// The type of attributes for this credential
     type Scalar: PrimeField;
 
     /// The type of the coordinates of the MAC for this credential
-    type Point: Group;
+    type Point: PrimeGroup;
 
     /// Produce a vector of strings containing the names of the
     /// attributes of this credential.  (The MAC is not included.)
@@ -34,6 +190,27 @@ pub trait CMZCredential<G: Group> {
     /// Get a mutable reference to one of the attributes, specified by
     /// name as a string.
     fn attr_mut(&mut self, name: &str) -> &mut Option<Self::Scalar>;
+
+    /// Set the public key for this credential.
+    fn set_pubkey(&mut self, pubkey: &CMZPubkey<G>);
+
+    /// Get a copy of the public key for this credential.  If the public
+    /// key has not yet been set or computed, a pubkey with X0 == None
+    /// will be returned.
+    fn get_pubkey(&self) -> CMZPubkey<G>;
+
+    /// Set the private key for this credential.  The public key will
+    /// automatically be computed from the private key.
+    fn set_privkey(&mut self, privkey: &CMZPrivkey<G>);
+
+    /// Get a copy of the private key for this credential.  If the
+    /// private key has not yet been set, a privkey with an empty x
+    /// vector will be returned.
+    fn get_privkey(&self) -> CMZPrivkey<G>;
+
+    /// Generate random private and public keys for this credential
+    /// type.
+    fn gen_keys(rng: &mut impl RngCore) -> (CMZPrivkey<G>, CMZPubkey<G>);
 }
 
 /** The CMZ macro for declaring CMZ credentials.
@@ -47,10 +224,11 @@ of the listed attributes.  The attribute fields will be of type
 `Option<Scalar>`.  It will also automatically add a field called `MAC`
 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 group elements for the commitments
-and MAC components) 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:
+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:
 
 use curve25519_dalek::ristretto::RistrettoPoint as G;
 
@@ -59,27 +237,33 @@ or:
 use curve25519_dalek::ristretto::RistrettoPoint;
 type G = RistrettoPoint;
 
+The group must implement the trait group::prime::PrimeGroup.
+
 */
 #[macro_export]
 macro_rules! CMZ {
     ( $name: ident < $G: ident > : $( $id: ident ),+ ) => {
-        #[derive(CMZCred,Copy,Clone,Debug,Default)]
+        #[derive(CMZCred,Clone,Debug,Default)]
         #[cmzcred_group(group = $G)]
         pub struct $name {
         $(
             pub $id: Option<<$G as Group>::Scalar>,
         )+
             pub MAC: CMZMac<$G>,
+            privkey: CMZPrivkey<$G>,
+            pubkey: CMZPubkey<$G>,
         }
     };
     ( $name: ident : $( $id: ident ),+ ) => {
-        #[derive(CMZCred,Copy,Clone,Debug,Default)]
+        #[derive(CMZCred,Clone,Debug,Default)]
         #[cmzcred_group(group = G)]
         pub struct $name {
         $(
             pub $id: Option<<G as Group>::Scalar>,
         )+
             pub MAC: CMZMac<G>,
+            privkey: CMZPrivkey<G>,
+            pubkey: CMZPubkey<G>,
         }
     };
 }
@@ -87,7 +271,6 @@ macro_rules! CMZ {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use cmzcred_derive::CMZCred;
 
     #[test]
     fn lox_credential_test() {

+ 26 - 0
tests/basic.rs

@@ -0,0 +1,26 @@
+use cmz::*;
+use curve25519_dalek::ristretto::RistrettoPoint;
+use group::Group;
+use rand_core::RngCore;
+use sha2::Sha512;
+
+CMZ! { Basic<RistrettoPoint> :
+    attr1,
+    attr2
+}
+
+#[test]
+fn test_basic() {
+    let mut rng = rand::thread_rng();
+    cmz_group_init(RistrettoPoint::hash_from_bytes::<Sha512>(
+        b"CMZ Generator A",
+    ));
+    let mut basic_cred = Basic::default();
+
+    println!("{:#?}", basic_cred);
+
+    let (privkey, pubkey) = Basic::gen_keys(&mut rng);
+    basic_cred.set_privkey(&privkey);
+
+    println!("{:#?}", basic_cred);
+}