Skip to content

Commit 25f0a77

Browse files
committed
hasher: Enforce input length to match the modulus
To avoid misunderstandings related to padding in Poseidon hash inputs, enforce them to have size 32, which is the number of bytes in `Fr` modulus.
1 parent 50778df commit 25f0a77

File tree

9 files changed

+44
-33
lines changed

9 files changed

+44
-33
lines changed

program-libs/compressed-account/src/compressed_account.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ impl ZCompressedAccount<'_> {
378378
#[cfg(not(feature = "pinocchio"))]
379379
#[cfg(test)]
380380
mod tests {
381-
use light_hasher::Poseidon;
381+
use light_hasher::{to_byte_array::ToByteArray, Poseidon};
382382
use light_zero_copy::borsh::Deserialize;
383383
use num_bigint::BigUint;
384384
use rand::Rng;
@@ -750,7 +750,7 @@ mod tests {
750750
Some(CompressedAccountData {
751751
discriminator: rng.gen(),
752752
data: Vec::new(), // not used in hash
753-
data_hash: Poseidon::hash(rng.gen::<u64>().to_be_bytes().as_slice())
753+
data_hash: Poseidon::hash(&rng.get::<u64>().to_byte_array().unwrap())
754754
.unwrap(),
755755
})
756756
} else {

program-libs/hasher/src/poseidon.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ impl Hasher for Poseidon {
8383
}
8484

8585
fn hashv(vals: &[&[u8]]) -> Result<Hash, HasherError> {
86+
for val in vals {
87+
if val.len() != 32 {
88+
return Err(HasherError::InvalidInputLength(32, val.len()));
89+
}
90+
}
91+
8692
// Perform the calculation inline, calling this from within a program is
8793
// not supported.
8894
#[cfg(not(target_os = "solana"))]
@@ -99,13 +105,6 @@ impl Hasher for Poseidon {
99105
#[cfg(target_os = "solana")]
100106
{
101107
use crate::HASH_BYTES;
102-
// TODO: reenable once LightHasher refactor is merged
103-
// solana_program::msg!("remove len check onchain.");
104-
// for val in vals {
105-
// if val.len() != 32 {
106-
// return Err(HasherError::InvalidInputLength(val.len()));
107-
// }
108-
// }
109108
let mut hash_result = [0; HASH_BYTES];
110109
let result = unsafe {
111110
crate::syscalls::sol_poseidon(

program-libs/hasher/src/to_byte_array.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ impl_to_byte_array_for_integer_type!(i32);
6767
impl_to_byte_array_for_integer_type!(u32);
6868
impl_to_byte_array_for_integer_type!(i64);
6969
impl_to_byte_array_for_integer_type!(u64);
70+
impl_to_byte_array_for_integer_type!(isize);
71+
impl_to_byte_array_for_integer_type!(usize);
7072
impl_to_byte_array_for_integer_type!(i128);
7173
impl_to_byte_array_for_integer_type!(u128);
7274

program-tests/create-address-test-program/src/create_pda.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,10 @@ pub struct RegisteredUser {
133133
impl light_hasher::DataHasher for RegisteredUser {
134134
fn hash<H: light_hasher::Hasher>(&self) -> std::result::Result<[u8; 32], HasherError> {
135135
let truncated_user_pubkey = hash_to_bn254_field_size_be(&self.user_pubkey.to_bytes());
136+
let mut data = [0u8; 32];
137+
data[1..].copy_from_slice(&self.data);
136138

137-
H::hashv(&[truncated_user_pubkey.as_slice(), self.data.as_slice()])
139+
H::hashv(&[truncated_user_pubkey.as_slice(), data.as_slice()])
138140
}
139141
}
140142

program-tests/system-cpi-test/src/create_pda.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,9 @@ pub struct RegisteredUser {
543543
impl light_hasher::DataHasher for RegisteredUser {
544544
fn hash<H: light_hasher::Hasher>(&self) -> std::result::Result<[u8; 32], HasherError> {
545545
let truncated_user_pubkey = hash_to_bn254_field_size_be(&self.user_pubkey.to_bytes());
546+
let mut data = [0u8; 32];
547+
data[1..].copy_from_slice(&self.data);
548+
546549
H::hashv(&[truncated_user_pubkey.as_slice(), self.data.as_slice()])
547550
}
548551
}

program-tests/utils/src/mock_batched_forester.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use light_compressed_account::{
22
hash_chain::create_hash_chain_from_slice, instruction_data::compressed_proof::CompressedProof,
33
};
4-
use light_hasher::{bigint::bigint_to_be_bytes_array, Hasher, Poseidon};
4+
use light_hasher::{
5+
bigint::bigint_to_be_bytes_array, to_byte_array::ToByteArray, Hasher, Poseidon,
6+
};
57
use light_merkle_tree_reference::{indexed::IndexedMerkleTree, MerkleTree};
68
use light_prover_client::{
79
errors::ProverClientError,
@@ -186,7 +188,7 @@ impl<const HEIGHT: usize> MockBatchedForester<HEIGHT> {
186188
.iter()
187189
.find(|tx_event| tx_event.inputs.contains(leaf))
188190
.expect("No event for leaf found.");
189-
let index_bytes = index.to_be_bytes();
191+
let index_bytes = index.to_byte_array().unwrap();
190192
let nullifier = Poseidon::hashv(&[leaf, &index_bytes, &event.tx_hash]).unwrap();
191193
tx_hashes.push(event.tx_hash);
192194
nullifiers.push(nullifier);

program-tests/utils/src/test_batch_forester.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ use light_compressed_account::{
2525
hash_chain::create_hash_chain_from_slice, instruction_data::compressed_proof::CompressedProof,
2626
QueueType,
2727
};
28-
use light_hasher::{bigint::bigint_to_be_bytes_array, Poseidon};
28+
use light_hasher::{
29+
bigint::bigint_to_be_bytes_array, to_byte_array::ToByteArray, Hasher, Poseidon,
30+
};
2931
use light_prover_client::{
3032
proof_client::ProofClient,
3133
proof_types::{
@@ -269,8 +271,7 @@ pub async fn get_batched_nullify_ix_data<R: Rpc>(
269271
let proof = bundle.merkle_tree.get_proof_of_leaf(index, true).unwrap();
270272
merkle_proofs.push(proof.to_vec());
271273
bundle.input_leaf_indices.remove(0);
272-
let index_bytes = index.to_be_bytes();
273-
use light_hasher::Hasher;
274+
let index_bytes = index.to_byte_array().unwrap();
274275
let nullifier = Poseidon::hashv(&[&leaf, &index_bytes, &leaf_info.tx_hash]).unwrap();
275276

276277
tx_hashes.push(leaf_info.tx_hash);

prover/client/src/proof_types/batch_update/proof_inputs.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use light_hasher::{hash_chain::create_hash_chain_from_array, Hasher, Poseidon};
1+
use light_hasher::{
2+
hash_chain::create_hash_chain_from_array, to_byte_array::ToByteArray, Hasher, Poseidon,
3+
};
24
use light_sparse_merkle_tree::changelog::ChangelogEntry;
35
use num_bigint::{BigInt, Sign};
46

@@ -91,7 +93,7 @@ pub fn get_batch_update_inputs<const HEIGHT: usize>(
9193
let merkle_proof_array = merkle_proof.try_into().unwrap();
9294

9395
// Use the adjusted index bytes for computing the nullifier.
94-
let index_bytes = (*index).to_be_bytes();
96+
let index_bytes = index.to_byte_array().unwrap();
9597
let nullifier = Poseidon::hashv(&[leaf, &index_bytes, &tx_hashes[i]]).unwrap();
9698
let (root, changelog_entry) =
9799
compute_root_from_merkle_proof(nullifier, &merkle_proof_array, *index);

sdk-libs/macros/tests/hasher.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ mod basic_hashing {
141141
let account = create_account(Some(42));
142142

143143
let manual_nested_bytes: Vec<Vec<u8>> = vec![
144-
nested_struct.a.to_be_bytes().to_vec(),
145-
nested_struct.b.to_be_bytes().to_vec(),
144+
nested_struct.a.to_byte_array().unwrap().to_vec(),
145+
nested_struct.b.to_byte_array().unwrap().to_vec(),
146146
light_compressed_account::hash_to_bn254_field_size_be(
147147
nested_struct.c.try_to_vec().unwrap().as_slice(),
148148
)
@@ -163,8 +163,8 @@ mod basic_hashing {
163163
assert_eq!(nested_hash_result, manual_nested_hash);
164164

165165
let manual_account_bytes: Vec<Vec<u8>> = vec![
166-
vec![u8::from(account.a)],
167-
account.b.to_be_bytes().to_vec(),
166+
account.a.to_byte_array().unwrap().to_vec(),
167+
account.b.to_byte_array().unwrap().to_vec(),
168168
account.c.hash::<Poseidon>().unwrap().to_vec(),
169169
light_compressed_account::hash_to_bn254_field_size_be(&account.d).to_vec(),
170170
{
@@ -495,18 +495,18 @@ fn test_poseidon_width_limits() {
495495

496496
assert!(max_fields.hash::<Poseidon>().is_ok());
497497
let expected_hash = Poseidon::hashv(&[
498-
1u64.to_be_bytes().as_ref(),
499-
2u64.to_be_bytes().as_ref(),
500-
3u64.to_be_bytes().as_ref(),
501-
4u64.to_be_bytes().as_ref(),
502-
5u64.to_be_bytes().as_ref(),
503-
6u64.to_be_bytes().as_ref(),
504-
7u64.to_be_bytes().as_ref(),
505-
8u64.to_be_bytes().as_ref(),
506-
9u64.to_be_bytes().as_ref(),
507-
10u64.to_be_bytes().as_ref(),
508-
11u64.to_be_bytes().as_ref(),
509-
12u64.to_be_bytes().as_ref(),
498+
1u64.to_byte_array().unwrap().as_ref(),
499+
2u64.to_byte_array().unwrap().as_ref(),
500+
3u64.to_byte_array().unwrap().as_ref(),
501+
4u64.to_byte_array().unwrap().as_ref(),
502+
5u64.to_byte_array().unwrap().as_ref(),
503+
6u64.to_byte_array().unwrap().as_ref(),
504+
7u64.to_byte_array().unwrap().as_ref(),
505+
8u64.to_byte_array().unwrap().as_ref(),
506+
9u64.to_byte_array().unwrap().as_ref(),
507+
10u64.to_byte_array().unwrap().as_ref(),
508+
11u64.to_byte_array().unwrap().as_ref(),
509+
12u64.to_byte_array().unwrap().as_ref(),
510510
])
511511
.unwrap();
512512
assert_eq!(max_fields.hash::<Poseidon>().unwrap(), expected_hash);

0 commit comments

Comments
 (0)