#![allow(clippy::needless_range_loop)]
use std::borrow::Borrow;
use std::borrow::BorrowMut;
use std::ops::Deref;
use p3_baby_bear::{MONTY_INVERSE, POSEIDON2_INTERNAL_MATRIX_DIAG_16_BABYBEAR_MONTY};
use p3_field::AbstractField;
use p3_field::PrimeField32;
pub mod air;
pub mod columns;
pub mod events;
pub mod trace;
use p3_poseidon2::matmul_internal;
use self::columns::Poseidon2;
use self::columns::Poseidon2Degree3;
use self::columns::Poseidon2Degree9;
use self::columns::Poseidon2Mut;
pub const WIDTH: usize = 16;
pub const RATE: usize = WIDTH / 2;
pub const NUM_EXTERNAL_ROUNDS: usize = 8;
pub const NUM_INTERNAL_ROUNDS: usize = 13;
pub const NUM_ROUNDS: usize = NUM_EXTERNAL_ROUNDS + NUM_INTERNAL_ROUNDS;
#[derive(Default)]
pub struct Poseidon2WideChip<const DEGREE: usize> {
pub fixed_log2_rows: Option<usize>,
pub pad: bool,
}
impl<'a, const DEGREE: usize> Poseidon2WideChip<DEGREE> {
pub(crate) fn convert<T>(row: impl Deref<Target = [T]>) -> Box<dyn Poseidon2<'a, T> + 'a>
where
T: Copy + 'a,
{
if DEGREE == 3 {
let convert: &Poseidon2Degree3<T> = (*row).borrow();
Box::new(*convert)
} else if DEGREE == 9 || DEGREE == 17 {
let convert: &Poseidon2Degree9<T> = (*row).borrow();
Box::new(*convert)
} else {
panic!("Unsupported degree");
}
}
pub(crate) fn convert_mut<'b: 'a, F: PrimeField32>(
&self,
row: &'b mut [F],
) -> Box<dyn Poseidon2Mut<'a, F> + 'a> {
if DEGREE == 3 {
let convert: &mut Poseidon2Degree3<F> = row.borrow_mut();
Box::new(convert)
} else if DEGREE == 9 || DEGREE == 17 {
let convert: &mut Poseidon2Degree9<F> = row.borrow_mut();
Box::new(convert)
} else {
panic!("Unsupported degree");
}
}
}
pub fn apply_m_4<AF>(x: &mut [AF])
where
AF: AbstractField,
{
let t01 = x[0].clone() + x[1].clone();
let t23 = x[2].clone() + x[3].clone();
let t0123 = t01.clone() + t23.clone();
let t01123 = t0123.clone() + x[1].clone();
let t01233 = t0123.clone() + x[3].clone();
x[3] = t01233.clone() + x[0].double(); x[1] = t01123.clone() + x[2].double(); x[0] = t01123 + t01; x[2] = t01233 + t23; }
pub(crate) fn external_linear_layer<AF: AbstractField>(state: &mut [AF; WIDTH]) {
for j in (0..WIDTH).step_by(4) {
apply_m_4(&mut state[j..j + 4]);
}
let sums: [AF; 4] = core::array::from_fn(|k| {
(0..WIDTH)
.step_by(4)
.map(|j| state[j + k].clone())
.sum::<AF>()
});
for j in 0..WIDTH {
state[j] += sums[j % 4].clone();
}
}
pub(crate) fn internal_linear_layer<F: AbstractField>(state: &mut [F; WIDTH]) {
let matmul_constants: [<F as AbstractField>::F; WIDTH] =
POSEIDON2_INTERNAL_MATRIX_DIAG_16_BABYBEAR_MONTY
.iter()
.map(|x| <F as AbstractField>::F::from_wrapped_u32(x.as_canonical_u32()))
.collect::<Vec<_>>()
.try_into()
.unwrap();
matmul_internal(state, matmul_constants);
let monty_inverse = F::from_wrapped_u32(MONTY_INVERSE.as_canonical_u32());
state.iter_mut().for_each(|i| *i *= monty_inverse.clone());
}
#[cfg(test)]
pub(crate) mod tests {
use std::array;
use std::time::Instant;
use crate::air::Block;
use crate::memory::MemoryRecord;
use crate::poseidon2_wide::events::Poseidon2HashEvent;
use crate::runtime::{ExecutionRecord, DIGEST_SIZE};
use itertools::Itertools;
use p3_baby_bear::{BabyBear, DiffusionMatrixBabyBear};
use p3_field::AbstractField;
use p3_matrix::dense::RowMajorMatrix;
use p3_poseidon2::{Poseidon2, Poseidon2ExternalMatrixGeneral};
use p3_symmetric::Permutation;
use rand::random;
use sp1_core::air::MachineAir;
use sp1_core::stark::StarkGenericConfig;
use sp1_core::utils::{inner_perm, uni_stark_prove, uni_stark_verify, BabyBearPoseidon2};
use zkhash::ark_ff::UniformRand;
use super::events::{Poseidon2AbsorbEvent, Poseidon2CompressEvent, Poseidon2FinalizeEvent};
use super::{Poseidon2WideChip, WIDTH};
fn poseidon2_wide_prove_babybear_degree<const DEGREE: usize>(
input_exec: ExecutionRecord<BabyBear>,
) {
let chip = Poseidon2WideChip::<DEGREE> {
fixed_log2_rows: None,
pad: true,
};
let trace: RowMajorMatrix<BabyBear> =
chip.generate_trace(&input_exec, &mut ExecutionRecord::<BabyBear>::default());
let config = BabyBearPoseidon2::compressed();
let mut challenger = config.challenger();
let start = Instant::now();
let proof = uni_stark_prove(&config, &chip, &mut challenger, trace);
let duration = start.elapsed().as_secs_f64();
println!("proof duration = {:?}", duration);
let mut challenger = config.challenger();
let start = Instant::now();
uni_stark_verify(&config, &chip, &mut challenger, &proof)
.expect("expected proof to be valid");
let duration = start.elapsed().as_secs_f64();
println!("verify duration = {:?}", duration);
}
fn dummy_memory_access_records(
memory_values: Vec<BabyBear>,
prev_ts: BabyBear,
ts: BabyBear,
) -> Vec<MemoryRecord<BabyBear>> {
memory_values
.iter()
.map(|value| MemoryRecord::new_read(BabyBear::zero(), Block::from(*value), ts, prev_ts))
.collect_vec()
}
pub(crate) fn generate_test_execution_record(
incorrect_trace: bool,
) -> ExecutionRecord<BabyBear> {
const NUM_ABSORBS: usize = 1000;
const NUM_COMPRESSES: usize = 1000;
let mut input_exec = ExecutionRecord::<BabyBear>::default();
let rng = &mut rand::thread_rng();
let permuter: Poseidon2<
BabyBear,
Poseidon2ExternalMatrixGeneral,
DiffusionMatrixBabyBear,
16,
7,
> = inner_perm();
let hash_test_input_sizes: [usize; NUM_ABSORBS] =
array::from_fn(|_| random::<usize>() % 128 + 1);
hash_test_input_sizes
.iter()
.enumerate()
.for_each(|(i, input_size)| {
let test_input = (0..*input_size).map(|_| BabyBear::rand(rng)).collect_vec();
let prev_ts = BabyBear::from_canonical_usize(i);
let absorb_ts = BabyBear::from_canonical_usize(i + 1);
let finalize_ts = BabyBear::from_canonical_usize(i + 2);
let hash_num = i as u32;
let absorb_num = 0_u32;
let hash_and_absorb_num =
BabyBear::from_canonical_u32(hash_num * (1 << 12) + absorb_num);
let start_addr = BabyBear::from_canonical_usize(i + 1);
let input_len = BabyBear::from_canonical_usize(*input_size);
let mut absorb_event = Poseidon2AbsorbEvent::new(
absorb_ts,
hash_and_absorb_num,
start_addr,
input_len,
BabyBear::from_canonical_u32(hash_num),
BabyBear::from_canonical_u32(absorb_num),
);
let mut hash_state = [BabyBear::zero(); WIDTH];
let mut hash_state_cursor = 0;
absorb_event.populate_iterations(
start_addr,
input_len,
&dummy_memory_access_records(test_input.clone(), prev_ts, absorb_ts),
&permuter,
&mut hash_state,
&mut hash_state_cursor,
);
input_exec
.poseidon2_hash_events
.push(Poseidon2HashEvent::Absorb(absorb_event));
let do_perm = hash_state_cursor != 0;
let mut perm_output = permuter.permute(hash_state);
if incorrect_trace {
perm_output = [BabyBear::rand(rng); WIDTH];
}
let state = if do_perm { perm_output } else { hash_state };
input_exec
.poseidon2_hash_events
.push(Poseidon2HashEvent::Finalize(Poseidon2FinalizeEvent {
clk: finalize_ts,
hash_num: BabyBear::from_canonical_u32(hash_num),
output_ptr: start_addr,
output_records: dummy_memory_access_records(
state.as_slice().to_vec(),
absorb_ts,
finalize_ts,
)[0..DIGEST_SIZE]
.try_into()
.unwrap(),
state_cursor: hash_state_cursor,
perm_input: hash_state,
perm_output,
previous_state: hash_state,
state,
do_perm,
}));
});
let compress_test_inputs: Vec<[BabyBear; WIDTH]> = (0..NUM_COMPRESSES)
.map(|_| core::array::from_fn(|_| BabyBear::rand(rng)))
.collect_vec();
compress_test_inputs
.iter()
.enumerate()
.for_each(|(i, input)| {
let mut result_array = permuter.permute(*input);
if incorrect_trace {
result_array = core::array::from_fn(|_| BabyBear::rand(rng));
}
let prev_ts = BabyBear::from_canonical_usize(i);
let input_ts = BabyBear::from_canonical_usize(i + 1);
let output_ts = BabyBear::from_canonical_usize(i + 2);
let dst = BabyBear::from_canonical_usize(i + 1);
let left = dst + BabyBear::from_canonical_usize(WIDTH / 2);
let right = left + BabyBear::from_canonical_usize(WIDTH / 2);
let compress_event = Poseidon2CompressEvent {
clk: input_ts,
dst,
left,
right,
input: *input,
result_array,
input_records: dummy_memory_access_records(input.to_vec(), prev_ts, input_ts)
.try_into()
.unwrap(),
result_records: dummy_memory_access_records(
result_array.to_vec(),
input_ts,
output_ts,
)
.try_into()
.unwrap(),
};
input_exec.poseidon2_compress_events.push(compress_event);
});
input_exec
}
#[test]
fn poseidon2_wide_prove_babybear_success() {
let input_exec = generate_test_execution_record(false);
poseidon2_wide_prove_babybear_degree::<3>(input_exec.clone());
poseidon2_wide_prove_babybear_degree::<9>(input_exec);
}
#[test]
#[should_panic]
fn poseidon2_wide_prove_babybear_failure() {
let input_exec = generate_test_execution_record(true);
poseidon2_wide_prove_babybear_degree::<3>(input_exec.clone());
poseidon2_wide_prove_babybear_degree::<9>(input_exec);
}
}