Skip to main content

spongefish_derive/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use proc_macro::TokenStream;
4use proc_macro2::TokenStream as TokenStream2;
5use quote::quote;
6use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Member, Result, Type};
7
8/// One field of the deriving struct, with the member it is reached by.
9///
10/// [`Member`] is what lets named and tuple structs share every code path in
11/// this crate. `self.#member` reads `self.field` and `self.0` alike, and braced
12/// construction — `Self { #member: .. }` — builds a tuple struct as readily as
13/// a named one. The field-less case falls out for free: `Self {}` constructs a
14/// unit struct.
15struct StructField<'a> {
16    member: Member,
17    ty: &'a Type,
18    /// Set by `#[spongefish(skip)]`: the field is left out of every map, and
19    /// filled with `Default::default()` on the way back.
20    skip: bool,
21}
22
23/// The deriving struct's fields, in declaration order.
24///
25/// Errors if the input is not a struct, or if a field carries a malformed
26/// `#[spongefish(..)]`.
27fn struct_fields<'a>(input: &'a DeriveInput, derive: &str) -> Result<Vec<StructField<'a>>> {
28    let Data::Struct(data) = &input.data else {
29        return Err(syn::Error::new_spanned(
30            &input.ident,
31            format!("{derive} can only be derived for structs"),
32        ));
33    };
34
35    data.fields
36        .iter()
37        .enumerate()
38        .map(|(index, field)| {
39            let member = field
40                .ident
41                .clone()
42                .map_or_else(|| Member::Unnamed(syn::Index::from(index)), Member::Named);
43            Ok(StructField {
44                member,
45                ty: &field.ty,
46                skip: has_skip_attribute(&field.attrs)?,
47            })
48        })
49        .collect()
50}
51
52/// The types that must be bounded for an impl to hold: every field that
53/// the generated maps actually touch, and no others.
54///
55/// A skipped field is untouched by the generated code, so bounding it would
56/// reject types the derive can perfectly well handle.
57fn bounded_types<'a>(fields: &'a [StructField<'a>]) -> Vec<&'a Type> {
58    fields
59        .iter()
60        .filter(|field| !field.skip)
61        .map(|field| field.ty)
62        .collect()
63}
64
65/// The types that need `Default` because skipped fields are constructed with
66/// `Default::default()` by the generated decoding implementations.
67fn defaulted_types<'a>(fields: &'a [StructField<'a>]) -> Vec<&'a Type> {
68    fields
69        .iter()
70        .filter(|field| field.skip)
71        .map(|field| field.ty)
72        .collect()
73}
74
75/// Wrap `items` in an impl of `trait_path` for the deriving type, adding a
76/// `#ty: #trait_path` predicate for each of `bounded`.
77fn impl_block(
78    input: &DeriveInput,
79    trait_path: &TokenStream2,
80    bounded: &[&Type],
81    defaulted: &[&Type],
82    items: &TokenStream2,
83) -> TokenStream2 {
84    let name = &input.ident;
85    let mut generics = input.generics.clone();
86
87    if !bounded.is_empty() {
88        let where_clause = generics.make_where_clause();
89        for ty in bounded {
90            where_clause.predicates.push(parse_quote!(#ty: #trait_path));
91        }
92    }
93    if !defaulted.is_empty() {
94        let where_clause = generics.make_where_clause();
95        for ty in defaulted {
96            where_clause
97                .predicates
98                .push(parse_quote!(#ty: ::core::default::Default));
99        }
100    }
101
102    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
103    quote! {
104        impl #impl_generics #trait_path for #name #ty_generics #where_clause {
105            #items
106        }
107    }
108}
109
110/// The compile-time width reserved for a field inside the derived `Repr`.
111///
112/// `FromUniform` exposes no associated constant for the length of its `Repr`, and
113/// `AsMut::as_mut(..).len()` — the width the sponge actually fills — is not a
114/// const expression, so the buffer size must be spelled with `size_of`. The
115/// generated `from_uniform` checks at run time that the two agree (see
116/// [`from_uniform_field_expr`]).
117fn field_repr_size(field_type: &Type) -> TokenStream2 {
118    quote! {
119        ::core::mem::size_of::<<#field_type as ::spongefish::FromUniform>::Repr>()
120    }
121}
122
123/// Sample one field out of `bytes`, advancing the shared `offset` cursor.
124///
125/// The width comes from `AsMut::<[u8]>::as_mut(..).len()`.
126///
127/// # Panics
128///
129/// The bounds check will panic if `Repr` is a slice whose length disagrees with its `size_of`.
130fn from_uniform_field_expr(field_type: &Type) -> TokenStream2 {
131    quote! {
132        {
133            let mut field_buf = <#field_type as ::spongefish::FromUniform>::Repr::default();
134            let field_size = ::core::convert::AsMut::<[u8]>::as_mut(&mut field_buf).len();
135            let start = offset;
136            let end = start + field_size;
137            assert!(
138                end <= bytes.len(),
139                "`FromUniform` derive: field representation is wider than the derived buffer; \
140                 `Repr` must satisfy `size_of::<Repr>() == Repr::default().as_mut().len()`"
141            );
142            ::core::convert::AsMut::<[u8]>::as_mut(&mut field_buf)
143                .copy_from_slice(&bytes[start..end]);
144            offset = end;
145            <#field_type as ::spongefish::FromUniform>::from_uniform(field_buf)
146        }
147    }
148}
149
150fn generate_encoding_impl(input: &DeriveInput) -> Result<TokenStream2> {
151    let fields = struct_fields(input, "Encoding")?;
152    let bounded = bounded_types(&fields);
153
154    let field_encodings = fields.iter().filter(|field| !field.skip).map(|field| {
155        let member = &field.member;
156        quote! {
157            output.extend_from_slice(self.#member.encode().as_ref());
158        }
159    });
160
161    let trait_path = quote!(::spongefish::Encoding);
162    Ok(impl_block(
163        input,
164        &trait_path,
165        &bounded,
166        &[],
167        &quote! {
168            fn encode(&self) -> impl AsRef<[u8]> {
169                // Sized up front from the struct's own width. That is only a
170                // hint — a field whose encoding is wider or narrower than
171                // its in-memory size just makes the vector grow or over-
172                // reserve — but for the fixed-width codecs that carry
173                // prover messages it is exact, which turns several
174                // reallocations per message into one allocation.
175                let mut output = ::spongefish::__private::Vec::with_capacity(
176                    ::core::mem::size_of::<Self>(),
177                );
178                #(#field_encodings)*
179                output
180            }
181        },
182    ))
183}
184
185fn generate_from_uniform_impl(input: &DeriveInput) -> Result<TokenStream2> {
186    let fields = struct_fields(input, "FromUniform")?;
187    let bounded = bounded_types(&fields);
188    let defaulted = defaulted_types(&fields);
189
190    let field_inits = fields.iter().map(|field| {
191        let member = &field.member;
192        if field.skip {
193            return quote!(#member: Default::default(),);
194        }
195        let from_uniform_field = from_uniform_field_expr(field.ty);
196        quote!(#member: #from_uniform_field,)
197    });
198
199    let size_components = bounded.iter().copied().map(field_repr_size);
200    let size_calc = if bounded.is_empty() {
201        quote!(0usize)
202    } else {
203        quote!(#(#size_components)+*)
204    };
205
206    // Fields are read in declaration order from a single `offset` cursor, so
207    // the offsets and the buffer size can never drift apart silently: the final
208    // check pins the total to the buffer length.
209    let body = if bounded.is_empty() {
210        quote! {
211            let _ = buf;
212            Self { #(#field_inits)* }
213        }
214    } else {
215        quote! {
216            // Keep the preimage in its wiping owner instead of copying it
217            // into an unprotected array on the stack.
218            let bytes = buf.as_ref();
219            let mut offset = 0usize;
220            let value = Self { #(#field_inits)* };
221            assert_eq!(
222                offset,
223                bytes.len(),
224                "`FromUniform` derive: field representations do not cover the derived buffer; \
225                 every `Repr` must satisfy `size_of::<Repr>() == Repr::default().as_mut().len()`"
226            );
227            value
228        }
229    };
230
231    let trait_path = quote!(::spongefish::FromUniform);
232    Ok(impl_block(
233        input,
234        &trait_path,
235        &bounded,
236        &defaulted,
237        &quote! {
238            type Repr = ::spongefish::ByteArray<{ #size_calc }>;
239
240            fn from_uniform(buf: Self::Repr) -> Self {
241                #body
242            }
243        },
244    ))
245}
246
247fn generate_from_narg_impl(input: &DeriveInput) -> Result<TokenStream2> {
248    let fields = struct_fields(input, "FromNarg")?;
249    let bounded = bounded_types(&fields);
250    let defaulted = defaulted_types(&fields);
251
252    let field_inits = fields.iter().map(|field| {
253        let member = &field.member;
254        if field.skip {
255            return quote!(#member: Default::default(),);
256        }
257        let field_type = field.ty;
258        quote! {
259            #member: ::spongefish::NargReader::read::<#field_type>(reader)?,
260        }
261    });
262
263    let trait_path = quote!(::spongefish::FromNarg);
264    Ok(impl_block(
265        input,
266        &trait_path,
267        &bounded,
268        &defaulted,
269        &quote! {
270            fn from_narg(
271                reader: &::spongefish::NargReader<'_>,
272            ) -> ::core::result::Result<Self, ::spongefish::VerificationError> {
273                // A unit struct, or one whose every field is skipped,
274                // reads nothing at all.
275                let _ = &reader;
276                Ok(Self { #(#field_inits)* })
277            }
278        },
279    ))
280}
281
282fn generate_unit_impl(input: &DeriveInput) -> Result<TokenStream2> {
283    let fields = struct_fields(input, "Unit")?;
284    let bounded = bounded_types(&fields);
285
286    let zero_fields = fields.iter().map(|field| {
287        let member = &field.member;
288        if field.skip {
289            return quote!(#member: ::core::default::Default::default(),);
290        }
291        let field_type = field.ty;
292        quote!(#member: <#field_type as ::spongefish::Unit>::ZERO,)
293    });
294
295    let trait_path = quote!(::spongefish::Unit);
296    Ok(impl_block(
297        input,
298        &trait_path,
299        &bounded,
300        &[],
301        &quote! {
302            const ZERO: Self = Self { #(#zero_fields)* };
303        },
304    ))
305}
306
307/// Turn a generated impl into tokens, reporting failure as a `compile_error!`
308/// on the offending span rather than as a proc-macro panic.
309fn expand(generated: Result<TokenStream2>) -> TokenStream {
310    TokenStream::from(generated.unwrap_or_else(syn::Error::into_compile_error))
311}
312
313/// Derive [`Encoding`](https://docs.rs/spongefish/latest/spongefish/trait.Encoding.html) for structs.
314///
315/// Fields marked with `#[spongefish(skip)]` are omitted from the encoding.
316/// Any other `#[spongefish(..)]` form is a compile error.
317///
318/// # Security
319///
320/// Skip only values that are recomputable, or genuinely irrelevant to the statement being proven.
321///
322/// A skipped field is not bound by the Fiat-Shamir transformation, and therefore is never part of
323/// the inputs to the random oracle, nor of the NARG string.
324///
325/// ```
326/// use spongefish::Encoding;
327/// # use spongefish_derive::Encoding;
328///
329/// #[derive(Encoding)]
330/// struct Rgb {
331///     r: u8,
332///     g: u8,
333///     b: u8,
334/// }
335///
336/// let colors = Rgb { r: 1, g: 2, b: 3 };
337/// let data = colors.encode();
338/// assert_eq!(data.as_ref(), [1, 2, 3]);
339/// ```
340#[proc_macro_derive(Encoding, attributes(spongefish))]
341pub fn derive_encoding(input: TokenStream) -> TokenStream {
342    let input = parse_macro_input!(input as DeriveInput);
343    expand(generate_encoding_impl(&input))
344}
345
346/// Derive macro for the [`FromUniform`](https://docs.rs/spongefish/latest/spongefish/trait.FromUniform.html) trait.
347///
348/// Generate an implementation that samples struct fields sequentially from a fixed-size buffer.
349/// Fields can be skipped using `#[spongefish(skip)]`.
350#[proc_macro_derive(FromUniform, attributes(spongefish))]
351pub fn derive_from_uniform(input: TokenStream) -> TokenStream {
352    let input = parse_macro_input!(input as DeriveInput);
353    expand(generate_from_uniform_impl(&input))
354}
355
356/// Derive macro for the [`FromNarg`](https://docs.rs/spongefish/latest/spongefish/trait.FromNarg.html) trait.
357///
358/// Generate an implementation that parses struct fields sequentially from the NARG string.
359/// Fields can be skipped using `#[spongefish(skip)]`.
360/// Parsing returns `VerificationError` on failure; use `NargReader::read` to
361/// reject poisoned readers and automatically record nested field errors.
362#[proc_macro_derive(FromNarg, attributes(spongefish))]
363pub fn derive_from_narg(input: TokenStream) -> TokenStream {
364    let input = parse_macro_input!(input as DeriveInput);
365    expand(generate_from_narg_impl(&input))
366}
367
368/// Derive macro that generates [`Encoding`](https://docs.rs/spongefish/latest/spongefish/trait.Encoding.html),
369/// [`FromUniform`](https://docs.rs/spongefish/latest/spongefish/trait.FromUniform.html), and
370/// [`FromNarg`](https://docs.rs/spongefish/latest/spongefish/trait.FromNarg.html) in one go.
371#[proc_macro_derive(Codec, attributes(spongefish))]
372pub fn derive_codec(input: TokenStream) -> TokenStream {
373    let input = parse_macro_input!(input as DeriveInput);
374    expand((|| {
375        let encoding = generate_encoding_impl(&input)?;
376        let from_uniform = generate_from_uniform_impl(&input)?;
377        let from_narg = generate_from_narg_impl(&input)?;
378        Ok(quote! {
379            #encoding
380            #from_uniform
381            #from_narg
382        })
383    })())
384}
385
386/// Derive [`Unit`](https://docs.rs/spongefish/latest/spongefish/trait.Unit.html) for structs.
387///
388/// ```
389/// use spongefish::Unit;
390/// # use spongefish_derive::Unit;
391///
392/// #[derive(Clone, Unit)]
393/// struct Rgb {
394///     r: u8,
395///     g: u8,
396///     b: u8,
397/// }
398///
399/// assert_eq!((Rgb::ZERO.r, Rgb::ZERO.g, Rgb::ZERO.b), (0, 0, 0));
400/// ```
401#[proc_macro_derive(Unit, attributes(spongefish))]
402pub fn derive_unit(input: TokenStream) -> TokenStream {
403    let input = parse_macro_input!(input as DeriveInput);
404    expand(generate_unit_impl(&input))
405}
406
407/// Whether a field carries `#[spongefish(skip)]`.
408///
409/// # Errors
410///
411/// Any `#[spongefish(..)]` that is not exactly `skip` is a compile error in the
412/// deriving crate, reported on the attribute itself.
413///
414/// # Security
415///
416/// Dropping a field from the encoding map is security-relevant, so it must be
417/// spelled out rather than inferred from an attribute that failed to parse:
418/// `#[spongefish()]` parses zero nested metas, and a misspelling like
419/// `#[spongefish(skpi)]` names no option at all. Neither may be read as "skip",
420/// and neither may be silently ignored. See [`spongefish::Encoding`].
421fn has_skip_attribute(attrs: &[syn::Attribute]) -> Result<bool> {
422    for attr in attrs {
423        if !attr.path().is_ident("spongefish") {
424            continue;
425        }
426
427        let mut skip = false;
428        attr.parse_nested_meta(|meta| {
429            if meta.path.is_ident("skip") {
430                skip = true;
431                Ok(())
432            } else {
433                Err(meta.error("unknown `spongefish` option; expected `skip`"))
434            }
435        })?;
436
437        if !skip {
438            return Err(syn::Error::new_spanned(
439                attr,
440                "empty `#[spongefish(..)]`: write `#[spongefish(skip)]` or remove the attribute",
441            ));
442        }
443        return Ok(true);
444    }
445
446    Ok(false)
447}