use ark_ff::PrimeField;
use num_bigint::BigUint;
struct PoseidonGrainLFSR {
pub prime_num_bits: u64,
pub state: [bool; 80],
pub head: usize,
}
impl PoseidonGrainLFSR {
fn new(
is_field: u64,
is_sbox_an_inverse: u64,
prime_num_bits: u64,
state_len: u64,
num_full_rounds: u64,
num_partial_rounds: u64,
) -> Self {
let mut state = [false; 80];
assert!(is_field == 1);
state[1] = is_field == 1;
assert!(is_sbox_an_inverse == 0 || is_sbox_an_inverse == 1);
state[5] = is_sbox_an_inverse == 1;
{
let mut cur = prime_num_bits;
for i in (6..=17).rev() {
state[i] = cur & 1 == 1;
cur >>= 1;
}
}
{
let mut cur = state_len;
for i in (18..=29).rev() {
state[i] = cur & 1 == 1;
cur >>= 1;
}
}
{
let mut cur = num_full_rounds;
for i in (30..=39).rev() {
state[i] = cur & 1 == 1;
cur >>= 1;
}
}
{
let mut cur = num_partial_rounds;
for i in (40..=49).rev() {
state[i] = cur & 1 == 1;
cur >>= 1;
}
}
for item in state.iter_mut().skip(50) {
*item = true;
}
let head = 0;
let mut res = Self {
prime_num_bits,
state,
head,
};
res.init();
res
}
fn get_bits(&mut self, num_bits: usize) -> Vec<bool> {
let mut res = Vec::new();
for _ in 0..num_bits {
let mut new_bit = self.update();
while !new_bit {
let _ = self.update();
new_bit = self.update();
}
res.push(self.update());
}
res
}
fn get_field_elements_rejection_sampling<F: PrimeField>(&mut self, num_elems: usize) -> Vec<F> {
assert_eq!(F::MODULUS_BIT_SIZE as u64, self.prime_num_bits);
let modulus: BigUint = F::MODULUS.into();
let mut res = Vec::new();
for _ in 0..num_elems {
loop {
let mut bits = self.get_bits(self.prime_num_bits as usize);
bits.reverse();
let bytes = bits
.chunks(8)
.map(|chunk| {
let mut result = 0u8;
for (i, bit) in chunk.iter().enumerate() {
result |= u8::from(*bit) << i
}
result
})
.collect::<Vec<u8>>();
let value = BigUint::from_bytes_le(&bytes);
if value < modulus {
res.push(F::from(value.clone()));
break;
}
}
}
res
}
fn get_field_elements_mod_p<F: PrimeField>(&mut self, num_elems: usize) -> Vec<F> {
assert_eq!(F::MODULUS_BIT_SIZE as u64, self.prime_num_bits);
let mut res = Vec::new();
for _ in 0..num_elems {
let mut bits = self.get_bits(self.prime_num_bits as usize);
bits.reverse();
let bytes = bits
.chunks(8)
.map(|chunk| {
let mut result = 0u8;
for (i, bit) in chunk.iter().enumerate() {
result |= u8::from(*bit) << i
}
result
})
.collect::<Vec<u8>>();
res.push(F::from_le_bytes_mod_order(&bytes));
}
res
}
#[inline]
fn update(&mut self) -> bool {
let new_bit = self.state[(self.head + 62) % 80]
^ self.state[(self.head + 51) % 80]
^ self.state[(self.head + 38) % 80]
^ self.state[(self.head + 23) % 80]
^ self.state[(self.head + 13) % 80]
^ self.state[self.head];
self.state[self.head] = new_bit;
self.head += 1;
self.head %= 80;
new_bit
}
fn init(&mut self) {
for _ in 0..160 {
let new_bit = self.state[(self.head + 62) % 80]
^ self.state[(self.head + 51) % 80]
^ self.state[(self.head + 38) % 80]
^ self.state[(self.head + 23) % 80]
^ self.state[(self.head + 13) % 80]
^ self.state[self.head];
self.state[self.head] = new_bit;
self.head += 1;
self.head %= 80;
}
}
}
pub fn find_poseidon_ark_and_mds<F: PrimeField>(
is_field: u64,
is_sbox_an_inverse: u64,
prime_bits: u64,
rate: usize,
full_rounds: u64,
partial_rounds: u64,
skip_matrices: usize,
) -> (Vec<F>, Vec<Vec<F>>) {
let mut lfsr = PoseidonGrainLFSR::new(
is_field,
is_sbox_an_inverse,
prime_bits,
rate as u64,
full_rounds,
partial_rounds,
);
let mut ark = Vec::<F>::with_capacity((full_rounds + partial_rounds) as usize);
for _ in 0..(full_rounds + partial_rounds) {
let values = lfsr.get_field_elements_rejection_sampling::<F>(rate);
for el in values {
ark.push(el);
}
}
let mut mds = Vec::<Vec<F>>::with_capacity(rate);
mds.resize(rate, vec![F::zero(); rate]);
for _ in 0..skip_matrices {
let _ = lfsr.get_field_elements_mod_p::<F>(2 * (rate));
}
let xs = lfsr.get_field_elements_mod_p::<F>(rate);
let ys = lfsr.get_field_elements_mod_p::<F>(rate);
for i in 0..(rate) {
for (j, ys_item) in ys.iter().enumerate().take(rate) {
mds[i][j] = (xs[i] + ys_item)
.inverse()
.expect("MDS matrix inverse must be valid");
}
}
(ark, mds)
}
#[cfg(test)]
mod test {
use ark_bn254::Fr;
use num_traits::Zero;
use super::*;
#[test]
fn test_find_poseidon_ark_and_mds_bn254_regression_no_inverse_panic() {
let result = std::panic::catch_unwind(|| {
find_poseidon_ark_and_mds::<Fr>(1, 0, 254, 2, 8, 56, 0)
});
assert!(
result.is_ok(),
"find_poseidon_ark_and_mds unexpectedly panicked (possible MDS inverse invariant break)"
);
let (ark, mds) = result.unwrap();
assert_eq!(ark.len(), (8 + 56) * 2);
assert_eq!(mds.len(), 2);
assert_eq!(mds[0].len(), 2);
assert_eq!(mds[1].len(), 2);
assert_ne!(mds[0][0], Fr::zero());
}
}