use std::collections::BTreeMap;
use ff::Field;
use group::Group;
use midnight_circuits::{
hash::poseidon::{PoseidonChip, PoseidonState},
instructions::{hash::HashCPU, *},
types::{AssignedNative, Instantiable},
verifier::{self, Accumulator, AssignedAccumulator},
};
use midnight_proofs::{
circuit::{Layouter, Value},
plonk::{self, ConstraintSystem, Error},
poly::{
kzg::{params::ParamsVerifierKZG, KZGCommitmentScheme},
EvaluationDomain,
},
transcript::{CircuitTranscript, Transcript},
utils::SerdeFormat,
};
use midnight_zk_stdlib::{ZkStdLib, ZkStdLibArch};
use super::aggregator::AggregationWitness;
use crate::{
ivc::{IvcContext, IvcIO, IvcState, IvcTransition, C, E, F, S},
multi_circuit_aggregator::{
utils::{assign_and_hash_vk, compute_vk_hash},
Claim,
},
};
#[derive(Clone, Debug)]
pub struct State {
claims: Vec<Claim>,
claims_hash: F,
inner_acc: Accumulator<S>,
}
impl State {
pub fn claims(&self) -> &[Claim] {
&self.claims
}
}
#[derive(Clone, Debug)]
pub struct AssignedState {
claims_hash: AssignedNative<F>,
inner_acc: AssignedAccumulator<S>,
}
#[derive(Clone, Debug)]
pub struct InnerCircuitsContext {
cs: ConstraintSystem<F>,
domain: EvaluationDomain<F>,
params_verifier: ParamsVerifierKZG<E>,
arch: ZkStdLibArch,
}
impl InnerCircuitsContext {
pub fn new(arch: ZkStdLibArch, k: u32, params_verifier: ParamsVerifierKZG<E>) -> Self {
let mut cs = ConstraintSystem::default();
ZkStdLib::configure(&mut cs, (arch, (k - 1) as u8));
let domain = EvaluationDomain::new(cs.degree() as u32, k);
InnerCircuitsContext {
cs,
domain,
params_verifier,
arch,
}
}
pub fn arch(&self) -> ZkStdLibArch {
self.arch
}
}
#[derive(Clone, Debug)]
pub struct ProofAggregation {
std_lib: ZkStdLib,
inner_ctx: InnerCircuitsContext,
}
impl IvcContext for ProofAggregation {
type Context = InnerCircuitsContext;
fn new(std_lib: ZkStdLib, ctx: &InnerCircuitsContext) -> Self {
ProofAggregation {
std_lib,
inner_ctx: ctx.clone(),
}
}
fn write_context<W: std::io::Write>(
ctx: &InnerCircuitsContext,
writer: &mut W,
) -> std::io::Result<()> {
ctx.arch.write(writer)?;
writer.write_all(&ctx.domain.k().to_le_bytes())?;
ctx.params_verifier.write(writer, SerdeFormat::RawBytes)
}
fn read_context<R: std::io::Read>(reader: &mut R) -> std::io::Result<InnerCircuitsContext> {
let arch = ZkStdLibArch::read(reader)?;
let mut k_bytes = [0u8; 4];
reader.read_exact(&mut k_bytes)?;
let k = u32::from_le_bytes(k_bytes);
let params_verifier = ParamsVerifierKZG::read(reader, SerdeFormat::RawBytes)?;
Ok(InnerCircuitsContext::new(arch, k, params_verifier))
}
}
impl IvcState for ProofAggregation {
type State = State;
type AssignedState = AssignedState;
fn genesis(_ctx: &InnerCircuitsContext) -> Self::State {
State {
claims: vec![],
claims_hash: F::ZERO,
inner_acc: Accumulator::<S>::trivial(&[]),
}
}
fn decider(ctx: &InnerCircuitsContext, state: &State) -> bool {
let claims_hash = state.claims.iter().fold(F::ZERO, |h_acc, claim| {
let vk_hash = compute_vk_hash(&claim.vk);
let statement = claim.statement.format_instance();
<PoseidonChip<F> as HashCPU<F, F>>::hash(&[vk_hash, statement, h_acc])
});
if claims_hash != state.claims_hash {
return false;
}
state.inner_acc.check(&ctx.params_verifier, &BTreeMap::new())
}
}
impl IvcIO for ProofAggregation {
fn assign(
&self,
layouter: &mut impl Layouter<F>,
value: Value<State>,
) -> Result<AssignedState, Error> {
let claims_hash = self.std_lib.assign(layouter, value.as_ref().map(|s| s.claims_hash))?;
let inner_acc = self.std_lib.verifier().assign_collapsed_accumulator(
layouter,
&[],
value.as_ref().map(|s| s.inner_acc.clone()),
)?;
Ok(AssignedState {
claims_hash,
inner_acc,
})
}
fn constrain_as_public_input(
&self,
layouter: &mut impl Layouter<F>,
state: &AssignedState,
) -> Result<(), Error> {
self.std_lib.constrain_as_public_input(layouter, &state.claims_hash)?;
self.std_lib.verifier().constrain_as_public_input(layouter, &state.inner_acc)
}
fn as_public_input(
&self,
layouter: &mut impl Layouter<F>,
state: &AssignedState,
) -> Result<Vec<AssignedNative<F>>, Error> {
Ok([
self.std_lib.as_public_input(layouter, &state.claims_hash)?,
self.std_lib.verifier().as_public_input(layouter, &state.inner_acc)?,
]
.concat())
}
fn format_public_input(state: &State) -> Vec<F> {
[
vec![state.claims_hash],
AssignedAccumulator::<S>::as_public_input(&state.inner_acc),
]
.concat()
}
}
impl IvcTransition for ProofAggregation {
type Witness = AggregationWitness;
fn arch() -> ZkStdLibArch {
ZkStdLibArch {
poseidon: true,
nr_pow2range_cols: 4,
..ZkStdLibArch::default()
}
}
fn transition(
ctx: &InnerCircuitsContext,
state: &Self::State,
witness: Self::Witness,
) -> Self::State {
let vk_hash = compute_vk_hash(&witness.claim.vk);
let statement = witness.claim.statement.format_instance();
let inner_proof_acc = {
let mut transcript =
CircuitTranscript::<PoseidonState<F>>::init_from_bytes(&witness.inner_proof);
let dual_msm =
plonk::prepare::<F, KZGCommitmentScheme<E>, CircuitTranscript<PoseidonState<F>>>(
witness.claim.vk.vk(),
&[&[C::identity()]],
&[&[&[statement]]],
&mut transcript,
)
.expect("off-circuit prepare should succeed");
assert!(
dual_msm.clone().check(&ctx.params_verifier),
"invalid inner proof"
);
let vk_bases = verifier::fixed_bases::<S>("inner_vk", witness.claim.vk.vk());
let mut acc = Accumulator::from_dual_msm(dual_msm, "inner_vk", &vk_bases);
acc.collapse();
acc.resolve_fixed_bases(&vk_bases);
acc
};
let inner_acc = {
let mut acc = Accumulator::accumulate(&[inner_proof_acc, state.inner_acc.clone()]);
acc.collapse();
acc
};
let claims_hash =
<PoseidonChip<F> as HashCPU<F, F>>::hash(&[vk_hash, statement, state.claims_hash]);
let mut claims = state.claims.clone();
claims.push(witness.claim);
State {
claims,
claims_hash,
inner_acc,
}
}
fn circuit_transition(
&self,
layouter: &mut impl Layouter<F>,
state: &Self::AssignedState,
witness: Value<Self::Witness>,
) -> Result<Self::AssignedState, Error> {
let (vk_hash, fixed_bases_map) = assign_and_hash_vk(
layouter,
&self.std_lib,
&self.inner_ctx.cs,
witness.as_ref().map(|w| &w.claim.vk),
)?;
let statement = self.std_lib.assign(
layouter,
witness.as_ref().map(|w| w.claim.statement.format_instance()),
)?;
let inner_proof_acc = {
let acc_value = witness.map(|w| {
let mut transcript =
CircuitTranscript::<PoseidonState<F>>::init_from_bytes(&w.inner_proof);
let dual_msm = plonk::prepare::<
F,
KZGCommitmentScheme<E>,
CircuitTranscript<PoseidonState<F>>,
>(
w.claim.vk.vk(),
&[&[C::identity()]],
&[&[&[w.claim.statement.format_instance()]]],
&mut transcript,
)
.expect("off-circuit prepare should succeed");
let vk_bases = verifier::fixed_bases::<S>("inner_vk", w.claim.vk.vk());
let mut acc = Accumulator::from_dual_msm(dual_msm, "inner_vk", &vk_bases);
acc.collapse();
acc
});
let mut acc = self.std_lib.verifier().assign_collapsed_accumulator(
layouter,
&fixed_bases_map.keys().cloned().collect::<Vec<_>>(),
acc_value,
)?;
acc.resolve_fixed_bases(&fixed_bases_map);
acc
};
let inner_acc = {
let mut acc = self
.std_lib
.verifier()
.accumulate(layouter, &[inner_proof_acc, state.inner_acc.clone()])?;
acc.collapse(
layouter,
self.std_lib.bls12_381(),
self.std_lib.bls12_381().scalar_field_chip(),
)?;
acc
};
let claims_hash = self
.std_lib
.poseidon(layouter, &[vk_hash, statement, state.claims_hash.clone()])?;
Ok(AssignedState {
claims_hash,
inner_acc,
})
}
}