starknet_crypto/
poseidon_hash.rs

1// Code ported from the the implementation from pathfinder here:
2//   https://github.com/eqlabs/pathfinder/blob/00a1a74a90a7b8a7f1d07ac3e616be1cb39cf8f1/crates/stark_poseidon/src/lib.rs
3
4use starknet_types_core::{felt::Felt, hash::Poseidon};
5
6/// A stateful hasher for Starknet Poseidon hash.
7///
8/// Using this hasher is the same as calling [`poseidon_hash_many`].
9#[derive(Debug, Default)]
10pub struct PoseidonHasher {
11    state: [Felt; 3],
12    buffer: Option<Felt>,
13}
14
15impl PoseidonHasher {
16    /// Creates a new [`PoseidonHasher`].
17    pub fn new() -> Self {
18        Self::default()
19    }
20
21    /// Absorbs message into the hash.
22    pub fn update(&mut self, msg: Felt) {
23        match self.buffer.take() {
24            Some(previous_message) => {
25                self.state[0] += previous_message;
26                self.state[1] += msg;
27                Poseidon::hades_permutation(&mut self.state);
28            }
29            None => {
30                self.buffer = Some(msg);
31            }
32        }
33    }
34
35    /// Finishes and returns hash.
36    pub fn finalize(mut self) -> Felt {
37        // Applies padding
38        match self.buffer.take() {
39            Some(last_message) => {
40                self.state[0] += last_message;
41                self.state[1] += Felt::ONE;
42            }
43            None => {
44                self.state[0] += Felt::ONE;
45            }
46        }
47        Poseidon::hades_permutation(&mut self.state);
48
49        self.state[0]
50    }
51}
52
53/// Computes the Starknet Poseidon hash of x and y.
54pub fn poseidon_hash(x: Felt, y: Felt) -> Felt {
55    let mut state = [x, y, Felt::TWO];
56    Poseidon::hades_permutation(&mut state);
57
58    state[0]
59}
60
61/// Computes the Starknet Poseidon hash of a single [`Felt`].
62pub fn poseidon_hash_single(x: Felt) -> Felt {
63    let mut state = [x, Felt::ZERO, Felt::ONE];
64    Poseidon::hades_permutation(&mut state);
65
66    state[0]
67}
68
69/// Computes the Starknet Poseidon hash of an arbitrary number of [`Felt`]s.
70///
71/// Using this function is the same as using [`PoseidonHasher`].
72pub fn poseidon_hash_many<'a, I: IntoIterator<Item = &'a Felt>>(msgs: I) -> Felt {
73    let mut state = [Felt::ZERO, Felt::ZERO, Felt::ZERO];
74    let mut iter = msgs.into_iter();
75
76    loop {
77        match iter.next() {
78            Some(v) => state[0] += *v,
79            None => {
80                state[0] += Felt::ONE;
81                break;
82            }
83        }
84
85        match iter.next() {
86            Some(v) => state[1] += *v,
87            None => {
88                state[1] += Felt::ONE;
89                break;
90            }
91        }
92
93        Poseidon::hades_permutation(&mut state);
94    }
95    Poseidon::hades_permutation(&mut state);
96
97    state[0]
98}
99
100/// Poseidon permutation function.                                                            
101pub fn poseidon_permute_comp(state: &mut [Felt; 3]) {
102    Poseidon::hades_permutation(state)
103}
104
105#[cfg(test)]
106mod tests {
107    use starknet_types_core::hash::StarkHash;
108
109    use super::*;
110
111    #[test]
112    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
113    fn test_poseidon_hash() {
114        // Test data generated from `cairo-lang` v0.11.0
115        let test_data = [
116            (
117                Felt::from_hex("0xb662f9017fa7956fd70e26129b1833e10ad000fd37b4d9f4e0ce6884b7bbe")
118                    .unwrap(),
119                Felt::from_hex("0x1fe356bf76102cdae1bfbdc173602ead228b12904c00dad9cf16e035468bea")
120                    .unwrap(),
121                Felt::from_hex("0x75540825a6ecc5dc7d7c2f5f868164182742227f1367d66c43ee51ec7937a81")
122                    .unwrap(),
123            ),
124            (
125                Felt::from_hex("0xf4e01b2032298f86b539e3d3ac05ced20d2ef275273f9325f8827717156529")
126                    .unwrap(),
127                Felt::from_hex("0x587bc46f5f58e0511b93c31134652a689d761a9e7f234f0f130c52e4679f3a")
128                    .unwrap(),
129                Felt::from_hex("0xbdb3180fdcfd6d6f172beb401af54dd71b6569e6061767234db2b777adf98b")
130                    .unwrap(),
131            ),
132        ];
133
134        for (x, y, hash) in test_data {
135            assert_eq!(Poseidon::hash(&x, &y), hash);
136        }
137    }
138
139    #[test]
140    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
141    fn test_poseidon_hash_single() {
142        // Test data generated from `cairo-lang` v0.11.0
143        let test_data = [
144            (
145                Felt::from_hex("0x9dad5d6f502ccbcb6d34ede04f0337df3b98936aaf782f4cc07d147e3a4fd6")
146                    .unwrap(),
147                Felt::from_hex("0x11222854783f17f1c580ff64671bc3868de034c236f956216e8ed4ab7533455")
148                    .unwrap(),
149            ),
150            (
151                Felt::from_hex("0x3164a8e2181ff7b83391b4a86bc8967f145c38f10f35fc74e9359a0c78f7b6")
152                    .unwrap(),
153                Felt::from_hex("0x79ad7aa7b98d47705446fa01865942119026ac748d67a5840f06948bce2306b")
154                    .unwrap(),
155            ),
156        ];
157
158        for (x, hash) in test_data {
159            let mut state = [x, Felt::ZERO, Felt::ONE];
160            Poseidon::hades_permutation(&mut state);
161            assert_eq!(state[0], hash);
162        }
163    }
164
165    #[test]
166    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
167    fn test_poseidon_hash_many() {
168        // Test data generated from `cairo-lang` v0.11.0
169        let test_data = [
170            (
171                vec![
172                    Felt::from_hex(
173                        "0x9bf52404586087391c5fbb42538692e7ca2149bac13c145ae4230a51a6fc47",
174                    )
175                    .unwrap(),
176                    Felt::from_hex(
177                        "0x40304159ee9d2d611120fbd7c7fb8020cc8f7a599bfa108e0e085222b862c0",
178                    )
179                    .unwrap(),
180                    Felt::from_hex(
181                        "0x46286e4f3c450761d960d6a151a9c0988f9e16f8a48d4c0a85817c009f806a",
182                    )
183                    .unwrap(),
184                ],
185                Felt::from_hex("0x1ec38b38dc88bac7b0ed6ff6326f975a06a59ac601b417745fd412a5d38e4f7")
186                    .unwrap(),
187            ),
188            (
189                vec![
190                    Felt::from_hex(
191                        "0xbdace8883922662601b2fd197bb660b081fcf383ede60725bd080d4b5f2fd3",
192                    )
193                    .unwrap(),
194                    Felt::from_hex(
195                        "0x1eb1daaf3fdad326b959dec70ced23649cdf8786537cee0c5758a1a4229097",
196                    )
197                    .unwrap(),
198                    Felt::from_hex(
199                        "0x869ca04071b779d6f940cdf33e62d51521e19223ab148ef571856ff3a44ff1",
200                    )
201                    .unwrap(),
202                    Felt::from_hex(
203                        "0x533e6df8d7c4b634b1f27035c8676a7439c635e1fea356484de7f0de677930",
204                    )
205                    .unwrap(),
206                ],
207                Felt::from_hex("0x2520b8f910174c3e650725baacad4efafaae7623c69a0b5513d75e500f36624")
208                    .unwrap(),
209            ),
210        ];
211
212        for (input, hash) in test_data {
213            // Direct function call
214            assert_eq!(Poseidon::hash_array(&input), hash);
215
216            // With hasher
217            let mut hasher = PoseidonHasher::new();
218            input.iter().for_each(|msg| hasher.update(*msg));
219            assert_eq!(hasher.finalize(), hash);
220        }
221    }
222}