1#![cfg_attr(
2 not(feature = "agave-unstable-api"),
3 deprecated(
4 since = "3.1.0",
5 note = "This crate has been marked for formal inclusion in the Agave Unstable API. From \
6 v4.0.0 onward, the `agave-unstable-api` crate feature must be specified to \
7 acknowledge use of an interface that may break without warning."
8 )
9)]
10use thiserror::Error;
15
16#[doc(hidden)]
17pub mod legacy;
18
19pub const HASH_BYTES: usize = 32;
21
22#[derive(Error, Debug)]
25pub enum PoseidonSyscallError {
26 #[error("Invalid parameters.")]
27 InvalidParameters,
28 #[error("Invalid endianness.")]
29 InvalidEndianness,
30 #[error("Invalid number of inputs. Maximum allowed is 12.")]
31 InvalidNumberOfInputs,
32 #[error("Input is an empty slice.")]
33 EmptyInput,
34 #[error(
35 "Invalid length of the input. The length matching the modulus of the prime field is 32."
36 )]
37 InvalidInputLength,
38 #[error("Failed to convert bytest into a prime field element.")]
39 BytesToPrimeFieldElement,
40 #[error("Input is larger than the modulus of the prime field.")]
41 InputLargerThanModulus,
42 #[error("Failed to convert a vector of bytes into an array.")]
43 VecToArray,
44 #[error("Failed to convert the number of inputs from u64 to u8.")]
45 U64Tou8,
46 #[error("Failed to convert bytes to BigInt")]
47 BytesToBigInt,
48 #[error("Invalid width. Choose a width between 2 and 16 for 1 to 15 inputs.")]
49 InvalidWidthCircom,
50 #[error("Unexpected error")]
51 Unexpected,
52}
53
54impl From<u64> for PoseidonSyscallError {
55 fn from(error: u64) -> Self {
56 match error {
57 1 => PoseidonSyscallError::InvalidParameters,
58 2 => PoseidonSyscallError::InvalidEndianness,
59 3 => PoseidonSyscallError::InvalidNumberOfInputs,
60 4 => PoseidonSyscallError::EmptyInput,
61 5 => PoseidonSyscallError::InvalidInputLength,
62 6 => PoseidonSyscallError::BytesToPrimeFieldElement,
63 7 => PoseidonSyscallError::InputLargerThanModulus,
64 8 => PoseidonSyscallError::VecToArray,
65 9 => PoseidonSyscallError::U64Tou8,
66 10 => PoseidonSyscallError::BytesToBigInt,
67 11 => PoseidonSyscallError::InvalidWidthCircom,
68 _ => PoseidonSyscallError::Unexpected,
69 }
70 }
71}
72
73impl From<PoseidonSyscallError> for u64 {
74 fn from(error: PoseidonSyscallError) -> Self {
75 match error {
76 PoseidonSyscallError::InvalidParameters => 1,
77 PoseidonSyscallError::InvalidEndianness => 2,
78 PoseidonSyscallError::InvalidNumberOfInputs => 3,
79 PoseidonSyscallError::EmptyInput => 4,
80 PoseidonSyscallError::InvalidInputLength => 5,
81 PoseidonSyscallError::BytesToPrimeFieldElement => 6,
82 PoseidonSyscallError::InputLargerThanModulus => 7,
83 PoseidonSyscallError::VecToArray => 8,
84 PoseidonSyscallError::U64Tou8 => 9,
85 PoseidonSyscallError::BytesToBigInt => 10,
86 PoseidonSyscallError::InvalidWidthCircom => 11,
87 PoseidonSyscallError::Unexpected => 12,
88 }
89 }
90}
91
92#[repr(u64)]
107pub enum Parameters {
108 Bn254X5 = 0,
119}
120
121impl TryFrom<u64> for Parameters {
122 type Error = PoseidonSyscallError;
123
124 fn try_from(value: u64) -> Result<Self, Self::Error> {
125 match value {
126 x if x == Parameters::Bn254X5 as u64 => Ok(Parameters::Bn254X5),
127 _ => Err(PoseidonSyscallError::InvalidParameters),
128 }
129 }
130}
131
132impl From<Parameters> for u64 {
133 fn from(value: Parameters) -> Self {
134 match value {
135 Parameters::Bn254X5 => 0,
136 }
137 }
138}
139
140#[repr(u64)]
142pub enum Endianness {
143 BigEndian = 0,
145 LittleEndian,
147}
148
149impl TryFrom<u64> for Endianness {
150 type Error = PoseidonSyscallError;
151
152 fn try_from(value: u64) -> Result<Self, Self::Error> {
153 match value {
154 x if x == Endianness::BigEndian as u64 => Ok(Endianness::BigEndian),
155 x if x == Endianness::LittleEndian as u64 => Ok(Endianness::LittleEndian),
156 _ => Err(PoseidonSyscallError::InvalidEndianness),
157 }
158 }
159}
160
161impl From<Endianness> for u64 {
162 fn from(value: Endianness) -> Self {
163 match value {
164 Endianness::BigEndian => 0,
165 Endianness::LittleEndian => 1,
166 }
167 }
168}
169
170#[repr(transparent)]
172pub struct PoseidonHash(pub [u8; HASH_BYTES]);
173
174impl PoseidonHash {
175 pub fn new(hash_array: [u8; HASH_BYTES]) -> Self {
176 Self(hash_array)
177 }
178
179 pub fn to_bytes(&self) -> [u8; HASH_BYTES] {
180 self.0
181 }
182}
183
184#[cfg(target_os = "solana")]
185pub use solana_define_syscall::definitions::sol_poseidon;
186
187#[allow(unused_variables)]
219pub fn hashv(
220 parameters: Parameters,
223 endianness: Endianness,
224 vals: &[&[u8]],
225) -> Result<PoseidonHash, PoseidonSyscallError> {
226 #[cfg(not(target_os = "solana"))]
229 {
230 use {
231 ark_bn254::Fr,
232 light_poseidon::{Poseidon, PoseidonBytesHasher, PoseidonError},
233 };
234
235 #[allow(non_local_definitions)]
236 impl From<PoseidonError> for PoseidonSyscallError {
237 fn from(error: PoseidonError) -> Self {
238 match error {
239 PoseidonError::InvalidNumberOfInputs { .. } => {
240 PoseidonSyscallError::InvalidNumberOfInputs
241 }
242 PoseidonError::EmptyInput => PoseidonSyscallError::EmptyInput,
243 PoseidonError::InvalidInputLength { .. } => {
244 PoseidonSyscallError::InvalidInputLength
245 }
246 PoseidonError::BytesToPrimeFieldElement { .. } => {
247 PoseidonSyscallError::BytesToPrimeFieldElement
248 }
249 PoseidonError::InputLargerThanModulus => {
250 PoseidonSyscallError::InputLargerThanModulus
251 }
252 PoseidonError::VecToArray => PoseidonSyscallError::VecToArray,
253 PoseidonError::U64Tou8 => PoseidonSyscallError::U64Tou8,
254 PoseidonError::BytesToBigInt => PoseidonSyscallError::BytesToBigInt,
255 PoseidonError::InvalidWidthCircom { .. } => {
256 PoseidonSyscallError::InvalidWidthCircom
257 }
258 }
259 }
260 }
261
262 let mut hasher =
263 Poseidon::<Fr>::new_circom(vals.len()).map_err(PoseidonSyscallError::from)?;
264 let res = match endianness {
265 Endianness::BigEndian => hasher.hash_bytes_be(vals),
266 Endianness::LittleEndian => hasher.hash_bytes_le(vals),
267 }
268 .map_err(PoseidonSyscallError::from)?;
269
270 Ok(PoseidonHash(res))
271 }
272 #[cfg(target_os = "solana")]
274 {
275 let mut hash_result = [0; HASH_BYTES];
276 let result = unsafe {
277 sol_poseidon(
278 parameters.into(),
279 endianness.into(),
280 vals as *const _ as *const u8,
281 vals.len() as u64,
282 &mut hash_result as *mut _ as *mut u8,
283 )
284 };
285
286 match result {
287 0 => Ok(PoseidonHash::new(hash_result)),
288 _ => Err(PoseidonSyscallError::Unexpected),
289 }
290 }
291}
292
293pub fn hash(
324 parameters: Parameters,
325 endianness: Endianness,
326 val: &[u8],
327) -> Result<PoseidonHash, PoseidonSyscallError> {
328 hashv(parameters, endianness, &[val])
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn test_poseidon_input_ones_be() {
337 let input = [1u8; 32];
338
339 let hash = hash(Parameters::Bn254X5, Endianness::BigEndian, &input).unwrap();
340 assert_eq!(
341 hash.to_bytes(),
342 [
343 5, 191, 172, 229, 129, 238, 97, 119, 204, 25, 198, 197, 99, 99, 166, 136, 130, 241,
344 30, 132, 7, 172, 99, 157, 185, 145, 224, 210, 127, 27, 117, 230
345 ]
346 );
347 }
348
349 #[test]
350 fn test_poseidon_input_ones_le() {
351 let input = [1u8; 32];
352
353 let hash = hash(Parameters::Bn254X5, Endianness::LittleEndian, &input).unwrap();
354 assert_eq!(
355 hash.to_bytes(),
356 [
357 230, 117, 27, 127, 210, 224, 145, 185, 157, 99, 172, 7, 132, 30, 241, 130, 136,
358 166, 99, 99, 197, 198, 25, 204, 119, 97, 238, 129, 229, 172, 191, 5
359 ],
360 );
361 }
362
363 #[test]
364 fn test_poseidon_input_ones_twos_be() {
365 let input1 = [1u8; 32];
366 let input2 = [2u8; 32];
367
368 let hash = hashv(
369 Parameters::Bn254X5,
370 Endianness::BigEndian,
371 &[&input1, &input2],
372 )
373 .unwrap();
374 assert_eq!(
375 hash.to_bytes(),
376 [
377 13, 84, 225, 147, 143, 138, 140, 28, 125, 235, 94, 3, 85, 242, 99, 25, 32, 123,
378 132, 254, 156, 162, 206, 27, 38, 231, 53, 200, 41, 130, 25, 144
379 ]
380 );
381 }
382
383 #[test]
384 fn test_poseidon_input_ones_twos_le() {
385 let input1 = [1u8; 32];
386 let input2 = [2u8; 32];
387
388 let hash = hashv(
389 Parameters::Bn254X5,
390 Endianness::LittleEndian,
391 &[&input1, &input2],
392 )
393 .unwrap();
394 assert_eq!(
395 hash.to_bytes(),
396 [
397 144, 25, 130, 41, 200, 53, 231, 38, 27, 206, 162, 156, 254, 132, 123, 32, 25, 99,
398 242, 85, 3, 94, 235, 125, 28, 140, 138, 143, 147, 225, 84, 13
399 ]
400 );
401 }
402
403 #[test]
404 fn test_poseidon_input_one() {
405 let input = [
406 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
407 0, 0, 1,
408 ];
409
410 let expected_hashes = [
411 [
412 41, 23, 97, 0, 234, 169, 98, 189, 193, 254, 108, 101, 77, 106, 60, 19, 14, 150,
413 164, 209, 22, 139, 51, 132, 139, 137, 125, 197, 2, 130, 1, 51,
414 ],
415 [
416 0, 122, 243, 70, 226, 211, 4, 39, 158, 121, 224, 169, 243, 2, 63, 119, 18, 148,
417 167, 138, 203, 112, 231, 63, 144, 175, 226, 124, 173, 64, 30, 129,
418 ],
419 [
420 2, 192, 6, 110, 16, 167, 42, 189, 43, 51, 195, 178, 20, 203, 62, 129, 188, 177,
421 182, 227, 9, 97, 205, 35, 194, 2, 177, 134, 115, 191, 37, 67,
422 ],
423 [
424 8, 44, 156, 55, 10, 13, 36, 244, 65, 111, 188, 65, 74, 55, 104, 31, 120, 68, 45,
425 39, 216, 99, 133, 153, 28, 23, 214, 252, 12, 75, 125, 113,
426 ],
427 [
428 16, 56, 150, 5, 174, 104, 141, 79, 20, 219, 133, 49, 34, 196, 125, 102, 168, 3,
429 199, 43, 65, 88, 156, 177, 191, 134, 135, 65, 178, 6, 185, 187,
430 ],
431 [
432 42, 115, 246, 121, 50, 140, 62, 171, 114, 74, 163, 229, 189, 191, 80, 179, 144, 53,
433 215, 114, 159, 19, 91, 151, 9, 137, 15, 133, 197, 220, 94, 118,
434 ],
435 [
436 34, 118, 49, 10, 167, 243, 52, 58, 40, 66, 20, 19, 157, 157, 169, 89, 190, 42, 49,
437 178, 199, 8, 165, 248, 25, 84, 178, 101, 229, 58, 48, 184,
438 ],
439 [
440 23, 126, 20, 83, 196, 70, 225, 176, 125, 43, 66, 51, 66, 81, 71, 9, 92, 79, 202,
441 187, 35, 61, 35, 11, 109, 70, 162, 20, 217, 91, 40, 132,
442 ],
443 [
444 14, 143, 238, 47, 228, 157, 163, 15, 222, 235, 72, 196, 46, 187, 68, 204, 110, 231,
445 5, 95, 97, 251, 202, 94, 49, 59, 138, 95, 202, 131, 76, 71,
446 ],
447 [
448 46, 196, 198, 94, 99, 120, 171, 140, 115, 48, 133, 79, 74, 112, 119, 193, 255, 146,
449 96, 228, 72, 133, 196, 184, 29, 209, 49, 173, 58, 134, 205, 150,
450 ],
451 [
452 0, 113, 61, 65, 236, 166, 53, 241, 23, 212, 236, 188, 235, 95, 58, 102, 220, 65,
453 66, 235, 112, 181, 103, 101, 188, 53, 143, 27, 236, 64, 187, 155,
454 ],
455 [
456 20, 57, 11, 224, 186, 239, 36, 155, 212, 124, 101, 221, 172, 101, 194, 229, 46,
457 133, 19, 192, 129, 193, 205, 114, 201, 128, 6, 9, 142, 154, 143, 190,
458 ],
459 ];
460
461 for (i, expected_hash) in expected_hashes.into_iter().enumerate() {
462 let inputs = vec![&input[..]; i + 1];
463 let hash = hashv(Parameters::Bn254X5, Endianness::BigEndian, &inputs).unwrap();
464 assert_eq!(hash.to_bytes(), expected_hash);
465 }
466 }
467
468 #[test]
469 fn test_poseidon_input_without_padding() {
470 let input = [1];
471
472 for i in 1..12 {
473 let inputs = vec![&input[..]; i + 1];
474 let res = hashv(Parameters::Bn254X5, Endianness::BigEndian, &inputs);
475 assert!(res.is_err());
476 let res = hashv(Parameters::Bn254X5, Endianness::LittleEndian, &inputs);
477 assert!(res.is_err());
478 }
479 }
480}