use std::time::Instant;
use ff::Field;
use midnight_aggregation::ivc::{self, IvcContext, IvcIO, IvcState, IvcTransition};
use midnight_circuits::{
hash::poseidon::PoseidonChip,
instructions::{hash::HashCPU, *},
types::AssignedNative,
verifier::{BlstrsEmulation, SelfEmulation},
};
use midnight_proofs::{
circuit::{Layouter, Value},
plonk::Error,
};
use midnight_zk_stdlib::{ZkStdLib, ZkStdLibArch};
type S = BlstrsEmulation;
type F = <S as SelfEmulation>::F;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct State {
cnt: F,
val: F,
}
#[derive(Clone, Debug)]
pub struct AssignedState {
cnt: AssignedNative<F>,
val: AssignedNative<F>,
}
#[derive(Clone, Debug)]
pub struct PoseidonChain<const N: usize> {
std_lib: ZkStdLib,
}
impl<const N: usize> IvcContext for PoseidonChain<N> {
type Context = ();
fn new(std_lib: ZkStdLib, _ctx: &()) -> Self {
PoseidonChain { std_lib }
}
fn write_context<W: std::io::Write>(_ctx: &(), _writer: &mut W) -> std::io::Result<()> {
Ok(())
}
fn read_context<R: std::io::Read>(_reader: &mut R) -> std::io::Result<()> {
Ok(())
}
}
impl<const N: usize> IvcState for PoseidonChain<N> {
type State = State;
type AssignedState = AssignedState;
fn genesis(_ctx: &()) -> Self::State {
State {
cnt: F::ZERO,
val: F::ZERO,
}
}
fn decider(_ctx: &Self::Context, _state: &Self::State) -> bool {
true
}
}
impl<const N: usize> IvcIO for PoseidonChain<N> {
fn assign(
&self,
layouter: &mut impl Layouter<F>,
value: Value<State>,
) -> Result<AssignedState, Error> {
let scalar_chip = self.std_lib.bls12_381().scalar_field_chip();
Ok(AssignedState {
cnt: scalar_chip.assign(layouter, value.as_ref().map(|s| s.cnt))?,
val: scalar_chip.assign(layouter, value.as_ref().map(|s| s.val))?,
})
}
fn constrain_as_public_input(
&self,
layouter: &mut impl Layouter<F>,
state: &AssignedState,
) -> Result<(), Error> {
let scalar_chip = self.std_lib.bls12_381().scalar_field_chip();
scalar_chip.constrain_as_public_input(layouter, &state.cnt)?;
scalar_chip.constrain_as_public_input(layouter, &state.val)
}
fn as_public_input(
&self,
_layouter: &mut impl Layouter<F>,
state: &AssignedState,
) -> Result<Vec<AssignedNative<F>>, Error> {
Ok(vec![state.cnt.clone(), state.val.clone()])
}
fn format_public_input(state: &State) -> Vec<F> {
vec![state.cnt, state.val]
}
}
impl<const N: usize> IvcTransition for PoseidonChain<N> {
type Witness = ();
fn arch() -> ZkStdLibArch {
ZkStdLibArch {
poseidon: true,
nr_pow2range_cols: 4,
..ZkStdLibArch::default()
}
}
fn transition(_ctx: &(), state: &Self::State, _witness: Self::Witness) -> Self::State {
let mut val = state.val;
for _ in 0..N {
val = <PoseidonChip<F> as HashCPU<F, F>>::hash(&[val]);
}
State {
cnt: state.cnt + F::from(N as u64),
val,
}
}
fn circuit_transition(
&self,
layouter: &mut impl Layouter<F>,
state: &Self::AssignedState,
_witness: Value<Self::Witness>,
) -> Result<Self::AssignedState, Error> {
let scalar_chip = self.std_lib.bls12_381().scalar_field_chip();
let mut val = state.val.clone();
for _ in 0..N {
val = self.std_lib.poseidon(layouter, &[val])?;
}
let cnt = scalar_chip.add_constant(layouter, &state.cnt, F::from(N as u64))?;
Ok(AssignedState { cnt, val })
}
}
fn main() {
const K: u32 = 18;
const N: usize = 1_000; const STEPS: usize = 3;
let srs = midnight_zk_stdlib::utils::plonk_api::filecoin_srs(K);
let start = Instant::now();
let (mut prover, verifier) = ivc::setup::<PoseidonChain<N>>(srs, K, ());
println!("IVC setup completed in {:.2?}", start.elapsed());
for i in 0..STEPS {
let start = Instant::now();
let proof = prover.prove_step(()).unwrap();
let prove_time = start.elapsed();
let instance = prover.instance();
let start = Instant::now();
verifier.verify(&instance, &proof).unwrap();
let verify_time = start.elapsed();
println!("Step {i}: prove {prove_time:.2?}, verify {verify_time:.2?}");
}
println!(
"IVC completed: {STEPS} steps from\n genesis {:?}\n to {:?}",
PoseidonChain::<N>::genesis(&()),
prover.instance().state()
);
}