|
@@ -1,3 +1,6 @@
|
|
|
+// We want the macros like CMZProto to be camel case
|
|
|
+#![allow(non_snake_case)]
|
|
|
+
|
|
|
/*! The implementation of the CMZCred derive.
|
|
|
|
|
|
This derive should not be explicitly used by a programmer using a CMZ
|
|
@@ -14,7 +17,13 @@ the CMZCredential trait for the declared credential.
|
|
|
use darling::FromDeriveInput;
|
|
|
use proc_macro::TokenStream;
|
|
|
use quote::quote;
|
|
|
-use syn::{Data, DataStruct, DeriveInput, Fields, FieldsNamed, Ident, Visibility};
|
|
|
+use std::collections::HashMap;
|
|
|
+use syn::parse::{Parse, ParseStream, Result};
|
|
|
+use syn::punctuated::Punctuated;
|
|
|
+use syn::{
|
|
|
+ braced, bracketed, parse_macro_input, token, Data, DataStruct, DeriveInput, Expr, Fields,
|
|
|
+ FieldsNamed, Ident, Token, Visibility,
|
|
|
+};
|
|
|
|
|
|
fn impl_cmzcred_derive(ast: &syn::DeriveInput, group_ident: &Ident) -> TokenStream {
|
|
|
// Ensure that CMZCred is derived on a struct and not something else
|
|
@@ -145,3 +154,264 @@ pub fn cmzcred_derive(input: TokenStream) -> TokenStream {
|
|
|
// Build the trait implementation
|
|
|
impl_cmzcred_derive(&ast, &group_ident.group)
|
|
|
}
|
|
|
+
|
|
|
+/** The CMZ Protocol creation macros.
|
|
|
+
|
|
|
+ The format is:
|
|
|
+
|
|
|
+ let proto = CMZProtocol! { proto_name,
|
|
|
+ [ A: Cred {
|
|
|
+ attr1: H,
|
|
|
+ attr2: R,
|
|
|
+ },
|
|
|
+ B: Cred2 {
|
|
|
+ attr3: H,
|
|
|
+ attr4: I,
|
|
|
+ } ],
|
|
|
+ C: Cred3 {
|
|
|
+ attr5: J,
|
|
|
+ attr6: R,
|
|
|
+ attr7: H,
|
|
|
+ attr8: I,
|
|
|
+ attr9: S,
|
|
|
+ },
|
|
|
+ A.attr1 == B.attr3,
|
|
|
+ A.attr1 == C.attr7,
|
|
|
+ };
|
|
|
+
|
|
|
+ The parameters are:
|
|
|
+ - an identifier for the protocol
|
|
|
+ - a list of zero or more specifications for credentials that will be shown
|
|
|
+ - a list of zero or more specifications for credentials that will be issued
|
|
|
+ - zero or more statements relating the attributes in the credentials
|
|
|
+
|
|
|
+ Each credential specification list can be:
|
|
|
+ - empty
|
|
|
+ - a single credential specification
|
|
|
+ - a square-bracketed list of credential specifications
|
|
|
+
|
|
|
+ Each credential specification is:
|
|
|
+ - an identifier for the credential
|
|
|
+ - a type for the credential, previously defined with the CMZ! macro
|
|
|
+ - a braced list of the attributes of the credential (as defined in
|
|
|
+ the CMZ! macro), annotated with the attribute specification
|
|
|
+
|
|
|
+ An attribute specification for a credential to be shown is one of:
|
|
|
+ - H (hide)
|
|
|
+ - R (reveal)
|
|
|
+ - I (implicit)
|
|
|
+
|
|
|
+ An attribute specification for a credential to be issued is one of:
|
|
|
+ - H (hide)
|
|
|
+ - R (reveal)
|
|
|
+ - I (implicit)
|
|
|
+ - S (set by issuer)
|
|
|
+ - J (joint creation)
|
|
|
+*/
|
|
|
+
|
|
|
+// The possible attribute specifications for a credential to be shown
|
|
|
+#[derive(Copy, Clone, Debug, PartialEq)]
|
|
|
+enum ShowSpec {
|
|
|
+ Hide,
|
|
|
+ Reveal,
|
|
|
+ Implicit,
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for ShowSpec {
|
|
|
+ fn parse(input: ParseStream) -> Result<Self> {
|
|
|
+ let spec: Ident = input.parse()?;
|
|
|
+ match spec.to_string().to_uppercase().as_str() {
|
|
|
+ "H" | "HIDE" => Ok(Self::Hide),
|
|
|
+ "R" | "REVEAL" => Ok(Self::Reveal),
|
|
|
+ "I" | "IMPLICIT" => Ok(Self::Implicit),
|
|
|
+ _ => Err(input.error("Unknown attribute spec for shown credential")),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// The possible attribute specifications for a credential to be issued
|
|
|
+#[derive(Copy, Clone, Debug, PartialEq)]
|
|
|
+enum IssueSpec {
|
|
|
+ Hide,
|
|
|
+ Reveal,
|
|
|
+ Implicit,
|
|
|
+ Set,
|
|
|
+ Joint,
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for IssueSpec {
|
|
|
+ fn parse(input: ParseStream) -> Result<Self> {
|
|
|
+ let spec: Ident = input.parse()?;
|
|
|
+ match spec.to_string().to_uppercase().as_str() {
|
|
|
+ "H" | "HIDE" => Ok(Self::Hide),
|
|
|
+ "R" | "REVEAL" => Ok(Self::Reveal),
|
|
|
+ "I" | "IMPLICIT" => Ok(Self::Implicit),
|
|
|
+ "S" | "SET" => Ok(Self::Set),
|
|
|
+ "J" | "JOINT" => Ok(Self::Joint),
|
|
|
+ _ => Err(input.error("Unknown attribute spec for issued credential")),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// An attribute specification like "attr1: Reveal"
|
|
|
+#[derive(Clone)]
|
|
|
+struct AttrSpec<ShowOrIssue: Parse> {
|
|
|
+ attr: Ident,
|
|
|
+ spec: ShowOrIssue,
|
|
|
+}
|
|
|
+
|
|
|
+impl<ShowOrIssue: Parse> Parse for AttrSpec<ShowOrIssue> {
|
|
|
+ fn parse(input: ParseStream) -> Result<Self> {
|
|
|
+ let attr: Ident = input.parse()?;
|
|
|
+ input.parse::<Token![:]>()?;
|
|
|
+ let spec: ShowOrIssue = input.parse()?;
|
|
|
+ Ok(Self { attr, spec })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// A specification of a credential, either to be shown or issued
|
|
|
+#[derive(Debug)]
|
|
|
+struct CredSpec<ShowOrIssue: Parse> {
|
|
|
+ id: Ident,
|
|
|
+ cred_type: Ident,
|
|
|
+ attrs: HashMap<String, ShowOrIssue>,
|
|
|
+}
|
|
|
+
|
|
|
+impl<ShowOrIssue: Parse + Copy> Parse for CredSpec<ShowOrIssue> {
|
|
|
+ fn parse(input: ParseStream) -> Result<Self> {
|
|
|
+ let id: Ident = input.parse()?;
|
|
|
+ input.parse::<Token![:]>()?;
|
|
|
+ let cred_type: Ident = input.parse()?;
|
|
|
+ let content;
|
|
|
+ braced!(content in input);
|
|
|
+ let attrspecs: Punctuated<AttrSpec<ShowOrIssue>, Token![,]> =
|
|
|
+ content.parse_terminated(AttrSpec::<ShowOrIssue>::parse, Token![,])?;
|
|
|
+ let mut attrs: HashMap<String, ShowOrIssue> = HashMap::new();
|
|
|
+ for attrspec in attrspecs.iter() {
|
|
|
+ attrs.insert(attrspec.attr.to_string(), attrspec.spec);
|
|
|
+ }
|
|
|
+ Ok(Self {
|
|
|
+ id,
|
|
|
+ cred_type,
|
|
|
+ attrs,
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// A vector of credential specifications, which could be empty, a single
|
|
|
+// credential specification, or a bracketed list of credential
|
|
|
+// specifications. We need a newtype here and not just a Vec so that we
|
|
|
+// can implement the Parse trait for it.
|
|
|
+struct CredSpecVec<ShowOrIssue: Parse>(Vec<CredSpec<ShowOrIssue>>);
|
|
|
+
|
|
|
+impl<ShowOrIssue: Parse + Copy> Parse for CredSpecVec<ShowOrIssue> {
|
|
|
+ fn parse(input: ParseStream) -> Result<Self> {
|
|
|
+ let mut specvec: Vec<CredSpec<ShowOrIssue>> = Vec::new();
|
|
|
+ if input.peek(Token![,]) {
|
|
|
+ // The list is empty
|
|
|
+ } else if input.peek(token::Bracket) {
|
|
|
+ let content;
|
|
|
+ bracketed!(content in input);
|
|
|
+ let specs: Punctuated<CredSpec<ShowOrIssue>, Token![,]> =
|
|
|
+ content.parse_terminated(CredSpec::<ShowOrIssue>::parse, Token![,])?;
|
|
|
+ for spec in specs.into_iter() {
|
|
|
+ specvec.push(spec);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ let spec: CredSpec<ShowOrIssue> = input.parse()?;
|
|
|
+ specvec.push(spec);
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(Self(specvec))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// A protocol specification, following the syntax described above.
|
|
|
+#[derive(Debug)]
|
|
|
+struct ProtoSpec {
|
|
|
+ proto_name: Ident,
|
|
|
+ show_creds: Vec<CredSpec<ShowSpec>>,
|
|
|
+ issue_creds: Vec<CredSpec<IssueSpec>>,
|
|
|
+ statements: Vec<Expr>,
|
|
|
+}
|
|
|
+
|
|
|
+impl Parse for ProtoSpec {
|
|
|
+ fn parse(input: ParseStream) -> Result<Self> {
|
|
|
+ let proto_name: Ident = input.parse()?;
|
|
|
+ input.parse::<Token![,]>()?;
|
|
|
+ let showvec: CredSpecVec<ShowSpec> = input.parse()?;
|
|
|
+ input.parse::<Token![,]>()?;
|
|
|
+ let issuevec: CredSpecVec<IssueSpec> = input.parse()?;
|
|
|
+ input.parse::<Token![,]>()?;
|
|
|
+ let statementpunc: Punctuated<Expr, Token![,]> =
|
|
|
+ input.parse_terminated(Expr::parse, Token![,])?;
|
|
|
+ let mut statements: Vec<Expr> = Vec::new();
|
|
|
+ for statement in statementpunc.into_iter() {
|
|
|
+ statements.push(statement);
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(ProtoSpec {
|
|
|
+ proto_name,
|
|
|
+ show_creds: showvec.0,
|
|
|
+ issue_creds: issuevec.0,
|
|
|
+ statements,
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// This is where the main work is done. The six macros in the
|
|
|
+// CMZProtocol macro family (below) all call this function, with
|
|
|
+// different values for the bools.
|
|
|
+fn protocol_macro(
|
|
|
+ input: TokenStream,
|
|
|
+ use_muCMZ: bool,
|
|
|
+ emit_client: bool,
|
|
|
+ emit_issuer: bool,
|
|
|
+) -> TokenStream {
|
|
|
+ let proto_spec: ProtoSpec = parse_macro_input!(input as ProtoSpec);
|
|
|
+ // For now, just return a string representation of the parsed
|
|
|
+ // protcol spec.
|
|
|
+ let s = format!("{proto_spec:#?}");
|
|
|
+ quote! {
|
|
|
+ { #s }
|
|
|
+ }
|
|
|
+ .into()
|
|
|
+}
|
|
|
+
|
|
|
+/** There are six variants of the CMZProtocol macro. The ones starting
|
|
|
+ with "CMZ" create protocol implementations using the original CMZ
|
|
|
+ issuing protocol. The ones starting with "muCMZ" using the more
|
|
|
+ efficient µCMZ protocol. The ones with "Cli" only create the code
|
|
|
+ for the client side of the protocol. The ones with "Iss" only create
|
|
|
+ the code for the issuer side of the protocol. (The ones without
|
|
|
+ either create the code for both sides of the protocol.)
|
|
|
+*/
|
|
|
+#[proc_macro]
|
|
|
+pub fn CMZProtocol(input: TokenStream) -> TokenStream {
|
|
|
+ protocol_macro(input, false, true, true)
|
|
|
+}
|
|
|
+
|
|
|
+#[proc_macro]
|
|
|
+pub fn CMZCliProtocol(input: TokenStream) -> TokenStream {
|
|
|
+ protocol_macro(input, false, true, false)
|
|
|
+}
|
|
|
+
|
|
|
+#[proc_macro]
|
|
|
+pub fn CMZIssProtocol(input: TokenStream) -> TokenStream {
|
|
|
+ protocol_macro(input, false, false, true)
|
|
|
+}
|
|
|
+
|
|
|
+#[proc_macro]
|
|
|
+pub fn muCMZProtocol(input: TokenStream) -> TokenStream {
|
|
|
+ protocol_macro(input, true, true, true)
|
|
|
+}
|
|
|
+
|
|
|
+#[proc_macro]
|
|
|
+pub fn muCMZCliProtocol(input: TokenStream) -> TokenStream {
|
|
|
+ protocol_macro(input, true, true, false)
|
|
|
+}
|
|
|
+
|
|
|
+#[proc_macro]
|
|
|
+pub fn muCMZIssProtocol(input: TokenStream) -> TokenStream {
|
|
|
+ protocol_macro(input, true, false, true)
|
|
|
+}
|