qp_poseidon_core/
lib.rs

1#![no_std]
2
3extern crate alloc;
4
5#[cfg(feature = "p3")]
6pub use qp_poseidon_constants as constants;
7
8pub mod serialization;
9
10use crate::serialization::{digest_felts_to_bytes, injective_bytes_to_felts};
11use alloc::vec::Vec;
12use p3_field::PrimeCharacteristicRing;
13use p3_goldilocks::{Goldilocks, Poseidon2Goldilocks};
14use p3_symmetric::Permutation;
15use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng};
16
17/// The number of field elements to which inputs are padded in circuit-compatible hashing functions.
18pub const FIELD_ELEMENT_PREIMAGE_PADDING_LEN: usize = 189;
19
20// 4 felt output => 4 felt rate per round => capacity = 12 - 4 = 8
21// => 256 bits of classical preimage security => 128 bits security against Grover's algorithm
22const WIDTH: usize = 12;
23const RATE: usize = 4;
24const OUTPUT: usize = 4;
25
26// Bring the selected Goldilocks type in as `GF`
27#[cfg(feature = "p2")]
28pub use serialization::p2_backend::GF as P2GF;
29#[cfg(feature = "p3")]
30pub use serialization::p3_backend::GF as P3GF;
31
32// Internal state for Poseidon2 hashing
33pub struct Poseidon2State {
34	poseidon2: Poseidon2Goldilocks<WIDTH>,
35	state: [Goldilocks; WIDTH],
36	buf: [Goldilocks; RATE],
37	buf_len: usize,
38}
39
40impl Poseidon2State {
41	fn new() -> Self {
42		Self {
43			poseidon2: constants::create_poseidon(),
44			state: [Goldilocks::ZERO; WIDTH],
45			buf: [Goldilocks::ZERO; RATE],
46			buf_len: 0,
47		}
48	}
49
50	#[inline]
51	fn push_to_buf(&mut self, x: Goldilocks) {
52		self.buf[self.buf_len] = x;
53		self.buf_len += 1;
54		if self.buf_len == RATE {
55			self.absorb_full_block();
56		}
57	}
58
59	#[inline]
60	fn absorb_full_block(&mut self) {
61		// absorb RATE elements into state, then permute
62		for i in 0..RATE {
63			self.state[i] += self.buf[i];
64		}
65		self.poseidon2.permute_mut(&mut self.state);
66		self.buf = [Goldilocks::ZERO; RATE];
67		self.buf_len = 0;
68	}
69
70	fn append(&mut self, blocks: &[Goldilocks]) {
71		for &b in blocks {
72			self.push_to_buf(b);
73		}
74	}
75
76	fn append_bytes(&mut self, bytes: &[u8]) {
77		let felts = injective_bytes_to_felts(bytes);
78		self.append(&felts);
79	}
80
81	/// Finalize with variable-length padding (…||1||0* to RATE) and return the full WIDTH state.
82	fn finalize_state(mut self) -> [Goldilocks; WIDTH] {
83		// message-end '1'
84		self.push_to_buf(Goldilocks::ONE);
85		// zero-fill remaining of final block
86		while self.buf_len != 0 {
87			self.push_to_buf(Goldilocks::ZERO);
88		}
89		self.state
90	}
91
92	/// Finalize and return a 32-byte digest (first RATE felts).
93	fn finalize(self) -> [u8; 32] {
94		let state = self.finalize_state();
95		digest_felts_to_bytes(&state[..OUTPUT].try_into().expect("OUTPUT <= WIDTH"))
96	}
97
98	/// Finalize and squeeze 64 bytes (two squeezes).
99	fn finalize_twice(mut self) -> [u8; 64] {
100		// message-end '1'
101		self.push_to_buf(Goldilocks::ONE);
102		// zero-fill remaining of final block
103		while self.buf_len != 0 {
104			self.push_to_buf(Goldilocks::ZERO);
105		}
106
107		let h1: [u8; 32] =
108			digest_felts_to_bytes(&self.state[..RATE].try_into().expect("RATE <= WIDTH"));
109		// second squeeze
110		self.poseidon2.permute_mut(&mut self.state);
111		let h2: [u8; 32] =
112			digest_felts_to_bytes(&self.state[..RATE].try_into().expect("RATE <= WIDTH"));
113
114		[h1, h2].concat().try_into().expect("64 bytes")
115	}
116}
117
118pub fn poseidon2_from_seed(seed: u64) -> Poseidon2State {
119	let mut rng = ChaCha20Rng::seed_from_u64(seed);
120	let poseidon2 = Poseidon2Goldilocks::<WIDTH>::new_from_rng_128(&mut rng);
121	Poseidon2State {
122		poseidon2,
123		state: [Goldilocks::ZERO; WIDTH],
124		buf: [Goldilocks::ZERO; RATE],
125		buf_len: 0,
126	}
127}
128
129// This function is for hashing field elements in the storage trie. It pads to 189 field elements
130// because the zk-circuit we use for transaction inclusion verifies a storage proof and requires a
131// fixed amount of field elements (the maximum that could be enountered in the storage proof) as a
132// preimage
133fn hash_circuit_padding_felts<const C: usize>(mut x: Vec<Goldilocks>) -> [u8; 32] {
134	// This function doesn't protect against length extension attacks but is safe as
135	// long as the input felts are the outputs of an injective encoding.
136	// For this reason, we wrap it in hash_padded which performs injective encoding from bytes,
137	// so application users are safe.
138	let len = x.len();
139	if len < C {
140		x.resize(C, Goldilocks::ZERO);
141	}
142
143	hash_variable_length(x)
144}
145
146/// Hash bytes with constant padding to size C to ensure consistent circuit behavior
147/// NOTE: Will panic if felt encoded input exceeds capacity of C
148pub fn hash_padded_bytes<const C: usize>(x: &[u8]) -> [u8; 32] {
149	hash_circuit_padding_felts::<C>(injective_bytes_to_felts(x))
150}
151
152/// Hash field elements without any padding
153pub fn hash_variable_length(x: Vec<Goldilocks>) -> [u8; 32] {
154	let mut st = Poseidon2State::new();
155	st.append(&x);
156	st.finalize()
157}
158
159/// Double hash (preimage -> hash -> hash) field elements without any padding
160pub fn double_hash_variable_length(x: Vec<Goldilocks>) -> [u8; 32] {
161	let mut st = Poseidon2State::new();
162	st.append(&x);
163	// Extract first digest
164	let state_0 = st.finalize_state();
165	let output_0 = &state_0[..OUTPUT];
166	// Hash the digest again
167	st = Poseidon2State::new();
168	st.append(output_0);
169	st.finalize()
170}
171
172/// Hash bytes without any padding
173/// NOTE: Not domain-separated from hash_variable_length; use with caution
174pub fn hash_variable_length_bytes(x: &[u8]) -> [u8; 32] {
175	let mut st = Poseidon2State::new();
176	st.append_bytes(x);
177	st.finalize()
178}
179
180/// Hash with 512-bit output by squeezing the sponge twice
181pub fn hash_squeeze_twice(x: &[u8]) -> [u8; 64] {
182	let mut st = Poseidon2State::new();
183	st.append_bytes(x);
184	st.finalize_twice()
185}
186
187#[cfg(test)]
188mod tests {
189	use crate::serialization::unsafe_digest_bytes_to_felts;
190
191	use super::*;
192	use alloc::vec;
193	use hex;
194	use p3_field::PrimeField64;
195
196	const C: usize = FIELD_ELEMENT_PREIMAGE_PADDING_LEN;
197
198	#[test]
199	fn test_empty_input() {
200		let result = hash_padded_bytes::<C>(&[]);
201		assert_eq!(result.len(), 32);
202	}
203
204	#[test]
205	fn test_single_byte() {
206		let input = vec![42u8];
207		let result = hash_padded_bytes::<C>(&input);
208		assert_eq!(result.len(), 32);
209	}
210
211	#[test]
212	fn test_exactly_32_bytes() {
213		let input = [1u8; 32];
214		let result = hash_padded_bytes::<C>(&input);
215		assert_eq!(result.len(), 32);
216	}
217
218	#[test]
219	fn test_multiple_chunks() {
220		let input = [2u8; 64]; // Two chunks
221		let result = hash_padded_bytes::<C>(&input);
222		assert_eq!(result.len(), 32);
223	}
224
225	#[test]
226	fn test_partial_chunk() {
227		let input = [3u8; 40]; // One full chunk plus 8 bytes
228		let result = hash_padded_bytes::<C>(&input);
229		assert_eq!(result.len(), 32);
230	}
231
232	#[test]
233	fn test_consistency() {
234		let input = [4u8; 50];
235		let iterations = 10;
236		let current_hash = hash_padded_bytes::<C>(&input);
237
238		for _ in 0..iterations {
239			let hash1 = hash_padded_bytes::<C>(&current_hash);
240			let hash2 = hash_padded_bytes::<C>(&current_hash);
241			assert_eq!(hash1, hash2, "Hash function should be deterministic");
242		}
243	}
244
245	#[test]
246	fn test_different_inputs() {
247		let input1 = [5u8; 32];
248		let input2 = [6u8; 32];
249		let hash1 = hash_padded_bytes::<C>(&input1);
250		let hash2 = hash_padded_bytes::<C>(&input2);
251		assert_ne!(hash1, hash2, "Different inputs should produce different hashes");
252	}
253
254	#[test]
255	fn test_poseidon2_hash_input_sizes() {
256		// Test inputs from 1 to 128 bytes
257		for size in 1..=128 {
258			// Create a predictable input: repeating byte value based on size
259			let input: Vec<u8> = (0..size).map(|i| (i * i % 256) as u8).collect();
260			let hash = hash_padded_bytes::<C>(&input);
261
262			// Assertions
263			assert_eq!(hash.len(), 32, "Input size {} should produce 32-byte hash", size);
264		}
265	}
266
267	#[test]
268	fn test_big_preimage() {
269		for overflow in 1..=10 {
270			let preimage =
271				(<p3_goldilocks::Goldilocks as PrimeField64>::ORDER_U64 + overflow).to_le_bytes();
272			let _hash = hash_padded_bytes::<C>(&preimage);
273		}
274	}
275
276	#[test]
277	fn test_circuit_preimage() {
278		let preimage =
279			hex::decode("afd8e7530b95ee5ebab950c9a0c62fae1e80463687b3982233028e914f8ec7cc");
280		let hash = hash_padded_bytes::<C>(&preimage.unwrap());
281		let _hash = hash_padded_bytes::<C>(&hash);
282	}
283
284	#[test]
285	fn test_random_inputs() {
286		let hex_strings = [
287			"a3f8",
288			"1b7e9d",
289			"4c2a6f81",
290			"e5d30b9a",
291			"1a4f7c2e9b0d8356",
292			"3e8d2a7f5c1b09e4d6f7a2c8",
293			"7b3e9a1f4c8d2e6b0a5f9d3c",
294			"1a4f7c2e9b0d83561a4f7c2e9b0d83561a4f7c2e9b0d83561a4f7c2e9b0d8356",
295			"e5d30b9a4c2a6f81e5d30b9a4c2a6f81e5d30b9a4c2a6f81e5d30b9a4c2a6f81",
296		];
297
298		for hex_string in hex_strings.iter() {
299			let preimage = hex::decode(hex_string).unwrap();
300			let hash = hash_padded_bytes::<C>(&preimage);
301			let _hash2 = hash_padded_bytes::<C>(&hash);
302		}
303	}
304
305	#[test]
306	fn test_known_value_hashes() {
307		let vectors = [
308			(
309				vec![],
310				"89d1c547f1b828c8659fe0600c90d58e95b435d91d04439b67c83b88a679380a",
311				"4d8d22af81f6c27a005a07028590ef4ee480f6c4b93f813daf9de47a07c8ae86",
312			),
313			(
314				vec![0u8],
315				"dbb29ba5d3bf3246356a8918dc2808ea5130a9ae02afefe360703afc848d3769",
316				"8f5b42e350ff5a12788210c86c2bcd49243b8f9350de818b3b0c56839a42ebad",
317			),
318			(
319				vec![0u8, 0u8],
320				"23b58c9f2aa60a1677e9bb360be87db2f48f52e8bd2702948f7f11b36cb1d607",
321				"3e6ee24fb61a22f4d825b72fc8ebd359e3b3b9566e246c71c3e450ebe3262f9c",
322			),
323			(
324				vec![0u8, 0u8, 0u8],
325				"1799097faca4e7faa34fa7e17c2e16ae281a655cd502f6ef9f1c993d74f161d6",
326				"34f4338a6f1b671062a3ac00b37ca05a47b43e16e589ccaa5b063416ba42356b",
327			),
328			(
329				vec![0u8, 0u8, 0u8, 0u8],
330				"5d1e9b2cdf43cce05de115f156dcf2062e3102341303613eeb1547886ebba4cc",
331				"7bac8c6bc49b0b750f2ce0912b815a2cb4ae20c75ac430850257882d9d321afa",
332			),
333			(
334				vec![0u8, 0u8, 0u8, 0u8, 0u8],
335				"d941bb3132ac34a919add937354f09cf302c5e972411c1854f2e5ebf5b054fc4",
336				"c95cfddb573adf4070b3d7c8d2dfbbee48b4b973d80cbda2b458abe7bb6f0def",
337			),
338			(
339				vec![0u8, 0u8, 0u8, 0u8, 0u8, 0u8],
340				"8d2fdb09cd31ab6fd59f0b429d50684a6425a7f21bc5e32e38ab19ced4fc5492",
341				"a4dc08d0a8c5ea44007462fe1fd8e45962d4ea85c420eab4140fbb30b5b5e111",
342			),
343			(
344				vec![0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8],
345				"490936de1357c80889dd9fee7f0ee58e7d7fe4c11e66bda55fa860bb6b94cddd",
346				"b01975012df91d9f9f040c34655f23f3ec1f6d1738d85679e9848143756637c9",
347			),
348			(
349				vec![0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8],
350				"eaa5c78853eac1ee6240512dd85077776ec909186fe46ec463e167790d768a40",
351				"eacd9e48d2e968131e48c8e69f2a211cc06c7778db6c5467348b45418fc7f585",
352			),
353			(
354				vec![0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8],
355				"2d2822c6cc2fd816ceb90cd9561bb1f5eb1638c2574b838a16b426e01d929928",
356				"00df670e8ec0751d3fb9b5f0281d0af9a7a82f62ad35a21247a9d6117daec151",
357			),
358			(
359				vec![0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8],
360				"9cd82ca4f742f54f62d577b3254d5138e4f5c9eea3f4173a6c1733c08cff79f2",
361				"6488c3c47c17114e3998bf90d6c50dc323a82e6e91768dca6977cfe152415ad5",
362			),
363			(
364				vec![0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8],
365				"9794607d182df1504c1a5af25d51105332b4520c06e9c669669a4060e704b15c",
366				"a5ba11e5959cdb59e6b2b0d25d470d656caf59aae961b52c159ffd6e0f04baff",
367			),
368			(
369				vec![0u8, 0u8, 0u8, 1u8],
370				"779f5f6d4ae11964fc2efd012bb691899ccc317ed9e186f9efdab73a2bf3af9e",
371				"6ffff0c97262139567c426e916c1fd70c924010153c366bb2a8957ea89902942",
372			),
373			(
374				vec![1u8, 2, 3, 4, 5, 6, 7, 8],
375				"ecdf30787278c049402e704b298c30c7787116d75e4dbcd8ce6b5757ed8833e5",
376				"131020b2e74819343f8568258ae2e9717e9b2253d57baabab78a518bc7499a8b",
377			),
378			(
379				vec![255u8; 32],
380				"fac64f5ed32acfa79a37cd5d1c4e48c67c500ae48043a61a95e51a2e181527ec",
381				"05a90ac8e3c4b7635fa3735c3a9c4fef620479fa68a9e4ae1421c39aa6939125",
382			),
383			(
384				b"hello world".to_vec(),
385				"95d6a29c17bfd2149cda69c8becbc8cc33c527f39b3a2f7d12865272fd7f5677",
386				"fd1f5d7d4701c25bbdd5dd6e3be6abb474fffbaa402f814dce95f8283abbf3e7",
387			),
388			(
389				(0u8..32).collect::<Vec<u8>>(),
390				"66f2c7df65a0f456314999fcf95899e27a5a5436cb4f04d79f11f12f8f86f0e0",
391				"2e3e4a00be0d8520cddaf3000d98c1f1d73c19bfe9fe181694bfa9afdfce7687",
392			),
393		];
394		for (input, expected_hex1, expected_hex2) in vectors.iter() {
395			let hash = hash_padded_bytes::<C>(input);
396			assert_eq!(
397				hex::encode(hash.as_slice()),
398				*expected_hex1,
399				"input: 0x{}",
400				hex::encode(input)
401			);
402
403			let hash2 = hash_variable_length_bytes(input);
404			assert_eq!(
405				hex::encode(hash2.as_slice()),
406				*expected_hex2,
407				"input: 0x{}",
408				hex::encode(input)
409			);
410		}
411	}
412
413	#[test]
414	fn test_hash_variable_length() {
415		let input = b"test";
416		let padded_hash = hash_padded_bytes::<C>(input);
417		let no_pad_hash = hash_variable_length_bytes(input);
418
419		// These should be different since one is padded and the other isn't
420		assert_ne!(padded_hash, no_pad_hash);
421		assert_eq!(padded_hash.len(), 32);
422		assert_eq!(no_pad_hash.len(), 32);
423	}
424
425	#[test]
426	fn test_hash_squeeze_twice() {
427		let input = b"test 512-bit hash";
428		let hash512 = hash_squeeze_twice(input);
429
430		// Should be exactly 64 bytes
431		assert_eq!(hash512.len(), 64);
432
433		// First 32 bytes should be hash of input
434		let expected_first = hash_variable_length_bytes(input);
435		assert_eq!(&hash512[0..32], &expected_first);
436
437		// Test deterministic
438		let hash512_2 = hash_squeeze_twice(input);
439		assert_eq!(hash512, hash512_2);
440
441		// Different inputs should produce different outputs
442		let different_hash = hash_squeeze_twice(b"different input");
443		assert_ne!(hash512, different_hash);
444	}
445
446	#[test]
447	fn test_double_hash_variable_length() {
448		let preimage = b"double hash test";
449		let first_hash = hash_variable_length_bytes(preimage);
450		let double_hash = double_hash_variable_length(injective_bytes_to_felts(preimage));
451
452		// Double hash should not equal single hash
453		assert_ne!(first_hash, double_hash);
454
455		// Double hash should equal hashing the first hash with the hash_variable_length function
456		let recomputed_double_hash =
457			hash_variable_length(unsafe_digest_bytes_to_felts(&first_hash).to_vec());
458		assert_eq!(double_hash, recomputed_double_hash);
459	}
460}