Move permutation keygen into plonk::permutation::keygen

This commit is contained in:
Jack Grigg 2020-12-01 02:09:03 +00:00
parent f63f3ff2af
commit 66240800a3
7 changed files with 332 additions and 214 deletions

View File

@ -8,7 +8,7 @@
use crate::arithmetic::CurveAffine;
use crate::poly::{
commitment::ChallengeScalar, multiopen, Coeff, EvaluationDomain, ExtendedLagrangeCoeff,
LagrangeCoeff, Polynomial,
Polynomial,
};
mod circuit;
@ -28,7 +28,7 @@ pub use verifier::*;
pub struct VerifyingKey<C: CurveAffine> {
domain: EvaluationDomain<C::Scalar>,
fixed_commitments: Vec<C>,
permutation_commitments: Vec<Vec<C>>,
permutations: Vec<permutation::VerifyingKey<C>>,
cs: ConstraintSystem<C::Scalar>,
}
@ -41,9 +41,7 @@ pub struct ProvingKey<C: CurveAffine> {
l0: Polynomial<C::Scalar, ExtendedLagrangeCoeff>,
fixed_polys: Vec<Polynomial<C::Scalar, Coeff>>,
fixed_cosets: Vec<Polynomial<C::Scalar, ExtendedLagrangeCoeff>>,
permutations: Vec<Vec<Polynomial<C::Scalar, LagrangeCoeff>>>,
permutation_polys: Vec<Vec<Polynomial<C::Scalar, Coeff>>>,
permutation_cosets: Vec<Vec<Polynomial<C::Scalar, ExtendedLagrangeCoeff>>>,
permutations: Vec<permutation::ProvingKey<C>>,
}
/// This is an object which represents a (Turbo)PLONK proof.

View File

@ -4,7 +4,7 @@ use ff::Field;
use std::collections::BTreeMap;
use std::convert::TryFrom;
use super::Error;
use super::{permutation, Error};
use crate::poly::Rotation;
/// A column type
@ -312,7 +312,7 @@ pub struct ConstraintSystem<F> {
// Vector of permutation arguments, where each corresponds to a sequence of columns
// that are involved in a permutation argument.
pub(crate) permutations: Vec<Vec<Column<Advice>>>,
pub(crate) permutations: Vec<permutation::Argument>,
}
impl<F: Field> Default for ConstraintSystem<F> {
@ -347,7 +347,8 @@ impl<F: Field> ConstraintSystem<F> {
for column in columns {
self.query_advice_index(*column, 0);
}
self.permutations.push(columns.to_vec());
self.permutations
.push(permutation::Argument::new(columns.to_vec()));
index
}

View File

@ -2,9 +2,9 @@ use ff::Field;
use super::{
circuit::{Advice, Assignment, Circuit, Column, ConstraintSystem, Fixed},
Error, ProvingKey, VerifyingKey,
permutation, Error, ProvingKey, VerifyingKey,
};
use crate::arithmetic::{Curve, CurveAffine, FieldExt};
use crate::arithmetic::{Curve, CurveAffine};
use crate::poly::{
commitment::{Blind, Params},
EvaluationDomain, LagrangeCoeff, Polynomial, Rotation,
@ -21,9 +21,7 @@ where
{
struct Assembly<F: Field> {
fixed: Vec<Polynomial<F, LagrangeCoeff>>,
mapping: Vec<Vec<Vec<(usize, usize)>>>,
aux: Vec<Vec<Vec<(usize, usize)>>>,
sizes: Vec<Vec<Vec<usize>>>,
permutations: permutation::keygen::Assembly,
_marker: std::marker::PhantomData<F>,
}
@ -61,62 +59,22 @@ where
right_column: usize,
right_row: usize,
) -> Result<(), Error> {
// Check bounds first
if permutation >= self.mapping.len()
|| left_column >= self.mapping[permutation].len()
|| left_row >= self.mapping[permutation][left_column].len()
|| right_column >= self.mapping[permutation].len()
|| right_row >= self.mapping[permutation][right_column].len()
{
return Err(Error::BoundsFailure);
}
let mut left_cycle = self.aux[permutation][left_column][left_row];
let mut right_cycle = self.aux[permutation][right_column][right_row];
if left_cycle == right_cycle {
return Ok(());
}
if self.sizes[permutation][left_cycle.0][left_cycle.1]
< self.sizes[permutation][right_cycle.0][right_cycle.1]
{
std::mem::swap(&mut left_cycle, &mut right_cycle);
}
self.sizes[permutation][left_cycle.0][left_cycle.1] +=
self.sizes[permutation][right_cycle.0][right_cycle.1];
let mut i = right_cycle;
loop {
self.aux[permutation][i.0][i.1] = left_cycle;
i = self.mapping[permutation][i.0][i.1];
if i == right_cycle {
break;
}
}
let tmp = self.mapping[permutation][left_column][left_row];
self.mapping[permutation][left_column][left_row] =
self.mapping[permutation][right_column][right_row];
self.mapping[permutation][right_column][right_row] = tmp;
Ok(())
self.permutations
.copy(permutation, left_column, left_row, right_column, right_row)
}
}
let mut cs = ConstraintSystem::default();
let config = ConcreteCircuit::configure(&mut cs);
// Get the largest permutation argument length in terms of the number of
// advice columns involved.
let mut largest_permutation_length = 0;
for permutation in &cs.permutations {
largest_permutation_length = std::cmp::max(permutation.len(), largest_permutation_length);
}
// The permutation argument will serve alongside the gates, so must be
// accounted for.
let mut degree = largest_permutation_length + 1;
let mut degree = cs
.permutations
.iter()
.map(|p| p.required_degree())
.max()
.unwrap_or(1);
// Account for each gate to ensure our quotient polynomial is the
// correct degree and that our extended domain is the right size.
@ -126,95 +84,16 @@ where
let domain = EvaluationDomain::new(degree as u32, params.k);
// Compute [omega^0, omega^1, ..., omega^{params.n - 1}]
let mut omega_powers = Vec::with_capacity(params.n as usize);
{
let mut cur = C::Scalar::one();
for _ in 0..params.n {
omega_powers.push(cur);
cur *= &domain.get_omega();
}
}
// Compute [omega_powers * \delta^0, omega_powers * \delta^1, ..., omega_powers * \delta^m]
let mut deltaomega = Vec::with_capacity(largest_permutation_length);
{
let mut cur = C::Scalar::one();
for _ in 0..largest_permutation_length {
let mut omega_powers = omega_powers.clone();
for o in &mut omega_powers {
*o *= &cur;
}
deltaomega.push(omega_powers);
cur *= &C::Scalar::DELTA;
}
}
let mut assembly: Assembly<C::Scalar> = Assembly {
fixed: vec![domain.empty_lagrange(); cs.num_fixed_columns],
mapping: vec![],
aux: vec![],
sizes: vec![],
permutations: permutation::keygen::Assembly::new(params, &cs),
_marker: std::marker::PhantomData,
};
// Initialize the copy vector to keep track of copy constraints in all
// the permutation arguments.
for permutation in &cs.permutations {
let mut columns = vec![];
for i in 0..permutation.len() {
// Computes [(i, 0), (i, 1), ..., (i, n - 1)]
columns.push((0..params.n).map(|j| (i, j as usize)).collect());
}
assembly.mapping.push(columns.clone());
assembly.aux.push(columns);
assembly
.sizes
.push(vec![vec![1usize; params.n as usize]; permutation.len()]);
}
// Synthesize the circuit to obtain SRS
circuit.synthesize(&mut assembly, config)?;
// Compute permutation polynomials, convert to coset form and
// pre-compute commitments for the SRS.
let mut permutation_commitments = vec![];
let mut permutations = vec![];
let mut permutation_polys = vec![];
let mut permutation_cosets = vec![];
for (permutation_index, permutation) in cs.permutations.iter().enumerate() {
let mut commitments = vec![];
let mut inner_permutations = vec![];
let mut polys = vec![];
let mut cosets = vec![];
for i in 0..permutation.len() {
// Computes the permutation polynomial based on the permutation
// description in the assembly.
let mut permutation_poly = domain.empty_lagrange();
for (j, p) in permutation_poly.iter_mut().enumerate() {
let (permuted_i, permuted_j) = assembly.mapping[permutation_index][i][j];
*p = deltaomega[permuted_i][permuted_j];
}
// Compute commitment to permutation polynomial
commitments.push(
params
.commit_lagrange(&permutation_poly, Blind::default())
.to_affine(),
);
// Store permutation polynomial and precompute its coset evaluation
inner_permutations.push(permutation_poly.clone());
let poly = domain.lagrange_to_coeff(permutation_poly);
polys.push(poly.clone());
cosets.push(domain.coeff_to_extended(poly, Rotation::default()));
}
permutation_commitments.push(commitments);
permutations.push(inner_permutations);
permutation_polys.push(polys);
permutation_cosets.push(cosets);
}
let (permutation_pks, permutation_vks) = assembly.permutations.build_keys(params, &cs, &domain);
let fixed_commitments = assembly
.fixed
@ -248,14 +127,12 @@ where
vk: VerifyingKey {
domain,
fixed_commitments,
permutation_commitments,
permutations: permutation_vks,
cs,
},
l0,
fixed_polys,
fixed_cosets,
permutations,
permutation_polys,
permutation_cosets,
permutations: permutation_pks,
})
}

View File

@ -1,10 +1,48 @@
//! Implementation of a PLONK permutation argument.
use crate::arithmetic::CurveAffine;
use super::circuit::{Advice, Column};
use crate::{
arithmetic::CurveAffine,
poly::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial},
};
pub(crate) mod keygen;
mod prover;
mod verifier;
/// A permutation argument.
#[derive(Debug, Clone)]
pub(crate) struct Argument {
/// A sequence of columns involved in the argument.
columns: Vec<Column<Advice>>,
}
impl Argument {
pub(crate) fn new(columns: Vec<Column<Advice>>) -> Self {
Argument { columns }
}
pub(crate) fn required_degree(&self) -> usize {
// The permutation argument will serve alongside the gates, so must be
// accounted for.
self.columns.len() + 1
}
}
/// The verifying key for a single permutation argument.
#[derive(Debug)]
pub(crate) struct VerifyingKey<C: CurveAffine> {
commitments: Vec<C>,
}
/// The proving key for a single permutation argument.
#[derive(Debug)]
pub(crate) struct ProvingKey<C: CurveAffine> {
permutations: Vec<Polynomial<C::Scalar, LagrangeCoeff>>,
polys: Vec<Polynomial<C::Scalar, Coeff>>,
cosets: Vec<Polynomial<C::Scalar, ExtendedLagrangeCoeff>>,
}
#[derive(Debug, Clone)]
pub(crate) struct Proof<C: CurveAffine> {
permutation_product_commitments: Vec<C>,

View File

@ -0,0 +1,179 @@
use ff::Field;
use super::{ProvingKey, VerifyingKey};
use crate::{
arithmetic::{Curve, CurveAffine, FieldExt},
plonk::{circuit::ConstraintSystem, Error},
poly::{
commitment::{Blind, Params},
EvaluationDomain, Rotation,
},
};
pub(crate) struct Assembly {
mapping: Vec<Vec<Vec<(usize, usize)>>>,
aux: Vec<Vec<Vec<(usize, usize)>>>,
sizes: Vec<Vec<Vec<usize>>>,
}
impl Assembly {
pub(crate) fn new<C: CurveAffine>(
params: &Params<C>,
cs: &ConstraintSystem<C::Scalar>,
) -> Self {
let mut assembly = Assembly {
mapping: vec![],
aux: vec![],
sizes: vec![],
};
// Initialize the copy vector to keep track of copy constraints in all
// the permutation arguments.
for p in &cs.permutations {
let mut columns = vec![];
for i in 0..p.columns.len() {
// Computes [(i, 0), (i, 1), ..., (i, n - 1)]
columns.push((0..params.n).map(|j| (i, j as usize)).collect());
}
assembly.mapping.push(columns.clone());
assembly.aux.push(columns);
assembly
.sizes
.push(vec![vec![1usize; params.n as usize]; p.columns.len()]);
}
assembly
}
pub(crate) fn copy(
&mut self,
permutation: usize,
left_column: usize,
left_row: usize,
right_column: usize,
right_row: usize,
) -> Result<(), Error> {
// Check bounds first
if permutation >= self.mapping.len()
|| left_column >= self.mapping[permutation].len()
|| left_row >= self.mapping[permutation][left_column].len()
|| right_column >= self.mapping[permutation].len()
|| right_row >= self.mapping[permutation][right_column].len()
{
return Err(Error::BoundsFailure);
}
let mut left_cycle = self.aux[permutation][left_column][left_row];
let mut right_cycle = self.aux[permutation][right_column][right_row];
if left_cycle == right_cycle {
return Ok(());
}
if self.sizes[permutation][left_cycle.0][left_cycle.1]
< self.sizes[permutation][right_cycle.0][right_cycle.1]
{
std::mem::swap(&mut left_cycle, &mut right_cycle);
}
self.sizes[permutation][left_cycle.0][left_cycle.1] +=
self.sizes[permutation][right_cycle.0][right_cycle.1];
let mut i = right_cycle;
loop {
self.aux[permutation][i.0][i.1] = left_cycle;
i = self.mapping[permutation][i.0][i.1];
if i == right_cycle {
break;
}
}
let tmp = self.mapping[permutation][left_column][left_row];
self.mapping[permutation][left_column][left_row] =
self.mapping[permutation][right_column][right_row];
self.mapping[permutation][right_column][right_row] = tmp;
Ok(())
}
pub(crate) fn build_keys<C: CurveAffine>(
self,
params: &Params<C>,
cs: &ConstraintSystem<C::Scalar>,
domain: &EvaluationDomain<C::Scalar>,
) -> (Vec<ProvingKey<C>>, Vec<VerifyingKey<C>>) {
// Get the largest permutation argument length in terms of the number of
// advice columns involved.
let largest_permutation_length = cs
.permutations
.iter()
.map(|p| p.columns.len())
.max()
.unwrap_or_default();
// Compute [omega^0, omega^1, ..., omega^{params.n - 1}]
let mut omega_powers = Vec::with_capacity(params.n as usize);
{
let mut cur = C::Scalar::one();
for _ in 0..params.n {
omega_powers.push(cur);
cur *= &domain.get_omega();
}
}
// Compute [omega_powers * \delta^0, omega_powers * \delta^1, ..., omega_powers * \delta^m]
let mut deltaomega = Vec::with_capacity(largest_permutation_length);
{
let mut cur = C::Scalar::one();
for _ in 0..largest_permutation_length {
let mut omega_powers = omega_powers.clone();
for o in &mut omega_powers {
*o *= &cur;
}
deltaomega.push(omega_powers);
cur *= &C::Scalar::DELTA;
}
}
// Compute permutation polynomials, convert to coset form and
// pre-compute commitments for the SRS.
let mut pks = vec![];
let mut vks = vec![];
for (p, mapping) in cs.permutations.iter().zip(self.mapping.iter()) {
let mut commitments = vec![];
let mut permutations = vec![];
let mut polys = vec![];
let mut cosets = vec![];
for i in 0..p.columns.len() {
// Computes the permutation polynomial based on the permutation
// description in the assembly.
let mut permutation_poly = domain.empty_lagrange();
for (j, p) in permutation_poly.iter_mut().enumerate() {
let (permuted_i, permuted_j) = mapping[i][j];
*p = deltaomega[permuted_i][permuted_j];
}
// Compute commitment to permutation polynomial
commitments.push(
params
.commit_lagrange(&permutation_poly, Blind::default())
.to_affine(),
);
// Store permutation polynomial and precompute its coset evaluation
permutations.push(permutation_poly.clone());
let poly = domain.lagrange_to_coeff(permutation_poly);
polys.push(poly.clone());
cosets.push(domain.coeff_to_extended(poly, Rotation::default()));
}
vks.push(VerifyingKey { commitments });
pks.push(ProvingKey {
permutations,
polys,
cosets,
});
}
(pks, vks)
}
}

View File

@ -67,11 +67,12 @@ impl<C: CurveAffine> Proof<C> {
//
// where p_j(X) is the jth advice column in this permutation,
// and i is the ith row of the column.
.map(|(columns, permuted_values)| {
.map(|(p, pkey)| {
let mut modified_advice = vec![C::Scalar::one(); params.n as usize];
// Iterate over each column of the permutation
for (&column, permuted_column_values) in columns.iter().zip(permuted_values.iter())
for (&column, permuted_column_values) in
p.columns.iter().zip(pkey.permutations.iter())
{
parallelize(&mut modified_advice, |modified_advice, start| {
for ((modified_advice, advice_value), permuted_advice_value) in
@ -97,7 +98,7 @@ impl<C: CurveAffine> Proof<C> {
.flat_map(|v| v.iter_mut())
.batch_invert();
for (columns, mut modified_advice) in pk
for (p, mut modified_advice) in pk
.vk
.cs
.permutations
@ -107,7 +108,7 @@ impl<C: CurveAffine> Proof<C> {
// Iterate over each column again, this time finishing the computation
// of the entire fraction by computing the numerators
let mut deltaomega = C::Scalar::one();
for &column in columns.iter() {
for &column in p.columns.iter() {
let omega = domain.get_omega();
parallelize(&mut modified_advice, |modified_advice, start| {
let mut deltaomega = deltaomega * &omega.pow_vartime(&[start as u64, 0, 0, 0]);
@ -195,8 +196,8 @@ impl<C: CurveAffine> Committed<C> {
> {
let domain = &pk.vk.domain;
let permutation_product_cosets_owned = self.permutation_product_cosets.clone();
let permutation_product_cosets = self.permutation_product_cosets;
let permutation_product_cosets_inv = self.permutation_product_cosets_inv;
let permutation_product_cosets = self.permutation_product_cosets.clone();
let permutation_product_cosets_inv = self.permutation_product_cosets_inv.clone();
let expressions = iter::empty()
// l_0(X) * (1 - z(X)) = 0
@ -206,46 +207,56 @@ impl<C: CurveAffine> Committed<C> {
.map(move |coset| Polynomial::one_minus(coset) * &pk.l0),
)
// z(X) \prod (p(X) + \beta s_i(X) + \gamma) - z(omega^{-1} X) \prod (p(X) + \delta^i \beta X + \gamma)
.chain(pk.vk.cs.permutations.iter().enumerate().map(
move |(permutation_index, columns)| {
let mut left = permutation_product_cosets[permutation_index].clone();
for (advice, permutation) in columns
.iter()
.map(|&column| &advice_cosets[pk.vk.cs.get_advice_query_index(column, 0)])
.zip(pk.permutation_cosets[permutation_index].iter())
{
parallelize(&mut left, |left, start| {
for ((left, advice), permutation) in left
.iter_mut()
.zip(advice[start..].iter())
.zip(permutation[start..].iter())
{
*left *= &(*advice + &(*beta * permutation) + &gamma);
}
});
}
.chain(
pk.vk
.cs
.permutations
.iter()
.zip(pk.permutations.iter())
.zip(permutation_product_cosets.into_iter())
.zip(permutation_product_cosets_inv.into_iter())
.map(move |(((p, pkey), cosets), cosets_inv)| {
let mut left = cosets;
for (advice, permutation) in p
.columns
.iter()
.map(|&column| {
&advice_cosets[pk.vk.cs.get_advice_query_index(column, 0)]
})
.zip(pkey.cosets.iter())
{
parallelize(&mut left, |left, start| {
for ((left, advice), permutation) in left
.iter_mut()
.zip(advice[start..].iter())
.zip(permutation[start..].iter())
{
*left *= &(*advice + &(*beta * permutation) + &gamma);
}
});
}
let mut right = permutation_product_cosets_inv[permutation_index].clone();
let mut current_delta = *beta * &C::Scalar::ZETA;
let step = domain.get_extended_omega();
for advice in columns
.iter()
.map(|&column| &advice_cosets[pk.vk.cs.get_advice_query_index(column, 0)])
{
parallelize(&mut right, move |right, start| {
let mut beta_term =
current_delta * &step.pow_vartime(&[start as u64, 0, 0, 0]);
for (right, advice) in right.iter_mut().zip(advice[start..].iter()) {
*right *= &(*advice + &beta_term + &gamma);
beta_term *= &step;
}
});
current_delta *= &C::Scalar::DELTA;
}
let mut right = cosets_inv;
let mut current_delta = *beta * &C::Scalar::ZETA;
let step = domain.get_extended_omega();
for advice in p.columns.iter().map(|&column| {
&advice_cosets[pk.vk.cs.get_advice_query_index(column, 0)]
}) {
parallelize(&mut right, move |right, start| {
let mut beta_term =
current_delta * &step.pow_vartime(&[start as u64, 0, 0, 0]);
for (right, advice) in right.iter_mut().zip(advice[start..].iter())
{
*right *= &(*advice + &beta_term + &gamma);
beta_term *= &step;
}
});
current_delta *= &C::Scalar::DELTA;
}
left - &right
},
));
left - &right
}),
);
Ok((
Constructed {
@ -258,6 +269,31 @@ impl<C: CurveAffine> Committed<C> {
}
}
impl<C: CurveAffine> super::ProvingKey<C> {
fn evaluate(&self, x: ChallengeX<C::Scalar>) -> Vec<C::Scalar> {
self.polys
.iter()
.map(|poly| eval_polynomial(poly, *x))
.collect()
}
fn open<'a>(
&'a self,
evals: &'a [C::Scalar],
x: ChallengeX<C::Scalar>,
) -> impl Iterator<Item = ProverQuery<'a, C>> + Clone {
self.polys
.iter()
.zip(evals.iter())
.map(move |(poly, eval)| ProverQuery {
point: *x,
poly,
blind: Blind::default(),
eval: *eval,
})
}
}
impl<C: CurveAffine> Constructed<C> {
pub(crate) fn evaluate<HBase: Hasher<C::Base>, HScalar: Hasher<C::Scalar>>(
self,
@ -279,11 +315,7 @@ impl<C: CurveAffine> Constructed<C> {
.map(|poly| eval_polynomial(poly, domain.rotate_omega(*x, Rotation(-1))))
.collect();
let permutation_evals: Vec<Vec<C::Scalar>> = pk
.permutation_polys
.iter()
.map(|polys| polys.iter().map(|poly| eval_polynomial(poly, *x)).collect())
.collect();
let permutation_evals: Vec<_> = pk.permutations.iter().map(|p| p.evaluate(x)).collect();
// Hash each advice evaluation
for eval in permutation_product_evals
@ -328,16 +360,10 @@ impl<C: CurveAffine> Evaluated<C> {
)
// Open permutation polynomial commitments at x
.chain(
pk.permutation_polys
pk.permutations
.iter()
.zip(self.permutation_evals.iter())
.flat_map(|(polys, evals)| polys.iter().zip(evals.iter()))
.map(move |(poly, eval)| ProverQuery {
point: *x,
poly,
blind: Blind::default(),
eval: *eval,
}),
.flat_map(move |(permutation, evals)| permutation.open(evals, x)),
)
// Open permutation product commitments at \omega^{-1} x
.chain(

View File

@ -15,10 +15,8 @@ impl<C: CurveAffine> Proof<C> {
return Err(Error::IncompatibleParams);
}
for (permutation_evals, permutation) in
self.permutation_evals.iter().zip(vk.cs.permutations.iter())
{
if permutation_evals.len() != permutation.len() {
for (permutation_evals, p) in self.permutation_evals.iter().zip(vk.cs.permutations.iter()) {
if permutation_evals.len() != p.columns.len() {
return Err(Error::IncompatibleParams);
}
}
@ -76,9 +74,10 @@ impl<C: CurveAffine> Proof<C> {
.zip(self.permutation_product_evals.iter())
.zip(self.permutation_product_inv_evals.iter())
.map(
move |(((columns, permutation_evals), product_eval), product_inv_eval)| {
move |(((p, permutation_evals), product_eval), product_inv_eval)| {
let mut left = *product_eval;
for (advice_eval, permutation_eval) in columns
for (advice_eval, permutation_eval) in p
.columns
.iter()
.map(|&column| {
advice_evals[vk.cs.get_advice_query_index(column, 0)]
@ -90,7 +89,7 @@ impl<C: CurveAffine> Proof<C> {
let mut right = *product_inv_eval;
let mut current_delta = *beta * &x;
for advice_eval in columns.iter().map(|&column| {
for advice_eval in p.columns.iter().map(|&column| {
advice_evals[vk.cs.get_advice_query_index(column, 0)]
}) {
right *= &(advice_eval + &current_delta + &gamma);
@ -132,12 +131,12 @@ impl<C: CurveAffine> Proof<C> {
)
// Open permutation commitments for each permutation argument at x
.chain(
(0..vk.permutation_commitments.len())
(0..vk.permutations.len())
.map(move |outer_idx| {
let inner_len = vk.permutation_commitments[outer_idx].len();
let inner_len = vk.permutations[outer_idx].commitments.len();
(0..inner_len).map(move |inner_idx| VerifierQuery {
point: *x,
commitment: &vk.permutation_commitments[outer_idx][inner_idx],
commitment: &vk.permutations[outer_idx].commitments[inner_idx],
eval: self.permutation_evals[outer_idx][inner_idx],
})
})