tuple_hash/
hash.rs

1use generic_array::{ArrayLength, GenericArray};
2#[cfg(feature = "rust-crypto")]
3use sha3::{digest::core_api::CoreProxy, CShake128, CShake256};
4use sha3_utils::{encode_string, right_encode, right_encode_bytes};
5
6/// A extendable output function (XOF).
7pub trait Xof: Clone {
8    /// Reads output bytes.
9    type Reader: XofReader;
10
11    /// Creates a new XOF with the customization string `s`.
12    fn new(s: &[u8]) -> Self;
13
14    /// Updates the running hash with `data`.
15    fn update(&mut self, data: &[u8]);
16
17    /// Returns the output of the XOF.
18    fn finalize_xof(self) -> Self::Reader;
19
20    /// Writes the XOF output to `out`.
21    fn finalize_xof_into(self, out: &mut [u8]) {
22        self.finalize_xof().read(out);
23    }
24}
25
26/// Output bytes from an XOF.
27pub trait XofReader {
28    /// Reads output bytes from the XOF into `out`.
29    fn read(&mut self, out: &mut [u8]);
30
31    /// Reads `N` output bytes from the XOF into `out`.
32    fn read_n<N: ArrayLength>(&mut self) -> GenericArray<u8, N> {
33        let mut out = GenericArray::default();
34        self.read(&mut out);
35        out
36    }
37}
38
39#[cfg(feature = "rust-crypto")]
40#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
41impl<R> XofReader for R
42where
43    R: sha3::digest::XofReader,
44{
45    #[inline]
46    fn read(&mut self, out: &mut [u8]) {
47        sha3::digest::XofReader::read(self, out);
48    }
49}
50
51/// `TupleHash128`.
52///
53/// For the XOF variant, see [`TupleHashXof128`].
54#[cfg(feature = "rust-crypto")]
55#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
56pub type TupleHash128 = TupleHash<CShake128>;
57
58/// `TupleHash256`.
59///
60/// For the XOF variant, see [`TupleHashXof256`].
61#[cfg(feature = "rust-crypto")]
62#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
63pub type TupleHash256 = TupleHash<CShake256>;
64
65/// A cryptographic hash over a set of strings such that each
66/// string is unambiguously encoded.
67///
68/// For example, the TupleHash of `("abc", "d")` will produce
69/// a different hash value than the TupleHash of `("ab", "cd")`.
70///
71/// For the XOF variant, see [`TupleHashXof`].
72///
73/// # Warning
74///
75/// `TupleHash` is only defined for cSHAKE128 and cSHAKE256.
76/// Using this with a different XOF might have worse security
77/// properties.
78#[derive(Clone, Debug, Default)]
79pub struct TupleHash<X> {
80    xof: X,
81}
82
83impl<X: Xof> TupleHash<X> {
84    /// Creates a `TupleHash` with the customization string `s`.
85    pub fn new(s: &[u8]) -> Self {
86        Self { xof: X::new(s) }
87    }
88
89    /// Writes the string `s` to the hash.
90    pub fn update(&mut self, s: &[u8]) {
91        for x in &encode_string(s) {
92            self.xof.update(x);
93        }
94    }
95
96    /// Returns a fixed-size output.
97    pub fn finalize_into(mut self, out: &mut [u8]) {
98        self.xof.update(right_encode_bytes(out.len()).as_bytes());
99        self.xof.finalize_xof_into(out)
100    }
101
102    /// Returns a fixed-size output.
103    pub fn finalize<N: ArrayLength>(self) -> GenericArray<u8, N> {
104        let mut out = GenericArray::default();
105        self.finalize_into(&mut out);
106        out
107    }
108}
109
110#[cfg(feature = "rust-crypto")]
111#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
112impl<X: Xof> sha3::digest::HashMarker for TupleHash<X> {}
113
114#[cfg(feature = "rust-crypto")]
115#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
116impl<X: Xof> sha3::digest::Update for TupleHash<X> {
117    #[inline]
118    fn update(&mut self, data: &[u8]) {
119        self.update(data);
120    }
121}
122
123/// `TupleHashXof128`.
124#[cfg(feature = "rust-crypto")]
125#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
126pub type TupleHashXof128 = TupleHashXof<CShake128>;
127
128/// `TupleHashXof256`.
129#[cfg(feature = "rust-crypto")]
130#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
131pub type TupleHashXof256 = TupleHashXof<CShake256>;
132
133/// A cryptographic hash over a set of strings such that each
134/// string is unambiguously encoded.
135///
136/// For example, the TupleHash of `("abc", "d")` will produce
137/// a different hash value than the TupleHash of `("ab", "cd")`.
138///
139/// # Warning
140///
141/// `TupleHash` is only defined for cSHAKE128 and cSHAKE256.
142/// Using this with a different XOF might have worse security
143/// properties.
144#[derive(Clone, Debug, Default)]
145pub struct TupleHashXof<X> {
146    xof: X,
147}
148
149impl<X: Xof> TupleHashXof<X> {
150    /// Creates a `TupleHash` with the customization string `s`.
151    pub fn new(s: &[u8]) -> Self {
152        Self { xof: X::new(s) }
153    }
154
155    /// Writes the string `s` to the hash.
156    pub fn update(&mut self, s: &[u8]) {
157        for x in &encode_string(s) {
158            self.xof.update(x);
159        }
160    }
161
162    /// Returns a variable-size output.
163    pub fn finalize_xof(mut self) -> TupleHashXofReader<X::Reader> {
164        self.xof.update(right_encode(0).as_bytes());
165        TupleHashXofReader(self.xof.finalize_xof())
166    }
167}
168
169impl<X: Xof> Xof for TupleHashXof<X> {
170    type Reader = TupleHashXofReader<X::Reader>;
171
172    #[inline]
173    fn new(s: &[u8]) -> Self {
174        Self { xof: X::new(s) }
175    }
176
177    #[inline]
178    fn update(&mut self, data: &[u8]) {
179        self.update(data);
180    }
181
182    #[inline]
183    fn finalize_xof(self) -> Self::Reader {
184        self.finalize_xof()
185    }
186}
187
188#[cfg(feature = "rust-crypto")]
189#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
190impl<X: Xof> sha3::digest::ExtendableOutput for TupleHashXof<X> {
191    type Reader = TupleHashXofReader<X::Reader>;
192
193    fn finalize_xof(self) -> Self::Reader {
194        self.finalize_xof()
195    }
196}
197
198#[cfg(feature = "rust-crypto")]
199#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
200impl<X: Xof> sha3::digest::HashMarker for TupleHashXof<X> {}
201
202#[cfg(feature = "rust-crypto")]
203#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
204impl<X: Xof> sha3::digest::Update for TupleHashXof<X> {
205    #[inline]
206    fn update(&mut self, data: &[u8]) {
207        self.update(data);
208    }
209}
210
211/// An [`XofReader`] for [`TupleHashXof`].
212#[derive(Clone, Debug)]
213pub struct TupleHashXofReader<R>(R);
214
215#[cfg(not(feature = "rust-crypto"))]
216impl<R: XofReader> XofReader for TupleHashXofReader<R> {
217    #[inline]
218    fn read(&mut self, out: &mut [u8]) {
219        self.0.read(out);
220    }
221}
222
223#[cfg(feature = "rust-crypto")]
224impl<R: XofReader> sha3::digest::XofReader for TupleHashXofReader<R> {
225    #[inline]
226    fn read(&mut self, out: &mut [u8]) {
227        self.0.read(out);
228    }
229}
230
231/// `TupleHash` over a fixed-size set of inputs.
232///
233/// # Warning
234///
235/// `TupleHash` is only defined for cSHAKE128 and cSHAKE256.
236/// Using this with a different XOF might have worse security
237/// properties.
238pub fn tuple_hash<X, I, N>(s: &[u8], x: I) -> GenericArray<u8, N>
239where
240    X: Xof,
241    I: IntoIterator,
242    I::Item: AsRef<[u8]>,
243    N: ArrayLength,
244{
245    let mut h = TupleHash::<X>::new(s);
246    for xi in x {
247        h.update(xi.as_ref());
248    }
249    h.finalize()
250}
251
252/// `TupleHash128` over a fixed-size set of inputs.
253///
254/// For the XOF variant, see [`tuple_hash_xof128`].
255#[cfg(feature = "rust-crypto")]
256#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
257pub fn tuple_hash128<I, N>(s: &[u8], x: I) -> GenericArray<u8, N>
258where
259    I: IntoIterator,
260    I::Item: AsRef<[u8]>,
261    N: ArrayLength,
262{
263    tuple_hash::<CShake128, I, N>(s, x)
264}
265
266/// `TupleHash256` over a fixed-size set of inputs.
267///
268/// For the XOF variant, see [`tuple_hash_xof256`].
269#[cfg(feature = "rust-crypto")]
270#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
271pub fn tuple_hash256<I, N>(s: &[u8], x: I) -> GenericArray<u8, N>
272where
273    I: IntoIterator,
274    I::Item: AsRef<[u8]>,
275    N: ArrayLength,
276{
277    tuple_hash::<CShake256, I, N>(s, x)
278}
279
280/// `TupleHashXof` over a fixed-size set of inputs.
281///
282/// # Warning
283///
284/// `TupleHashXof` is only defined for cSHAKE128 and cSHAKE256.
285/// Using this with a different XOF might have worse security
286/// properties.
287pub fn tuple_hash_xof<X, I>(s: &[u8], x: I) -> impl XofReader
288where
289    X: Xof,
290    I: IntoIterator,
291    I::Item: AsRef<[u8]>,
292{
293    let mut h = TupleHashXof::<X>::new(s);
294    for xi in x {
295        h.update(xi.as_ref());
296    }
297    h.finalize_xof()
298}
299
300/// `TupleHashXof256` over a fixed-size set of inputs.
301#[cfg(feature = "rust-crypto")]
302#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
303pub fn tuple_hash_xof128<I>(s: &[u8], x: I) -> impl XofReader
304where
305    I: IntoIterator,
306    I::Item: AsRef<[u8]>,
307{
308    tuple_hash_xof::<CShake128, I>(s, x)
309}
310
311/// `TupleHashXof128` over a fixed-size set of inputs.
312#[cfg(feature = "rust-crypto")]
313#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
314pub fn tuple_hash_xof256<I>(s: &[u8], x: I) -> impl XofReader
315where
316    I: IntoIterator,
317    I::Item: AsRef<[u8]>,
318{
319    tuple_hash_xof::<CShake256, I>(s, x)
320}
321
322macro_rules! impl_cshake {
323    ($ty:ty) => {
324        #[cfg(feature = "rust-crypto")]
325        #[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
326        impl Xof for $ty {
327            type Reader = <$ty as sha3::digest::ExtendableOutput>::Reader;
328
329            fn new(s: &[u8]) -> Self {
330                let core = <$ty as CoreProxy>::Core::new_with_function_name(b"TupleHash", s);
331                <$ty>::from_core(core)
332            }
333
334            fn update(&mut self, data: &[u8]) {
335                sha3::digest::Update::update(self, data);
336            }
337
338            fn finalize_xof(self) -> Self::Reader {
339                sha3::digest::ExtendableOutput::finalize_xof(self)
340            }
341        }
342    };
343}
344impl_cshake!(CShake128);
345impl_cshake!(CShake256);
346
347#[cfg(test)]
348#[allow(clippy::type_complexity, reason = "Tests")]
349mod tests {
350    use generic_array::typenum::{U32, U64};
351
352    use super::*;
353
354    #[test]
355    fn test_tuple_hash128_basic() {
356        let lhs = tuple_hash128::<_, U32>(b"test", ["abc", "d"]);
357        let rhs = tuple_hash128::<_, U32>(b"test", ["ab", "cd"]);
358        assert_ne!(lhs, rhs);
359    }
360
361    #[test]
362    fn test_tuple_hash256_basic() {
363        let lhs = tuple_hash256::<_, U32>(b"test", ["abc", "d"]);
364        let rhs = tuple_hash256::<_, U32>(b"test", ["ab", "cd"]);
365        assert_ne!(lhs, rhs);
366    }
367
368    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/tuplehash_samples.pdf
369    #[test]
370    fn test_tuple_hash128_vectors() {
371        let vectors: &[(&[u8], &[&[u8]], [u8; 32])] = &[
372            (
373                &[],
374                &[&[0x0, 0x1, 0x2], &[0x10, 0x11, 0x12, 0x13, 0x14, 0x15]],
375                [
376                    0xC5, 0xD8, 0x78, 0x6C, 0x1A, 0xFB, 0x9B, 0x82, 0x11, 0x1A, 0xB3, 0x4B, 0x65,
377                    0xB2, 0xC0, 0x04, 0x8F, 0xA6, 0x4E, 0x6D, 0x48, 0xE2, 0x63, 0x26, 0x4C, 0xE1,
378                    0x70, 0x7D, 0x3F, 0xFC, 0x8E, 0xD1,
379                ],
380            ),
381            (
382                b"My Tuple App",
383                &[&[0x0, 0x1, 0x2], &[0x10, 0x11, 0x12, 0x13, 0x14, 0x15]],
384                [
385                    0x75, 0xCD, 0xB2, 0x0F, 0xF4, 0xDB, 0x11, 0x54, 0xE8, 0x41, 0xD7, 0x58, 0xE2,
386                    0x41, 0x60, 0xC5, 0x4B, 0xAE, 0x86, 0xEB, 0x8C, 0x13, 0xE7, 0xF5, 0xF4, 0x0E,
387                    0xB3, 0x55, 0x88, 0xE9, 0x6D, 0xFB,
388                ],
389            ),
390            (
391                b"My Tuple App",
392                &[
393                    &[0x0, 0x1, 0x2],
394                    &[0x10, 0x11, 0x12, 0x13, 0x14, 0x15],
395                    &[0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28],
396                ],
397                [
398                    0xE6, 0x0F, 0x20, 0x2C, 0x89, 0xA2, 0x63, 0x1E, 0xDA, 0x8D, 0x4C, 0x58, 0x8C,
399                    0xA5, 0xFD, 0x07, 0xF3, 0x9E, 0x51, 0x51, 0x99, 0x8D, 0xEC, 0xCF, 0x97, 0x3A,
400                    0xDB, 0x38, 0x04, 0xBB, 0x6E, 0x84,
401                ],
402            ),
403        ];
404        for (i, (s, x, want)) in vectors.iter().enumerate() {
405            let got = tuple_hash128::<&[&[u8]], U32>(s, x);
406            let want = GenericArray::from(*want);
407            assert_eq!(got, want, "#{i}");
408        }
409    }
410
411    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/tuplehash_samples.pdf
412    #[test]
413    fn test_tuple_hash256_vectors() {
414        let vectors: &[(&[u8], &[&[u8]], [u8; 64])] = &[
415            (
416                &[],
417                &[&[0x0, 0x1, 0x2], &[0x10, 0x11, 0x12, 0x13, 0x14, 0x15]],
418                [
419                    0xCF, 0xB7, 0x05, 0x8C, 0xAC, 0xA5, 0xE6, 0x68, 0xF8, 0x1A, 0x12, 0xA2, 0x0A,
420                    0x21, 0x95, 0xCE, 0x97, 0xA9, 0x25, 0xF1, 0xDB, 0xA3, 0xE7, 0x44, 0x9A, 0x56,
421                    0xF8, 0x22, 0x01, 0xEC, 0x60, 0x73, 0x11, 0xAC, 0x26, 0x96, 0xB1, 0xAB, 0x5E,
422                    0xA2, 0x35, 0x2D, 0xF1, 0x42, 0x3B, 0xDE, 0x7B, 0xD4, 0xBB, 0x78, 0xC9, 0xAE,
423                    0xD1, 0xA8, 0x53, 0xC7, 0x86, 0x72, 0xF9, 0xEB, 0x23, 0xBB, 0xE1, 0x94,
424                ],
425            ),
426            (
427                b"My Tuple App",
428                &[&[0x0, 0x1, 0x2], &[0x10, 0x11, 0x12, 0x13, 0x14, 0x15]],
429                [
430                    0x14, 0x7C, 0x21, 0x91, 0xD5, 0xED, 0x7E, 0xFD, 0x98, 0xDB, 0xD9, 0x6D, 0x7A,
431                    0xB5, 0xA1, 0x16, 0x92, 0x57, 0x6F, 0x5F, 0xE2, 0xA5, 0x06, 0x5F, 0x3E, 0x33,
432                    0xDE, 0x6B, 0xBA, 0x9F, 0x3A, 0xA1, 0xC4, 0xE9, 0xA0, 0x68, 0xA2, 0x89, 0xC6,
433                    0x1C, 0x95, 0xAA, 0xB3, 0x0A, 0xEE, 0x1E, 0x41, 0x0B, 0x0B, 0x60, 0x7D, 0xE3,
434                    0x62, 0x0E, 0x24, 0xA4, 0xE3, 0xBF, 0x98, 0x52, 0xA1, 0xD4, 0x36, 0x7E,
435                ],
436            ),
437            (
438                b"My Tuple App",
439                &[
440                    &[0x0, 0x1, 0x2],
441                    &[0x10, 0x11, 0x12, 0x13, 0x14, 0x15],
442                    &[0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28],
443                ],
444                [
445                    0x45, 0x00, 0x0B, 0xE6, 0x3F, 0x9B, 0x6B, 0xFD, 0x89, 0xF5, 0x47, 0x17, 0x67,
446                    0x0F, 0x69, 0xA9, 0xBC, 0x76, 0x35, 0x91, 0xA4, 0xF0, 0x5C, 0x50, 0xD6, 0x88,
447                    0x91, 0xA7, 0x44, 0xBC, 0xC6, 0xE7, 0xD6, 0xD5, 0xB5, 0xE8, 0x2C, 0x01, 0x8D,
448                    0xA9, 0x99, 0xED, 0x35, 0xB0, 0xBB, 0x49, 0xC9, 0x67, 0x8E, 0x52, 0x6A, 0xBD,
449                    0x8E, 0x85, 0xC1, 0x3E, 0xD2, 0x54, 0x02, 0x1D, 0xB9, 0xE7, 0x90, 0xCE,
450                ],
451            ),
452        ];
453        for (i, (s, x, want)) in vectors.iter().enumerate() {
454            let got = tuple_hash256::<&[&[u8]], U64>(s, x);
455            let want = GenericArray::from(*want);
456            assert_eq!(got, want, "#{i}");
457        }
458    }
459}