Skip to main content

miden_crypto/hash/keccak/
mod.rs

1use core::mem::size_of;
2
3use sha3::Digest as Sha3Digest;
4
5use super::{
6    Felt, HasherExt,
7    digest::{DIGEST256_BYTES, Digest256},
8};
9use crate::field::{BasedVectorSpace, PrimeField64};
10
11#[cfg(test)]
12mod tests;
13
14// RE-EXPORTS
15// ================================================================================================
16
17/// Re-export of the Keccak hasher from Plonky3 for use in the prover config downstream.
18pub use p3_keccak::{Keccak256Hash, KeccakF, VECTOR_LEN};
19
20// KECCAK256 DIGEST
21// ================================================================================================
22
23/// Keccak-256 digest (32 bytes).
24///
25/// This is a type alias to the generic `Digest256` type.
26pub type Keccak256Digest = Digest256;
27
28// KECCAK256 HASHER
29// ================================================================================================
30
31/// Keccak256 hash function
32#[derive(Debug, Copy, Clone, Eq, PartialEq)]
33pub struct Keccak256;
34
35impl HasherExt for Keccak256 {
36    type Digest = Keccak256Digest;
37
38    fn hash_iter<'a>(slices: impl Iterator<Item = &'a [u8]>) -> Self::Digest {
39        let mut hasher = sha3::Keccak256::new();
40        for slice in slices {
41            hasher.update(slice);
42        }
43        Keccak256Digest::from(<[u8; DIGEST256_BYTES]>::from(hasher.finalize()))
44    }
45}
46
47impl Keccak256 {
48    /// Keccak256 collision resistance is 128-bits for 32-bytes output.
49    pub const COLLISION_RESISTANCE: u32 = 128;
50
51    pub fn hash(bytes: &[u8]) -> Keccak256Digest {
52        let mut hasher = sha3::Keccak256::new();
53        hasher.update(bytes);
54        Keccak256Digest::from(<[u8; DIGEST256_BYTES]>::from(hasher.finalize()))
55    }
56
57    pub fn merge(values: &[Keccak256Digest; 2]) -> Keccak256Digest {
58        Self::hash(Keccak256Digest::digests_as_bytes(values))
59    }
60
61    pub fn merge_many(values: &[Keccak256Digest]) -> Keccak256Digest {
62        let data = Keccak256Digest::digests_as_bytes(values);
63        let mut hasher = sha3::Keccak256::new();
64        hasher.update(data);
65        Keccak256Digest::from(<[u8; DIGEST256_BYTES]>::from(hasher.finalize()))
66    }
67
68    pub fn merge_with_int(seed: Keccak256Digest, value: u64) -> Keccak256Digest {
69        let mut hasher = sha3::Keccak256::new();
70        hasher.update(&*seed);
71        hasher.update(value.to_le_bytes());
72        Keccak256Digest::from(<[u8; DIGEST256_BYTES]>::from(hasher.finalize()))
73    }
74
75    /// Returns a hash of the provided field elements.
76    #[inline(always)]
77    pub fn hash_elements<E>(elements: &[E]) -> Keccak256Digest
78    where
79        E: BasedVectorSpace<Felt>,
80    {
81        hash_elements(elements).into()
82    }
83
84    /// Hashes an iterator of byte slices.
85    #[inline(always)]
86    pub fn hash_iter<'a>(slices: impl Iterator<Item = &'a [u8]>) -> Keccak256Digest {
87        <Self as HasherExt>::hash_iter(slices)
88    }
89}
90
91// HELPER FUNCTIONS
92// ================================================================================================
93
94/// Hash the elements into bytes.
95fn hash_elements<E>(elements: &[E]) -> [u8; DIGEST256_BYTES]
96where
97    E: BasedVectorSpace<Felt>,
98{
99    // don't leak assumptions from felt and check its actual implementation.
100    let digest = {
101        const FELT_BYTES: usize = size_of::<u64>();
102        const { assert!(FELT_BYTES == 8, "buffer arithmetic assumes 8-byte field elements") };
103
104        let mut hasher = sha3::Keccak256::new();
105        // Keccak256 rate: 1600 bits (state) - 512 bits (capacity) = 1088 bits = 136 bytes
106        let mut buf = [0_u8; 136];
107        let mut buf_offset = 0;
108
109        for elem in elements.iter() {
110            for &felt in E::as_basis_coefficients_slice(elem) {
111                buf[buf_offset..buf_offset + FELT_BYTES]
112                    .copy_from_slice(&felt.as_canonical_u64().to_le_bytes());
113                buf_offset += FELT_BYTES;
114
115                if buf_offset == 136 {
116                    hasher.update(buf);
117                    buf_offset = 0;
118                }
119            }
120        }
121
122        if buf_offset > 0 {
123            hasher.update(&buf[..buf_offset]);
124        }
125
126        hasher.finalize()
127    };
128    digest.into()
129}