Skip to main content

p3_symmetric/
hasher.rs

1/// A generic trait for cryptographic hashers that consume an arbitrary sequence of input items
2/// and produce a fixed-size output.
3///
4/// This trait abstracts over hash functions in a flexible way, supporting both field elements,
5/// scalars, or any other data type that implements `Clone`.
6pub trait CryptographicHasher<Item: Clone, Out>: Clone {
7    /// Number of equal-length messages this implementation hashes most efficiently in one call.
8    ///
9    /// One means every message is hashed on its own, which is the behaviour of a plain scalar sponge.
10    /// A vectorized implementation reports how many independent states its permutation advances at once.
11    ///
12    /// Callers read this only to decide whether grouping messages is worth the bookkeeping.
13    const LANES: usize = 1;
14
15    /// Whether materializing a compound input iterator can improve hashing throughput.
16    ///
17    /// Callers concatenating disjoint inputs may use this hint to reuse a contiguous
18    /// staging buffer and pass its simple iterator to [`Self::hash_iter`]. It is not
19    /// a request to copy inputs that already have an efficient iterator, nor does it
20    /// affect the digest: the input sequence and message boundaries must stay identical.
21    const PREFER_CONTIGUOUS_INPUT: bool = false;
22
23    /// Hash an iterator of input items.
24    /// # Arguments
25    /// - `input`: An iterator over items to be hashed.
26    ///
27    /// # Returns
28    /// A fixed-size digest of type `Out`.
29    fn hash_iter<I>(&self, input: I) -> Out
30    where
31        I: IntoIterator<Item = Item>;
32
33    /// Hash an iterator of slices, by flattening it into a single stream of items.
34    ///
35    /// # Arguments
36    /// - `input`: An iterator over slices of items to hash.
37    ///
38    /// # Returns
39    /// A fixed-size digest of type `Out`.
40    fn hash_iter_slices<'a, I>(&self, input: I) -> Out
41    where
42        I: IntoIterator<Item = &'a [Item]>,
43        Item: 'a,
44    {
45        self.hash_iter(input.into_iter().flatten().cloned())
46    }
47
48    /// Hash a single slice of items.
49    ///
50    /// # Arguments
51    /// - `input`: A slice of items to hash.
52    ///
53    /// # Returns
54    /// A fixed-size digest of type `Out`.
55    fn hash_slice(&self, input: &[Item]) -> Out {
56        self.hash_iter_slices(core::iter::once(input))
57    }
58
59    /// Hash a single item.
60    ///
61    /// # Arguments
62    /// - `input`: A single item to hash.
63    ///
64    /// # Returns
65    /// A fixed-size digest of type `Out`.
66    fn hash_item(&self, input: Item) -> Out {
67        self.hash_slice(&[input])
68    }
69
70    /// Hash a batch of equal-length messages, one digest per message.
71    ///
72    /// All messages sit back to back in a single slice.
73    /// The common message length is the input length divided by the digest count:
74    ///
75    /// ```text
76    ///     input: [ msg_0 | msg_1 | ... | msg_{m-1} ]   m * len items
77    ///     out:   [ dig_0 | dig_1 | ... | dig_{m-1} ]   m digests
78    /// ```
79    ///
80    /// The default hashes the messages one at a time.
81    /// An override exists purely to exploit vector hardware and must return the very same digests.
82    ///
83    /// # Panics
84    ///
85    /// Panics if the batch is ragged: the input length must be a whole multiple of the digest count.
86    fn hash_many(&self, input: &[Item], out: &mut [Out]) {
87        // No digests requested means there is nothing to read from the input.
88        if out.is_empty() {
89            return;
90        }
91
92        // Every message has the same length, so the split is exact by contract.
93        assert!(
94            input.len().is_multiple_of(out.len()),
95            "input length ({}) must be a whole multiple of the digest count ({})",
96            input.len(),
97            out.len()
98        );
99        let len = input.len() / out.len();
100
101        // Zero-length messages all hash to the same digest.
102        // They are also the one case a chunked walk over the input cannot express.
103        if len == 0 {
104            for digest in out.iter_mut() {
105                *digest = self.hash_slice(&[]);
106            }
107            return;
108        }
109
110        // Walk the messages in order so the digests land in the caller's order.
111        for (digest, message) in out.iter_mut().zip(input.chunks_exact(len)) {
112            *digest = self.hash_slice(message);
113        }
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use alloc::vec;
120    use core::array;
121
122    use crate::CryptographicHasher;
123
124    /// A byte hasher whose digest carries both a running fold and the message length.
125    ///
126    /// The multiplier makes the digest order sensitive.
127    /// The counter makes a message of a different length impossible to collide with.
128    #[derive(Clone)]
129    struct Fold;
130
131    impl CryptographicHasher<u8, [u8; 2]> for Fold {
132        fn hash_iter<I>(&self, input: I) -> [u8; 2]
133        where
134            I: IntoIterator<Item = u8>,
135        {
136            let mut acc = 0u8;
137            let mut count = 0u8;
138            for byte in input {
139                acc = acc.wrapping_mul(31).wrapping_add(byte);
140                count = count.wrapping_add(1);
141            }
142            [acc, count]
143        }
144    }
145
146    #[test]
147    fn hash_many_matches_hashing_each_message_alone() {
148        // Four messages of three bytes, laid out back to back.
149        let input: [u8; 12] = array::from_fn(|i| i as u8);
150
151        let mut batched = [[0u8; 2]; 4];
152        Fold.hash_many(&input, &mut batched);
153
154        let expected: [[u8; 2]; 4] = array::from_fn(|i| Fold.hash_slice(&input[3 * i..][..3]));
155        assert_eq!(batched, expected);
156    }
157
158    #[test]
159    fn hash_many_of_zero_length_messages_hashes_the_empty_message() {
160        // No input with digests requested means every message is empty.
161        let mut out = vec![[1u8; 2]; 3];
162        Fold.hash_many(&[], &mut out);
163
164        assert_eq!(out, vec![Fold.hash_slice(&[]); 3]);
165    }
166
167    #[test]
168    fn hash_many_reads_nothing_when_no_digests_are_requested() {
169        // A message length cannot be derived from zero digests, so the input is left untouched
170        // instead of tripping the multiple check on a length that divides nothing.
171        Fold.hash_many(&[1, 2, 3], &mut []);
172    }
173
174    #[test]
175    #[should_panic(expected = "must be a whole multiple")]
176    fn hash_many_rejects_ragged_input() {
177        // Five bytes cannot split into two equal messages.
178        let mut out = [[0u8; 2]; 2];
179        Fold.hash_many(&[1, 2, 3, 4, 5], &mut out);
180    }
181}