light_hasher/
hash_to_field_size.rs

1use 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        // SAFETY: cannot panic Hasher::hashv returns an error because Poseidon can panic.
30        let mut hashed_value = Keccak::hashv(slices.as_slice()).unwrap();
31        // Truncates to 31 bytes so that value is less than bn254 Fr modulo
32        // field size.
33        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    // SAFETY: cannot panic Hasher::hashv returns an error because Poseidon can panic.
44    let mut hashed_value: [u8; 32] = Keccak::hashv(&slices).unwrap();
45    // Truncates to 31 bytes so that value is less than bn254 Fr modulo
46    // field size.
47    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    // Truncates to 31 bytes so that value is less than bn254 Fr modulo
58    // field size.
59    hashed_value[0] = 0;
60    hashed_value
61}
62
63/// MAX_SLICES - 1 is usable.
64pub 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    // Truncates to 31 bytes so that value is less than bn254 Fr modulo
76    // field size.
77    hashed_value[0] = 0;
78    Ok(hashed_value)
79}
80
81/// Hashes the provided `bytes` with Keccak256 and ensures the result fits
82/// in the BN254 field by truncating the resulting hash to 31 bytes.
83///
84/// # Examples
85///
86/// ```
87/// use light_hasher::hash_to_field_size::hashv_to_bn254_field_size_be;
88///
89/// hashv_to_bn254_field_size_be(&[&[0u8;32][..]]);
90/// ```
91pub 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(); // Sample input
148            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}