light_hasher/
hash_to_field_size.rs1use arrayvec::ArrayVec;
2use borsh::BorshSerialize;
3
4use crate::{keccak::Keccak, Hasher, HasherError};
5
6pub const HASH_TO_FIELD_SIZE_SEED: u8 = u8::MAX;
7
8pub trait HashToFieldSize {
9 fn hash_to_field_size(&self) -> Result<[u8; 32], HasherError>;
10}
11
12impl<T> HashToFieldSize for T
13where
14 T: BorshSerialize,
15{
16 fn hash_to_field_size(&self) -> Result<[u8; 32], HasherError> {
17 let borsh_vec = self.try_to_vec().map_err(|_| HasherError::BorshError)?;
18 #[cfg(debug_assertions)]
19 {
20 if std::env::var("RUST_BACKTRACE").is_ok() {
21 println!(
22 "#[hash] hash_to_field_size borsh try_to_vec {:?}",
23 borsh_vec
24 );
25 }
26 }
27 let bump_seed = [HASH_TO_FIELD_SIZE_SEED];
28 let slices = [borsh_vec.as_slice(), bump_seed.as_slice()];
29 let mut hashed_value = Keccak::hashv(slices.as_slice()).unwrap();
31 hashed_value[0] = 0;
34 Ok(hashed_value)
35 }
36}
37
38pub fn hashv_to_bn254_field_size_be(bytes: &[&[u8]]) -> [u8; 32] {
39 let mut slices = Vec::with_capacity(bytes.len() + 1);
40 bytes.iter().for_each(|x| slices.push(*x));
41 let bump_seed = [HASH_TO_FIELD_SIZE_SEED];
42 slices.push(bump_seed.as_slice());
43 let mut hashed_value: [u8; 32] = Keccak::hashv(&slices).unwrap();
45 hashed_value[0] = 0;
48 hashed_value
49}
50
51pub fn hashv_to_bn254_field_size_be_array(bytes: &[[u8; 32]]) -> [u8; 32] {
52 let mut slices = Vec::with_capacity(bytes.len() + 1);
53 bytes.iter().for_each(|x| slices.push(x.as_slice()));
54 let bump_seed = [HASH_TO_FIELD_SIZE_SEED];
55 slices.push(bump_seed.as_slice());
56 let mut hashed_value: [u8; 32] = Keccak::hashv(&slices).unwrap();
57 hashed_value[0] = 0;
60 hashed_value
61}
62
63pub fn hashv_to_bn254_field_size_be_const_array<const MAX_SLICES: usize>(
65 bytes: &[&[u8]],
66) -> Result<[u8; 32], HasherError> {
67 let bump_seed = [HASH_TO_FIELD_SIZE_SEED];
68 let mut slices = ArrayVec::<&[u8], MAX_SLICES>::new();
69 if bytes.len() > MAX_SLICES - 1 {
70 return Err(HasherError::InvalidInputLength(MAX_SLICES, bytes.len()));
71 }
72 bytes.iter().for_each(|x| slices.push(x));
73 slices.push(bump_seed.as_slice());
74 let mut hashed_value: [u8; 32] = Keccak::hashv(&slices)?;
75 hashed_value[0] = 0;
78 Ok(hashed_value)
79}
80
81pub fn hash_to_bn254_field_size_be(bytes: &[u8]) -> [u8; 32] {
92 hashv_to_bn254_field_size_be_const_array::<2>(&[bytes]).unwrap()
93}
94
95#[cfg(not(target_os = "solana"))]
96pub fn is_smaller_than_bn254_field_size_be(bytes: &[u8; 32]) -> bool {
97 use ark_ff::PrimeField;
98 use num_bigint::BigUint;
99 let bigint = BigUint::from_bytes_be(bytes);
100 bigint < ark_bn254::Fr::MODULUS.into()
101}
102
103#[cfg(test)]
104mod tests {
105 use ark_ff::PrimeField;
106 use borsh::BorshDeserialize;
107 use num_bigint::{BigUint, ToBigUint};
108
109 use super::*;
110 use crate::bigint::bigint_to_be_bytes_array;
111
112 #[test]
113 fn test_is_smaller_than_bn254_field_size_be() {
114 let modulus: BigUint = ark_bn254::Fr::MODULUS.into();
115 let modulus_bytes: [u8; 32] = bigint_to_be_bytes_array(&modulus).unwrap();
116 assert!(!is_smaller_than_bn254_field_size_be(&modulus_bytes));
117
118 let bigint = modulus.clone() - 1.to_biguint().unwrap();
119 let bigint_bytes: [u8; 32] = bigint_to_be_bytes_array(&bigint).unwrap();
120 assert!(is_smaller_than_bn254_field_size_be(&bigint_bytes));
121
122 let bigint = modulus + 1.to_biguint().unwrap();
123 let bigint_bytes: [u8; 32] = bigint_to_be_bytes_array(&bigint).unwrap();
124 assert!(!is_smaller_than_bn254_field_size_be(&bigint_bytes));
125 }
126
127 #[test]
128 fn hash_to_field_size_borsh() {
129 #[derive(BorshSerialize, BorshDeserialize)]
130 pub struct TestStruct {
131 a: u32,
132 b: u32,
133 c: u64,
134 }
135 let test_struct = TestStruct { a: 1, b: 2, c: 3 };
136 let serialized = test_struct.try_to_vec().unwrap();
137 let hash = test_struct.hash_to_field_size().unwrap();
138 let manual_hash = hash_to_bn254_field_size_be(&serialized);
139 assert_eq!(hash, manual_hash);
140 }
141
142 #[cfg(feature = "solana")]
143 #[test]
144 fn test_hash_to_bn254_field_size_be() {
145 use solana_pubkey::Pubkey;
146 for _ in 0..10_000 {
147 let input_bytes = Pubkey::new_unique().to_bytes(); let hashed_value = hash_to_bn254_field_size_be(input_bytes.as_slice());
149 assert!(
150 is_smaller_than_bn254_field_size_be(&hashed_value),
151 "Hashed value should be within BN254 field size"
152 );
153 }
154
155 let max_input = [u8::MAX; 32];
156 let hashed_value = hash_to_bn254_field_size_be(max_input.as_slice());
157 assert!(
158 is_smaller_than_bn254_field_size_be(&hashed_value),
159 "Hashed value should be within BN254 field size"
160 );
161 }
162
163 #[cfg(feature = "solana")]
164 #[test]
165 fn test_hashv_to_bn254_field_size_be() {
166 use solana_pubkey::Pubkey;
167 for _ in 0..10_000 {
168 let input_bytes = [Pubkey::new_unique().to_bytes(); 4];
169 let input_bytes = input_bytes.iter().map(|x| x.as_slice()).collect::<Vec<_>>();
170 let hashed_value = hashv_to_bn254_field_size_be(input_bytes.as_slice());
171 assert!(
172 is_smaller_than_bn254_field_size_be(&hashed_value),
173 "Hashed value should be within BN254 field size"
174 );
175 }
176
177 let max_input = [[u8::MAX; 32]; 16];
178 let max_input = max_input.iter().map(|x| x.as_slice()).collect::<Vec<_>>();
179 let hashed_value = hashv_to_bn254_field_size_be(max_input.as_slice());
180 assert!(
181 is_smaller_than_bn254_field_size_be(&hashed_value),
182 "Hashed value should be within BN254 field size"
183 );
184 }
185}