starknet_rust_crypto/
blake2s_hash.rs

1use blake2::Blake2s256;
2use digest::Digest;
3use starknet_types_core::{felt::Felt, hash::Blake2Felt252};
4
5/// A stateful hasher for Starknet Blake2s hash.
6///
7/// Using this hasher is the same as calling [`blake2s_hash_many`].
8#[derive(Debug, Default, Clone)]
9pub struct Blake2Hasher {
10    hasher: Blake2s256,
11}
12
13impl Blake2Hasher {
14    /// Creates a new [`crate::Blake2Hasher`].
15    pub fn new() -> Self {
16        Self::default()
17    }
18
19    /// Absorbs message into the hash.
20    pub fn update(&mut self, msg: Felt) {
21        // 1) Encode msg into 2 or 8 u32.
22        let u32_words = Blake2Felt252::encode_felts_to_u32s(&[msg]);
23
24        // 2) Serialize the u32 limbs into a little-endian byte stream.
25        // Allocate fixed-size buffer on stack (u32 → 4 bytes)
26        let mut buf = [0u8; 8 * 4]; // Support up to 8 u32s = 32 bytes
27        let mut offset = 0;
28
29        for word in u32_words {
30            let bytes = word.to_le_bytes();
31            buf[offset..offset + 4].copy_from_slice(&bytes);
32            offset += 4;
33        }
34
35        self.hasher.update(&buf[0..offset]);
36    }
37
38    /// Finishes and returns hash.
39    pub fn finalize(self) -> Felt {
40        let hash32 = self.hasher.finalize();
41        pack_256_le_to_felt(hash32.as_slice())
42    }
43}
44
45/// Computes the Starknet Blake2s hash of x and y.
46#[inline(always)]
47pub fn blake2s_hash(x: Felt, y: Felt) -> Felt {
48    // We don't use [`Blake2Felt252::hash`] to avoid taking a reference.
49    Blake2Felt252::encode_felt252_data_and_calc_blake_hash(&[x, y])
50}
51
52/// Computes the Starknet Blake2s hash of a single [`Felt`].
53#[inline(always)]
54pub fn blake2s_hash_single(x: Felt) -> Felt {
55    // We don't use [`Blake2Felt252::hash_single`] to avoid taking a reference.
56    Blake2Felt252::encode_felt252_data_and_calc_blake_hash(&[x])
57}
58
59/// Computes the Starknet Blake2s hash of an arbitrary number of [`Felt`]s.
60///
61/// Using this function is the same as using [`Blake2Hasher`].
62#[inline(always)]
63pub fn blake2s_hash_many(msgs: &[Felt]) -> Felt {
64    Blake2Felt252::encode_felt252_data_and_calc_blake_hash(msgs)
65}
66
67// TODO: (#10) Investigate if this can be not duplicated from the `starknet-types-core`
68// Adapted from https://github.com/starknet-io/types-rs/blob/734276638c8a6976ce69364acdbbb2b3c3463f07/crates/starknet-types-core/src/hash/blake2s.rs
69fn pack_256_le_to_felt(bytes: &[u8]) -> Felt {
70    assert!(bytes.len() >= 32, "need at least 32 bytes to pack 8 words");
71
72    // 1) copy your 32-byte LE-hash into the low 32 bytes of a 32-byte buffer.
73    let mut buf = [0u8; 32];
74    buf[..32].copy_from_slice(&bytes[..32]);
75
76    // 2) interpret the whole 32-byte buffer as a little-endian Felt.
77    Felt::from_bytes_le(&buf)
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use test_case::test_case;
84
85    /// Adapted from `<https://github.com/starknet-io/types-rs/blob/734276638c8a6976ce69364acdbbb2b3c3463f07/crates/starknet-types-core/src/hash/blake2s.rs>`
86    #[test_case(vec![] => "874258848688468311465623299960361657518391155660316941922502367727700287818"; "empty_input")]
87    #[test_case(vec![Felt::from((1u64 << 63) - 1)] => "94160078030592802631039216199460125121854007413180444742120780261703604445"; "max_i64")]
88    #[test_case(vec![Felt::from(1u64 << 63)] => "318549634615606806810268830802792194529205864650702991817600345489579978482"; "i64_sign_boundary")]
89    #[test_case(vec![Felt::from_hex_unchecked("800000000000011000000000000000000000000000000000000000000000000")] => "3505594194634492896230805823524239179921427575619914728883524629460058657521"; "large_hex")]
90    #[test_case(vec![Felt::from(42), Felt::from(1u64 << 63), Felt::from(1337)] => "1127477916086913892828040583976438888091205536601278656613505514972451246501"; "mixed_sizes")]
91    #[test_case(vec![Felt::from(u64::MAX)] => "3515074221976790747383295076946184515593027667350620348239642126105984996390"; "u64_max")]
92    fn test_encode_felt252_data_and_calc_blake_hash(input: Vec<Felt>) -> String {
93        let result_hash_many = blake2s_hash_many(&input);
94
95        // Compute using Blake2s hasher
96        let mut hasher = Blake2Hasher::new();
97        for &felt in &input {
98            hasher.update(felt);
99        }
100        let result_blake = hasher.finalize();
101
102        assert_eq!(result_hash_many, result_blake);
103
104        // Return string of the result for the `test_case` expected comparison.
105        result_hash_many.to_string()
106    }
107}