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}