use crate::api::FieldElement;
use crate::poseidon::{domain_tags, poseidon_hash_tagged};
use ff::PrimeField;
pub fn bytes31_to_field_le<F: PrimeField>(bytes31: &[u8]) -> F {
debug_assert!(bytes31.len() <= 31);
let mut repr = <F as PrimeField>::Repr::default();
let buf = repr.as_mut();
buf[..bytes31.len()].copy_from_slice(bytes31);
F::from_repr(repr).expect("31-byte chunks should always fit in the field")
}
pub fn field_to_bytes31_le<F: PrimeField>(element: &F) -> [u8; 31] {
let repr = element.to_repr();
let bytes = repr.as_ref();
let mut out = [0u8; 31];
out.copy_from_slice(&bytes[..31]);
out
}
pub fn derive_index_from_bits<F: ff::PrimeField>(hash: F, depth: usize) -> usize {
if depth == 0 {
return 0;
}
let repr = hash.to_repr();
let bytes = repr.as_ref();
let mut idx: usize = 0;
let mut bits_taken = 0;
let mut byte_i = 0;
let mut bit_i = 0;
while bits_taken < depth {
let b = *bytes.get(byte_i).unwrap_or(&0);
let bit = (b >> bit_i) & 1;
idx |= (bit as usize) << bits_taken;
bits_taken += 1;
bit_i += 1;
if bit_i == 8 {
bit_i = 0;
byte_i += 1;
}
}
idx
}
pub fn leaf_to_bytes31<F: ff::PrimeField>(leaf: &F) -> [u8; 31] {
field_to_bytes31_le(leaf)
}
pub fn derive_index_unbiased(hash: FieldElement, leaf_count: usize) -> usize {
assert!(leaf_count > 0, "leaf_count must be positive");
if leaf_count.is_power_of_two() {
let depth = leaf_count.trailing_zeros() as usize;
return derive_index_from_bits(hash, depth);
}
const MAX_REJECTION_SAMPLES: u64 = 1000;
let bound = leaf_count.next_power_of_two();
let bits_needed = bound.trailing_zeros() as usize;
let mut sample = hash;
let mut counter: u64 = 0;
while counter < MAX_REJECTION_SAMPLES {
let candidate = derive_index_from_bits(sample, bits_needed);
if candidate < leaf_count {
return candidate;
}
counter += 1;
let ctr_fe = FieldElement::from(counter);
sample = poseidon_hash_tagged(domain_tags::challenge(), sample, ctr_fe);
}
let final_candidate = derive_index_from_bits(sample, bits_needed);
final_candidate % leaf_count
}
pub fn derive_leaf_index_for_file(
file_idx: usize,
file_depth: usize,
seed: FieldElement,
local_state: FieldElement,
is_multi_file: bool,
) -> usize {
let ch = poseidon_hash_tagged(domain_tags::challenge(), seed, local_state);
let per_file = if is_multi_file {
poseidon_hash_tagged(
domain_tags::challenge_per_file(),
ch,
FieldElement::from(file_idx as u64),
)
} else {
ch
};
derive_index_unbiased(per_file, 1usize << file_depth)
}