use std::collections::BTreeMap;
use ff::Field;
use group::Group;
use midnight_proofs::{
circuit::{Layouter, Value},
plonk::Error,
poly::{
kzg::{
msm::{DualMSM, MSMKZG},
params::ParamsVerifierKZG,
},
CommitmentLabel,
},
};
use num_bigint::BigUint;
use num_traits::One;
#[cfg(not(feature = "truncated-challenges"))]
use crate::verifier::utils::powers;
#[cfg(feature = "truncated-challenges")]
use crate::verifier::utils::{truncate_off_circuit, truncated_powers};
use crate::{
instructions::{hash::HashCPU, HashInstructions, PublicInputInstructions},
types::{AssignedBit, InnerValue, Instantiable},
verifier::{
fixed_commitment_name,
msm::{AssignedMsm, Msm},
perm_commitment_name,
utils::AssignedBoundedScalar,
SelfEmulation,
},
};
#[derive(Clone, Debug)]
pub struct Accumulator<S: SelfEmulation> {
lhs: Msm<S>,
rhs: Msm<S>,
}
#[derive(Clone, Debug)]
pub struct AssignedAccumulator<C: SelfEmulation> {
pub(crate) lhs: AssignedMsm<C>,
pub(crate) rhs: AssignedMsm<C>,
}
impl<S: SelfEmulation> Accumulator<S> {
pub fn from_dual_msm(
dual_msm: DualMSM<S::Engine>,
prefix: &str,
fixed_bases: &BTreeMap<String, S::C>,
) -> Self {
let (lhs, rhs) = dual_msm.split();
let process_msm = |msm: Vec<(&CommitmentLabel, &S::F, &S::C)>| {
let mut bases = Vec::with_capacity(msm.len());
let mut scalars = Vec::with_capacity(msm.len());
let mut fixed_base_scalars = BTreeMap::new();
for (label, scalar, base) in msm {
match label {
CommitmentLabel::Fixed(i) => {
let name = fixed_commitment_name(prefix, *i);
assert_eq!(fixed_bases.get(&name), Some(base));
fixed_base_scalars.insert(name, *scalar);
}
CommitmentLabel::Permutation(i) => {
let name = perm_commitment_name(prefix, *i);
assert_eq!(fixed_bases.get(&name), Some(base));
fixed_base_scalars.insert(name, *scalar);
}
CommitmentLabel::Custom(s) if s == "-G" => {
assert_eq!(fixed_bases.get(s), Some(base));
fixed_base_scalars.insert("-G".into(), *scalar);
}
_ => {
bases.push(*base);
scalars.push(*scalar);
}
}
}
(bases, scalars, fixed_base_scalars)
};
let (lhs_bases, lhs_scalars, lhs_fixed_base_scalars) = process_msm(lhs);
let (rhs_bases, rhs_scalars, rhs_fixed_base_scalars) = process_msm(rhs);
Accumulator {
lhs: Msm::new(&lhs_bases, &lhs_scalars, &lhs_fixed_base_scalars),
rhs: Msm::new(&rhs_bases, &rhs_scalars, &rhs_fixed_base_scalars),
}
}
pub fn check(
&self,
params: &ParamsVerifierKZG<S::Engine>,
fixed_bases: &BTreeMap<String, S::C>,
) -> bool {
let lhs = MSMKZG::<S::Engine>::from_base(&self.lhs.eval(fixed_bases));
let rhs = MSMKZG::<S::Engine>::from_base(&self.rhs.eval(fixed_bases));
DualMSM::new(lhs, rhs).check(params)
}
pub fn trivial(fixed_base_names: &[String]) -> Self {
let zero_fixed = fixed_base_names.iter().map(|n| (n.clone(), S::F::ZERO)).collect();
Accumulator {
lhs: Msm::new(&[S::C::identity()], &[S::F::ONE], &BTreeMap::new()),
rhs: Msm::new(&[S::C::identity()], &[S::F::ONE], &zero_fixed),
}
}
pub fn new(lhs: Msm<S>, rhs: Msm<S>) -> Self {
Accumulator { lhs, rhs }
}
pub fn lhs(&self) -> Msm<S> {
self.lhs.clone()
}
pub fn rhs(&self) -> Msm<S> {
self.rhs.clone()
}
pub fn resolve_fixed_bases(&mut self, fixed_bases: &BTreeMap<String, S::C>) {
self.lhs.resolve_fixed_bases(fixed_bases);
self.rhs.resolve_fixed_bases(fixed_bases);
}
pub fn collapse(&mut self) {
self.lhs.collapse();
self.rhs.collapse();
}
pub fn accumulate(accs: &[Self]) -> Self {
let hash_input =
accs.iter().flat_map(AssignedAccumulator::as_public_input).collect::<Vec<_>>();
let r = <S::SpongeChip as HashCPU<S::F, S::F>>::hash(&hash_input);
let rs = (0..accs.len()).map(|i| r.pow([i as u64]));
#[cfg(feature = "truncated-challenges")]
let rs = rs.map(truncate_off_circuit).collect::<Vec<_>>();
let mut acc = accs[0].clone();
for (other, ri) in accs.iter().zip(rs).skip(1) {
acc.lhs = acc.lhs.accumulate_with_r(&other.lhs, ri);
acc.rhs = acc.rhs.accumulate_with_r(&other.rhs, ri);
}
acc
}
}
impl<S: SelfEmulation> InnerValue for AssignedAccumulator<S> {
type Element = Accumulator<S>;
fn value(&self) -> Value<Accumulator<S>> {
(self.lhs.value())
.zip(self.rhs.value())
.map(|(lhs, rhs)| Accumulator { lhs, rhs })
}
}
impl<S: SelfEmulation> Instantiable<S::F> for AssignedAccumulator<S> {
fn as_public_input(acc: &Accumulator<S>) -> Vec<S::F> {
[
AssignedMsm::as_public_input(&acc.lhs),
AssignedMsm::as_public_input(&acc.rhs),
]
.into_iter()
.flatten()
.collect()
}
}
impl<S: SelfEmulation> AssignedAccumulator<S> {
pub fn as_public_input_with_committed_scalars(acc: &Accumulator<S>) -> (Vec<S::F>, Vec<S::F>) {
let (rhs_scalars, rhs_committed_scalars) =
AssignedMsm::as_public_input_with_committed_scalars(&acc.rhs);
let normal_instance = [AssignedMsm::as_public_input(&acc.lhs), rhs_scalars]
.into_iter()
.flatten()
.collect();
(normal_instance, rhs_committed_scalars)
}
}
impl<S: SelfEmulation> AssignedAccumulator<S> {
#[allow(clippy::too_many_arguments)]
pub fn assign(
layouter: &mut impl Layouter<S::F>,
curve_chip: &S::CurveChip,
scalar_chip: &S::ScalarChip,
lhs_len: usize,
rhs_len: usize,
lhs_fixed_base_names: &[String],
rhs_fixed_base_names: &[String],
acc_val: Value<Accumulator<S>>,
) -> Result<Self, Error> {
let (acc_lhs_val, acc_rhs_val) = acc_val.map(|acc| (acc.lhs, acc.rhs)).unzip();
Ok(AssignedAccumulator::new(
AssignedMsm::<S>::assign(
layouter,
curve_chip,
scalar_chip,
lhs_len,
lhs_fixed_base_names,
acc_lhs_val,
)?,
AssignedMsm::<S>::assign(
layouter,
curve_chip,
scalar_chip,
rhs_len,
rhs_fixed_base_names,
acc_rhs_val,
)?,
))
}
pub fn new(lhs: AssignedMsm<S>, rhs: AssignedMsm<S>) -> Self {
Self { lhs, rhs }
}
pub fn scale_by_bit(
layouter: &mut impl Layouter<S::F>,
scalar_chip: &S::ScalarChip,
cond: &AssignedBit<S::F>,
acc: &mut Self,
) -> Result<(), Error> {
let cond_as_bounded = AssignedBoundedScalar {
scalar: cond.clone().into(),
bound: BigUint::one(),
};
acc.lhs.scale(layouter, scalar_chip, &cond_as_bounded)?;
acc.rhs.scale(layouter, scalar_chip, &cond_as_bounded)
}
pub fn collapse(
&mut self,
layouter: &mut impl Layouter<S::F>,
curve_chip: &S::CurveChip,
scalar_chip: &S::ScalarChip,
) -> Result<(), Error> {
self.lhs.collapse(layouter, curve_chip, scalar_chip)?;
self.rhs.collapse(layouter, curve_chip, scalar_chip)
}
pub fn resolve_fixed_bases(&mut self, fixed_bases: &BTreeMap<String, S::AssignedPoint>) {
self.lhs.resolve_fixed_bases(fixed_bases);
self.rhs.resolve_fixed_bases(fixed_bases);
}
pub fn accumulate(
layouter: &mut impl Layouter<S::F>,
acc_pi_chip: &impl PublicInputInstructions<S::F, AssignedAccumulator<S>>,
scalar_chip: &S::ScalarChip,
sponge_chip: &S::SpongeChip,
accs: &[Self],
) -> Result<Self, Error> {
let hash_input = accs
.iter()
.map(|acc| acc_pi_chip.as_public_input(layouter, acc))
.collect::<Result<Vec<_>, Error>>()?
.into_iter()
.flatten()
.collect::<Vec<_>>();
let r = sponge_chip.hash(layouter, &hash_input)?;
#[cfg(feature = "truncated-challenges")]
let rs = truncated_powers::<S::F>(layouter, scalar_chip, &r, accs.len())?;
#[cfg(not(feature = "truncated-challenges"))]
let rs = powers::<S::F>(layouter, scalar_chip, &r, accs.len())?
.iter()
.map(|ri| AssignedBoundedScalar::new(ri, None))
.collect::<Vec<_>>();
let mut acc = accs[0].clone();
for (other, ri) in accs.iter().zip(rs).skip(1) {
acc.lhs = acc.lhs.accumulate_with_r(layouter, scalar_chip, &other.lhs, &ri)?;
acc.rhs = acc.rhs.accumulate_with_r(layouter, scalar_chip, &other.rhs, &ri)?;
}
Ok(acc)
}
}