use p3_field::PrimeField64;
use p3_util::relatively_prime_u64;
use thiserror::Error;
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum Poseidon2RoundNumbersError {
#[error(
"Poseidon2 S-box exponent {exponent} is not coprime to the field order minus one ({field_order_minus_one})"
)]
InvalidSboxExponent {
exponent: u64,
field_order_minus_one: u64,
},
#[error(
"no precomputed Poseidon2 round count for a {field_bits}-bit field with width {width} and S-box exponent {exponent}"
)]
UnsupportedWidthAndExponent {
field_bits: u32,
width: usize,
exponent: u64,
},
#[error("no precomputed Poseidon2 round counts for a {field_bits}-bit field")]
UnsupportedFieldSize {
field_bits: u32,
},
}
const FULL_ROUNDS_128: usize = 8;
pub const fn poseidon2_round_numbers_128<F: PrimeField64>(
width: usize,
d: u64,
) -> Result<(usize, usize), Poseidon2RoundNumbersError> {
let field_order_minus_one = F::ORDER_U64 - 1;
if !relatively_prime_u64(d, field_order_minus_one) {
return Err(Poseidon2RoundNumbersError::InvalidSboxExponent {
exponent: d,
field_order_minus_one,
});
}
let prime_bit_number = F::ORDER_U64.ilog2() + 1;
match prime_bit_number {
31 => match (width, d) {
(16, 3) => Ok((FULL_ROUNDS_128, 20)),
(16, 5) => Ok((FULL_ROUNDS_128, 14)),
(16, 7) => Ok((FULL_ROUNDS_128, 13)),
(16, 9) => Ok((FULL_ROUNDS_128, 13)),
(16, 11) => Ok((FULL_ROUNDS_128, 13)),
(24, 3) => Ok((FULL_ROUNDS_128, 23)),
(24, 5) => Ok((FULL_ROUNDS_128, 22)),
(24, 7) => Ok((FULL_ROUNDS_128, 21)),
(24, 9) => Ok((FULL_ROUNDS_128, 21)),
(24, 11) => Ok((FULL_ROUNDS_128, 21)),
(32, 3) => Ok((FULL_ROUNDS_128, 31)),
(32, 5) => Ok((FULL_ROUNDS_128, 30)),
(32, 7) => Ok((FULL_ROUNDS_128, 30)),
(32, 9) => Ok((FULL_ROUNDS_128, 30)),
(32, 11) => Ok((FULL_ROUNDS_128, 30)),
_ => Err(Poseidon2RoundNumbersError::UnsupportedWidthAndExponent {
field_bits: prime_bit_number,
width,
exponent: d,
}),
},
64 => match (width, d) {
(8, 3) => Ok((FULL_ROUNDS_128, 41)),
(8, 5) => Ok((FULL_ROUNDS_128, 27)),
(8, 7) => Ok((FULL_ROUNDS_128, 22)),
(8, 9) => Ok((FULL_ROUNDS_128, 19)),
(8, 11) => Ok((FULL_ROUNDS_128, 17)),
(12, 3) => Ok((FULL_ROUNDS_128, 42)),
(12, 5) => Ok((FULL_ROUNDS_128, 27)),
(12, 7) => Ok((FULL_ROUNDS_128, 22)),
(12, 9) => Ok((FULL_ROUNDS_128, 20)),
(12, 11) => Ok((FULL_ROUNDS_128, 18)),
(16, 3) => Ok((FULL_ROUNDS_128, 42)),
(16, 5) => Ok((FULL_ROUNDS_128, 27)),
(16, 7) => Ok((FULL_ROUNDS_128, 22)),
(16, 9) => Ok((FULL_ROUNDS_128, 20)),
(16, 11) => Ok((FULL_ROUNDS_128, 18)),
_ => Err(Poseidon2RoundNumbersError::UnsupportedWidthAndExponent {
field_bits: prime_bit_number,
width,
exponent: d,
}),
},
_ => Err(Poseidon2RoundNumbersError::UnsupportedFieldSize {
field_bits: prime_bit_number,
}),
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::string::ToString;
use p3_baby_bear::BabyBear;
use p3_field::PrimeField64;
use super::{Poseidon2RoundNumbersError, poseidon2_round_numbers_128};
#[test]
fn invalid_sbox_exponent_reports_the_exponent_and_group_order() {
let error = poseidon2_round_numbers_128::<BabyBear>(16, 2).unwrap_err();
assert_eq!(
error,
Poseidon2RoundNumbersError::InvalidSboxExponent {
exponent: 2,
field_order_minus_one: BabyBear::ORDER_U64 - 1,
}
);
assert_eq!(
error.to_string(),
format!(
"Poseidon2 S-box exponent 2 is not coprime to the field order minus one ({})",
BabyBear::ORDER_U64 - 1
)
);
}
#[test]
fn unsupported_parameters_report_every_table_key() {
let error = poseidon2_round_numbers_128::<BabyBear>(15, 7).unwrap_err();
assert_eq!(
error,
Poseidon2RoundNumbersError::UnsupportedWidthAndExponent {
field_bits: 31,
width: 15,
exponent: 7,
}
);
assert_eq!(
error.to_string(),
"no precomputed Poseidon2 round count for a 31-bit field with width 15 and S-box exponent 7"
);
}
#[test]
fn unsupported_field_size_message_reports_the_table_key() {
let error = Poseidon2RoundNumbersError::UnsupportedFieldSize { field_bits: 48 };
assert_eq!(
error.to_string(),
"no precomputed Poseidon2 round counts for a 48-bit field"
);
}
}