extra_safe/
lib.rs

1#![warn(
2    missing_debug_implementations,
3    missing_docs,
4    rust_2018_idioms,
5    unreachable_pub
6)]
7#![deny(rustdoc::broken_intra_doc_links)]
8#![cfg_attr(docsrs, feature(doc_cfg))]
9#![doc = include_str!("../README.md")]
10
11//! This crate offers a method and a set of traits that lifts the errors produces by the Sponge API at runtime.
12//! To read more about the Sponge API, you can read the Spec at [this link][1].
13//!
14//! [1]: https://hackmd.io/bHgsH6mMStCVibM_wYvb2w#SAFE-Sponge-API-for-Field-Elements-%E2%80%93-A-Toolbox-for-ZK-Hash-Applications
15
16pub mod traits;
17
18use std::iter;
19use std::marker::PhantomData;
20
21use hybrid_array::{Array, ArraySize};
22use traits::{Absorb, Cons, Consume, IOWord, List, Nil, Norm, Normalize, Squeeze, Use};
23use typenum::Unsigned;
24
25/// The Error returned at runtime by the sponge API in case the finalize operation fails.
26#[derive(Debug)]
27pub enum Error {
28    /// Error returned when the sponge is not in a state where it can be finalized.
29    ParameterUsageMismatch,
30}
31
32/// The SpongeWord type is lifted straight from the Neptune codebase.
33/// See `<https://github.com/filecoin-project/neptune/blob/master/src/sponge/api.rs>`
34#[derive(Clone, Copy, Debug, PartialEq)]
35pub enum SpongeOp {
36    /// The absorb operation.
37    Absorb(u32),
38    /// The squeeze operation.
39    Squeeze(u32),
40}
41
42/// Conversion from a type-level IOWord to a crate::SpongeOp
43/// This is, morally speaking, an extension trait of the IOWord trait,
44/// though Rust can of course not check exhaustivity.
45pub trait ToSpongeOp: IOWord {
46    /// Converts the type-level operation to its term-level representation
47    fn to_sponge_op() -> SpongeOp;
48}
49
50impl<U: Unsigned> ToSpongeOp for Absorb<U> {
51    fn to_sponge_op() -> SpongeOp {
52        SpongeOp::Absorb(U::to_u32())
53    }
54}
55
56impl<U: Unsigned> ToSpongeOp for Squeeze<U> {
57    fn to_sponge_op() -> SpongeOp {
58        SpongeOp::Squeeze(U::to_u32())
59    }
60}
61
62/// The type describing the I/O pattern of a sponge, at a term level.
63#[derive(Clone, Debug, PartialEq)]
64pub struct IOPattern(pub Vec<SpongeOp>);
65
66// TODO : convert SpongeOp -> IOWord using macros
67
68/// Conversion from a trait::List type-level IOPattern to a crate::IOpattern
69/// This is morally an extension trait of the List trait, though Rust can of
70/// course not check exhaustivity.
71pub trait ToIOPattern {
72    /// Converts the type-level pattern to its term-level representation
73    fn to_iopattern() -> IOPattern;
74}
75
76impl ToIOPattern for Nil {
77    fn to_iopattern() -> IOPattern {
78        IOPattern(vec![])
79    }
80}
81impl<Item: ToSpongeOp, T: List + ToIOPattern> ToIOPattern for Cons<Item, T> {
82    fn to_iopattern() -> IOPattern {
83        // TODO: avoid the quadratic cost of prepending here
84        IOPattern(
85            iter::once(<Item as ToSpongeOp>::to_sponge_op())
86                .chain(T::to_iopattern().0)
87                .collect(),
88        )
89    }
90}
91
92/// This is the SpongeAPI trait as you can find it in Neptune,
93/// see `<https://github.com/filecoin-project/neptune/blob/master/src/sponge/api.rs>`
94/// Slightly modified so that the squeeze function takes an argument as a mutable slice
95/// instead of returning a Vec.
96pub trait SpongeAPI {
97    /// The type of the sponge state
98    type Acc;
99    /// The type of the elements froming the I/O of the sponge
100    type Value;
101
102    /// This initializes the internal state of the sponge, modifying up to c/2
103    /// field elements of the state. It’s done once in the lifetime of a sponge.
104    fn start(&mut self, p: IOPattern, domain_separator: Option<u32>, acc: &mut Self::Acc);
105
106    /// This injects `length` field elements to the state from the array `elements`, interleaving calls to the permutation
107    /// It also checks if the current call matches the IO pattern.
108    fn absorb(&mut self, length: u32, elements: &[Self::Value], acc: &mut Self::Acc);
109
110    /// This extracts `length` field elements from the state to the array `elements`, interleaving calls to the permutation
111    /// It also checks if the current call matches the IO pattern.
112    // This differs from the original API in that it takes a mutable slice instead of returning a Vec.
113    fn squeeze(&mut self, length: u32, elements: &mut [Self::Value], acc: &mut Self::Acc);
114
115    /// This marks the end of the sponge life, preventing any further operation.
116    /// In particular, the state is erased from memory. The result is OK, or an error
117    // This differs from the original API in that if does not take a final Self::Acc argument.
118    // It would not be impossible to do it without this change, but it would require depending on something other
119    // than the Drop impelementation to detect the ExtraSponge going out of scope (e.g. MIRAI).
120    fn finish(&mut self) -> Result<(), Error>;
121}
122
123/// This is a slightly extended generic NewType wrapper around the original SpongeAPI.
124/// It is decorated with the IOPattern I intended for this sponge instance.
125#[derive(Debug)]
126pub struct ExtraSponge<A: SpongeAPI, I: List> {
127    api: A,
128    _current_pattern: PhantomData<I>,
129}
130
131impl<A: SpongeAPI, I: List> ExtraSponge<A, I> {
132    // This is the internal constructor for the ExtraSponge type: a simple wrapper, which needs type annotations
133    // to be used properly. This should remain private.
134    fn new(api: A) -> ExtraSponge<A, I> {
135        ExtraSponge {
136            api,
137            _current_pattern: PhantomData,
138        }
139    }
140
141    // This allows reinterpreting the type decorator of an ExtraSponge<A, I> into
142    // an ExtraSponge<A, J> where J is another pattern.
143    // Safety: this should stay private to ensure it is only used in the below.
144    fn repattern<J: List>(self) -> ExtraSponge<A, J> {
145        // Mandated by the existence of a Drop implementation which we cannot move out of.
146        // Safe since the only type that differs between source and destination is a Phantom
147        let res =
148            unsafe { std::mem::transmute_copy::<ExtraSponge<A, I>, ExtraSponge<A, J>>(&self) };
149        // This is really important, as it lets us bypass the drop logic, which would blow up in a
150        // non-empty Sponge.
151        std::mem::forget(self);
152        res
153    }
154}
155
156impl<A: SpongeAPI, I: Normalize> ExtraSponge<A, I>
157where
158    Norm<I>: ToIOPattern, // Satisfied in all cases
159{
160    /// Creates a sponge with the IOPatten given as a type parameter.
161    /// Note that we do not require this pattern to be normalized - instead the constructor will return
162    /// an ExtraSPonge with a normalized pattern.
163    pub fn start(
164        domain_separator: Option<u32>,
165        api: A,
166        acc: &mut A::Acc,
167    ) -> ExtraSponge<A, Norm<I>> {
168        // Note: we not directly creating the state on I but on its normalization, satifying the requirement
169        // in subsequent calls to absorb and squeeze - the pattern, by then, will be in normalized form and these calls
170        // will maintain it as such.
171        let mut extra_sponge: ExtraSponge<A, Norm<I>> = ExtraSponge::new(api);
172        extra_sponge
173            .api
174            .start(Norm::<I>::to_iopattern(), domain_separator, acc);
175        extra_sponge
176    }
177}
178
179impl<A: SpongeAPI, I: Normalize> ExtraSponge<A, I> {
180    /// This pass-through function is used to absorb elements in the sponge.
181    /// It calls the underlying API's absorb function, and then returns a new ExtraSponge
182    /// but a successful method dispatch to this implementation gaurantees the call is coherent with
183    /// the IOPattern.
184    pub fn absorb<U>(
185        mut self,
186        harray: Array<A::Value, U>,
187        acc: &mut A::Acc,
188    ) -> ExtraSponge<A, Use<I, Absorb<U>>>
189    where
190        U: ArraySize<A::Value>,
191        I: Consume<Absorb<U>>,
192    {
193        self.api.absorb(U::to_u32(), harray.as_slice(), acc);
194        self.repattern()
195    }
196}
197
198impl<A: SpongeAPI, I: Normalize> ExtraSponge<A, I> {
199    /// This pass-through function is used to squeeze elements out of the sponge.
200    /// It calls the underlying API's squeeze function, and then returns a new ExtraSponge
201    /// but a successful method dispatch to this implementation gaurantees the call is coherent with
202    /// the IOPattern.
203    pub fn squeeze<U>(
204        mut self,
205        harray: &mut Array<A::Value, U>,
206        acc: &mut A::Acc,
207    ) -> ExtraSponge<A, Use<I, Squeeze<U>>>
208    where
209        U: ArraySize<A::Value>,
210        I: Consume<Squeeze<U>>,
211    {
212        self.api.squeeze(U::to_u32(), harray.as_mut_slice(), acc);
213        self.repattern()
214    }
215}
216
217/// This implementation of drop is called automatically when the ExtraSponge drops out of scope.
218/// It checks that the IOPattern is empty by then, and if it is not, it panics. Otherwise, it calls finalize.
219impl<A: SpongeAPI, I: List> Drop for ExtraSponge<A, I> {
220    fn drop(&mut self) {
221        if I::is_empty() {
222            self.api
223                .finish()
224                .expect("SpongeAPI invariant violated: finish failed on an empty IO pattern");
225        } else {
226            // TODO: find a better behavior than a panic, here: this induces aborts!
227            panic!("SpongeAPI invariant violated: forgot to empty IO pattern before dropping it");
228        }
229    }
230}
231
232#[cfg(test)]
233pub mod unit_tests;
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238    use typenum::{U3, U5};
239
240    #[test]
241    fn test_to_sponge_op() {
242        assert_eq!(Absorb::<U5>::to_sponge_op(), SpongeOp::Absorb(5));
243        assert_eq!(Squeeze::<U5>::to_sponge_op(), SpongeOp::Squeeze(5));
244    }
245
246    #[test]
247    fn test_to_iopattern() {
248        assert_eq!(Nil::to_iopattern(), IOPattern(Vec::default()));
249        assert_eq!(
250            <iopat![Absorb::<U5>, Squeeze<U3>]>::to_iopattern(),
251            IOPattern(vec![SpongeOp::Absorb(5), SpongeOp::Squeeze(3)])
252        );
253    }
254}