Skip to content

Commit

Permalink
Merge pull request #38 from EspressoSystems/feat/macros
Browse files Browse the repository at this point in the history
Move tagged_blob macro here from jf-utils
  • Loading branch information
jbearer authored Nov 16, 2022
2 parents 2b7c4c0 + 7c3e908 commit 388d116
Show file tree
Hide file tree
Showing 6 changed files with 339 additions and 34 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ jobs:
with:
token: ${{ github.token }}

- name: Clippy without default features
uses: actions-rs/clippy-check@v1
with:
token: ${{ github.token }}
args: --no-default-features

- name: Audit
uses: actions-rs/audit-check@v1
with:
Expand Down
14 changes: 11 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@ name = "tagged-base64"
required-features = ["build-cli"]

[features]
default = []
default = ["ark-serialize", "serde"]
ark-serialize = ["dep:ark-serialize"]
serde = ["dep:serde", "tagged-base64-macros/serde"]
wasm-debug = ["dep:console_error_panic_hook"]
build-cli = ["dep:clap"]

[dependencies]
crc-any = { version = "2.4.1", default-features = false }

ark-serialize = { version = "0.3.0", optional = true, default-features = false, features = ["derive"] }
base64 = "0.13.0"
crc-any = { version = "2.4.1", default-features = false }
serde = { version = "1.0", optional = true, features = ["derive"] }
snafu = { version = "0.7", features = ["backtraces"] }
tagged-base64-macros = { path = "macros", default-features = false }

# Command line argument processing
clap = { version = "4.0", optional = true, features = ["derive"] }
Expand All @@ -40,9 +45,12 @@ web-sys = { version = "0.3.49", optional = true, features = ["console", "Headers
console_error_panic_hook = { version = "0.1.7", optional = true }

[dev-dependencies]
ark-std = { version = "0.3.0", default-features = false }
bincode = "1.3"
getrandom = { version = "0.2", features = ["js"] }
quickcheck = "1.0"
quickcheck_macros = "1.0"
serde_json = "1.0"
wasm-bindgen-test = { version = "0.3.28" }

[profile.release]
Expand Down
26 changes: 26 additions & 0 deletions macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "tagged-base64-macros"
description = "Procedural macros associated with tagged-base64"
version = "0.2.0"
authors = ["Espresso Systems <[email protected]>"]
edition = "2018"

[lib]
proc-macro = true

[features]
default-features = ["serde"]
serde = []

[dependencies]
ark-std = { version = "0.3.0", default-features = false }
syn = { version = "1.0", features = ["extra-traits"] }
quote = "1.0"

[dev-dependencies]
ark-serialize = { version = "0.3.0", default-features = false, features = ["derive"] }
ark-bls12-381 = { version = "0.3.0", default-features = false, features = ["curve"] }
bincode = { version = "1.3.3", default-features = false }
rand_chacha = { version = "0.3.1" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.61"
164 changes: 164 additions & 0 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright (c) 2022 Espresso Systems (espressosys.com)
#![no_std]

extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, AttributeArgs, Item, Meta, NestedMeta};

/// Derive serdes for a type which serializes as a binary blob.
///
/// This macro can be used to easily derive friendly serde implementations for a binary type which
/// implements [CanonicalSerialize](ark_serialize::CanonicalSerialize) and
/// [CanonicalDeserialize](ark_serialize::CanonicalDeserialize). This is useful for cryptographic
/// primitives and other types which do not have a human-readable serialization, but which may be
/// embedded in structs with a human-readable serialization. The serde implementations derived by
/// this macro will serialize the type as bytes for binary encodings and as base 64 for human
/// readable encodings.
///
/// Specifically, this macro does 4 things when applied to a type definition:
/// * It adds `#[derive(Serialize, Deserialize)]` to the type definition, along with serde
/// attributes to serialize using [TaggedBase64].
/// * It creates an implementation of [Tagged] for the type using the specified tag. This tag will
/// be used to identify base 64 strings which represent this type in human-readable encodings.
/// * It creates an implementation of `TryFrom<TaggedBase64>` for the type `T`, which is needed to
/// make the `serde(try_from)` attribute work.
/// * It creates implementations of [Display](ark_std::fmt::Display) and
/// [FromStr](ark_std::str::FromStr) using tagged base 64 as a display format. This allows tagged
/// blob types to be conveniently displayed and read to and from user interfaces in a manner
/// consistent with how they are serialized.
///
/// Usage example:
///
/// ```
/// #[macro_use] extern crate tagged_base64_macros;
/// use ark_serialize::*;
///
/// #[tagged("PRIM")]
/// #[derive(Clone, CanonicalSerialize, CanonicalDeserialize, /* any other derives */)]
/// pub struct CryptoPrim(
/// // This type can only be serialied as an opaque, binary blob using ark_serialize.
/// pub(crate) ark_bls12_381::Fr,
/// );
/// ```
///
/// The type `CryptoPrim` can now be serialized as binary:
/// ```
/// # use ark_serialize::*;
/// # use ark_std::UniformRand;
/// # use tagged_base64_macros::tagged;
/// # use rand_chacha::{ChaChaRng, rand_core::SeedableRng};
/// # #[tagged("PRIM")]
/// # #[derive(Clone, CanonicalSerialize, CanonicalDeserialize, /* any other derives */)]
/// # struct CryptoPrim(ark_bls12_381::Fr);
/// # let crypto_prim = CryptoPrim(ark_bls12_381::Fr::rand(&mut ChaChaRng::from_seed([42; 32])));
/// bincode::serialize(&crypto_prim).unwrap();
/// ```
/// or as base64:
/// ```
/// # use ark_serialize::*;
/// # use ark_std::UniformRand;
/// # use tagged_base64_macros::tagged;
/// # use rand_chacha::{ChaChaRng, rand_core::SeedableRng};
/// # #[tagged("PRIM")]
/// # #[derive(Clone, CanonicalSerialize, CanonicalDeserialize, /* any other derives */)]
/// # struct CryptoPrim(ark_bls12_381::Fr);
/// # let crypto_prim = CryptoPrim(ark_bls12_381::Fr::rand(&mut ChaChaRng::from_seed([42; 32])));
/// serde_json::to_string(&crypto_prim).unwrap();
/// ```
/// which will produce a tagged base64 string like
/// "PRIM~8oaujwbov8h4eEq7HFpqW6mIXhVbtJGxLUgiKrGpMCoJ".
#[proc_macro_attribute]
pub fn tagged(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as AttributeArgs);
let input = parse_macro_input!(input as Item);
let (name, generics) = match &input {
Item::Struct(item) => (&item.ident, &item.generics),
Item::Enum(item) => (&item.ident, &item.generics),
_ => panic!("expected struct or enum"),
};
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let tag: &dyn quote::ToTokens = match args.as_slice() {
[NestedMeta::Lit(tag)] => tag,
[NestedMeta::Meta(Meta::Path(path))] => path,
x => panic!(
"`tagged` takes one argument, the tag, as a string literal or expression, found {:?}",
x
),
};

#[cfg(feature = "serde")]
let struct_def = quote! {
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(try_from = "tagged_base64::TaggedBase64", into = "tagged_base64::TaggedBase64")]
// Override the inferred bound for Serialize/Deserialize impls. If we're converting to and
// from CanonicalBytes as an intermediate, the impls should work for any generic parameters.
#[serde(bound = "")]
#input
};
#[cfg(not(feature = "serde"))]
let struct_def = &input;

let output = quote! {
#struct_def

impl #impl_generics tagged_base64::Tagged for #name #ty_generics #where_clause {
fn tag() -> ark_std::string::String {
ark_std::string::String::from(#tag)
}
}

impl #impl_generics core::convert::TryFrom<tagged_base64::TaggedBase64>
for #name #ty_generics
#where_clause
{
type Error = tagged_base64::Tb64Error;
fn try_from(t: tagged_base64::TaggedBase64) -> Result<Self, Self::Error> {
if t.tag() == <#name #ty_generics>::tag() {
<Self as CanonicalDeserialize>::deserialize(t.as_ref())
.map_err(|_| tagged_base64::Tb64Error::InvalidData)
} else {
Err(tagged_base64::Tb64Error::InvalidTag)
}
}
}

impl #impl_generics core::convert::From<#name #ty_generics> for tagged_base64::TaggedBase64
#where_clause
{
fn from(x: #name #ty_generics) -> Self {
(&x).into()
}
}

impl #impl_generics core::convert::From<&#name #ty_generics> for tagged_base64::TaggedBase64
#where_clause
{
fn from(x: &#name #ty_generics) -> Self {
let mut bytes = ark_std::vec![];
x.serialize(&mut bytes).unwrap();
Self::new(&<#name #ty_generics>::tag(), &bytes).unwrap()
}
}

impl #impl_generics ark_std::fmt::Display for #name #ty_generics #where_clause {
fn fmt(&self, f: &mut ark_std::fmt::Formatter<'_>) -> ark_std::fmt::Result {
ark_std::write!(
f, "{}",
tagged_base64::TaggedBase64::from(self)
)
}
}

impl #impl_generics ark_std::str::FromStr for #name #ty_generics #where_clause {
type Err = tagged_base64::Tb64Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use core::convert::TryFrom;
Self::try_from(tagged_base64::TaggedBase64::from_str(s)?)
.map_err(|_| tagged_base64::Tb64Error::InvalidData)
}
}
};
output.into()
}
Loading

0 comments on commit 388d116

Please sign in to comment.