plonky2/evm/src/cross_table_lookup.rs

460 lines
15 KiB
Rust
Raw Normal View History

2022-05-11 16:09:12 +02:00
use anyhow::{ensure, Result};
2022-05-06 14:55:54 +02:00
use plonky2::field::extension_field::{Extendable, FieldExtension};
2022-05-05 22:21:09 +02:00
use plonky2::field::field_types::Field;
use plonky2::field::packed_field::PackedField;
use plonky2::field::polynomial::PolynomialValues;
use plonky2::hash::hash_types::RichField;
use plonky2::iop::challenger::Challenger;
2022-05-20 11:21:13 +02:00
use plonky2::iop::ext_target::ExtensionTarget;
2022-05-24 16:24:52 +02:00
use plonky2::iop::target::Target;
use plonky2::plonk::circuit_builder::CircuitBuilder;
2022-05-05 22:21:09 +02:00
use plonky2::plonk::config::GenericConfig;
2022-05-06 17:35:25 +02:00
use crate::all_stark::Table;
2022-05-05 22:21:09 +02:00
use crate::config::StarkConfig;
2022-05-24 16:24:52 +02:00
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
2022-05-12 13:47:55 +02:00
use crate::permutation::{
get_grand_product_challenge_set, GrandProductChallenge, GrandProductChallengeSet,
2022-05-12 13:47:55 +02:00
};
2022-05-23 17:49:04 +02:00
use crate::proof::{StarkProofWithPublicInputs, StarkProofWithPublicInputsTarget};
2022-05-06 14:55:54 +02:00
use crate::stark::Stark;
2022-05-24 16:24:52 +02:00
use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars};
2022-05-05 22:21:09 +02:00
2022-06-06 20:51:14 +02:00
#[derive(Clone)]
pub struct TableWithColumns {
pub table: Table,
pub columns: Vec<usize>,
pub filter_column: Option<usize>,
}
impl TableWithColumns {
pub fn new(table: Table, columns: Vec<usize>, filter_column: Option<usize>) -> Self {
Self {
table,
columns,
filter_column,
}
}
}
2022-05-12 13:47:55 +02:00
#[derive(Clone)]
2022-05-17 09:24:22 +02:00
pub struct CrossTableLookup<F: Field> {
2022-06-06 20:51:14 +02:00
pub looking_tables: Vec<TableWithColumns>,
pub looked_table: TableWithColumns,
2022-05-17 09:24:22 +02:00
pub default: Vec<F>,
2022-05-06 17:22:30 +02:00
}
2022-05-17 09:24:22 +02:00
impl<F: Field> CrossTableLookup<F> {
2022-05-06 17:22:30 +02:00
pub fn new(
2022-06-06 20:51:14 +02:00
looking_tables: Vec<TableWithColumns>,
looked_table: TableWithColumns,
2022-05-17 09:24:22 +02:00
default: Vec<F>,
2022-05-06 17:22:30 +02:00
) -> Self {
2022-06-06 20:51:14 +02:00
assert!(looking_tables
2022-06-01 18:53:19 +02:00
.iter()
2022-06-06 20:51:14 +02:00
.all(|twc| twc.columns.len() == looked_table.columns.len()));
2022-05-06 17:22:30 +02:00
Self {
2022-06-01 18:53:19 +02:00
looking_tables,
2022-05-06 17:22:30 +02:00
looked_table,
2022-05-17 09:24:22 +02:00
default,
2022-05-06 17:22:30 +02:00
}
}
}
2022-05-16 20:45:30 +02:00
/// Cross-table lookup data for one table.
2022-05-05 22:21:09 +02:00
#[derive(Clone)]
2022-05-13 11:20:29 +02:00
pub struct CtlData<F: Field> {
2022-05-17 09:24:22 +02:00
/// Challenges used in the argument.
pub(crate) challenges: GrandProductChallengeSet<F>,
2022-05-17 09:24:22 +02:00
/// Vector of `(Z, columns)` where `Z` is a Z-polynomial for a lookup on columns `columns`.
2022-05-11 14:35:33 +02:00
pub zs_columns: Vec<(PolynomialValues<F>, Vec<usize>)>,
2022-05-05 22:21:09 +02:00
}
2022-05-13 11:20:29 +02:00
impl<F: Field> CtlData<F> {
pub(crate) fn new(challenges: GrandProductChallengeSet<F>) -> Self {
2022-05-05 22:21:09 +02:00
Self {
2022-05-12 13:47:55 +02:00
challenges,
2022-05-11 14:35:33 +02:00
zs_columns: vec![],
2022-05-05 22:21:09 +02:00
}
}
2022-05-10 15:08:08 +02:00
pub fn len(&self) -> usize {
2022-05-11 14:35:33 +02:00
self.zs_columns.len()
2022-05-10 15:08:08 +02:00
}
2022-05-05 22:21:09 +02:00
pub fn is_empty(&self) -> bool {
2022-05-11 14:35:33 +02:00
self.zs_columns.is_empty()
2022-05-05 22:21:09 +02:00
}
pub fn z_polys(&self) -> Vec<PolynomialValues<F>> {
2022-05-11 14:35:33 +02:00
self.zs_columns.iter().map(|(p, _)| p.clone()).collect()
2022-05-05 22:21:09 +02:00
}
}
2022-05-13 11:20:29 +02:00
pub fn cross_table_lookup_data<F: RichField, C: GenericConfig<D, F = F>, const D: usize>(
2022-05-05 22:21:09 +02:00
config: &StarkConfig,
trace_poly_values: &[Vec<PolynomialValues<F>>],
2022-05-17 09:24:22 +02:00
cross_table_lookups: &[CrossTableLookup<F>],
2022-05-05 22:21:09 +02:00
challenger: &mut Challenger<F, C::Hasher>,
2022-05-13 11:20:29 +02:00
) -> Vec<CtlData<F>> {
let challenges = get_grand_product_challenge_set(challenger, config.num_challenges);
2022-05-18 09:22:58 +02:00
let mut ctl_data_per_table = vec![CtlData::new(challenges.clone()); trace_poly_values.len()];
for CrossTableLookup {
2022-06-01 18:53:19 +02:00
looking_tables,
2022-05-18 09:22:58 +02:00
looked_table,
default,
} in cross_table_lookups
{
for &challenge in &challenges.challenges {
2022-06-06 20:51:14 +02:00
let zs_looking = looking_tables.iter().map(|table| {
partial_products(
&trace_poly_values[table.table as usize],
&table.columns,
challenge,
)
});
2022-05-18 09:22:58 +02:00
let z_looked = partial_products(
2022-06-06 20:51:14 +02:00
&trace_poly_values[looked_table.table as usize],
&looked_table.columns,
2022-05-18 09:22:58 +02:00
challenge,
);
2022-05-05 22:21:09 +02:00
2022-05-18 09:22:58 +02:00
debug_assert_eq!(
2022-06-01 18:53:19 +02:00
zs_looking
2022-06-01 20:21:59 +02:00
.clone()
2022-06-01 18:53:19 +02:00
.map(|z| *z.values.last().unwrap())
.product::<F>(),
2022-05-18 09:22:58 +02:00
*z_looked.values.last().unwrap()
* challenge.combine(default).exp_u64(
2022-06-01 18:53:19 +02:00
looking_tables
.iter()
2022-06-06 20:51:14 +02:00
.map(|table| trace_poly_values[table.table as usize][0].len() as u64)
2022-06-01 18:53:19 +02:00
.sum::<u64>()
2022-06-06 20:51:14 +02:00
- trace_poly_values[looked_table.table as usize][0].len() as u64
2022-05-18 09:22:58 +02:00
)
);
2022-05-17 09:24:22 +02:00
2022-06-06 20:51:14 +02:00
for (table, z) in looking_tables.iter().zip(zs_looking) {
ctl_data_per_table[table.table as usize]
2022-06-01 18:53:19 +02:00
.zs_columns
2022-06-06 20:51:14 +02:00
.push((z, table.columns.clone()));
2022-06-01 18:53:19 +02:00
}
2022-06-06 20:51:14 +02:00
ctl_data_per_table[looked_table.table as usize]
2022-05-18 09:22:58 +02:00
.zs_columns
2022-06-06 20:51:14 +02:00
.push((z_looked, looked_table.columns.clone()));
2022-05-18 09:22:58 +02:00
}
}
ctl_data_per_table
2022-05-05 22:21:09 +02:00
}
fn partial_products<F: Field>(
trace: &[PolynomialValues<F>],
columns: &[usize],
2022-05-18 09:22:58 +02:00
challenge: GrandProductChallenge<F>,
2022-05-05 22:21:09 +02:00
) -> PolynomialValues<F> {
let mut partial_prod = F::ONE;
2022-05-12 20:38:11 +02:00
let degree = trace[0].len();
let mut res = Vec::with_capacity(degree);
for i in 0..degree {
2022-05-18 09:22:58 +02:00
partial_prod *= challenge.combine(columns.iter().map(|&j| &trace[j].values[i]));
2022-05-05 22:21:09 +02:00
res.push(partial_prod);
}
res.into()
}
2022-05-12 13:47:55 +02:00
#[derive(Clone)]
2022-05-16 20:45:30 +02:00
pub struct CtlCheckVars<'a, F, FE, P, const D2: usize>
2022-05-05 22:21:09 +02:00
where
F: Field,
FE: FieldExtension<D2, BaseField = F>,
P: PackedField<Scalar = FE>,
{
2022-05-06 14:55:54 +02:00
pub(crate) local_z: P,
pub(crate) next_z: P,
pub(crate) challenges: GrandProductChallenge<F>,
2022-05-12 13:47:55 +02:00
pub(crate) columns: &'a [usize],
}
impl<'a, F: RichField + Extendable<D>, const D: usize>
2022-05-16 20:45:30 +02:00
CtlCheckVars<'a, F, F::Extension, F::Extension, D>
2022-05-12 13:47:55 +02:00
{
pub(crate) fn from_proofs<C: GenericConfig<D, F = F>>(
2022-05-19 09:41:15 +02:00
proofs: &[StarkProofWithPublicInputs<F, C, D>],
2022-05-17 09:24:22 +02:00
cross_table_lookups: &'a [CrossTableLookup<F>],
ctl_challenges: &'a GrandProductChallengeSet<F>,
2022-05-13 10:48:56 +02:00
num_permutation_zs: &[usize],
2022-05-12 13:47:55 +02:00
) -> Vec<Vec<Self>> {
2022-05-13 10:48:56 +02:00
debug_assert_eq!(proofs.len(), num_permutation_zs.len());
2022-05-12 13:47:55 +02:00
let mut ctl_zs = proofs
.iter()
2022-05-13 10:48:56 +02:00
.zip(num_permutation_zs)
2022-05-18 09:22:58 +02:00
.map(|(p, &num_perms)| {
let openings = &p.proof.openings;
let ctl_zs = openings.permutation_ctl_zs.iter().skip(num_perms);
2022-06-02 23:55:56 +02:00
let ctl_zs_next = openings.permutation_ctl_zs_next.iter().skip(num_perms);
ctl_zs.zip(ctl_zs_next)
2022-05-12 13:47:55 +02:00
})
.collect::<Vec<_>>();
2022-05-18 09:22:58 +02:00
let mut ctl_vars_per_table = vec![vec![]; proofs.len()];
for CrossTableLookup {
2022-06-01 18:53:19 +02:00
looking_tables,
2022-05-18 09:22:58 +02:00
looked_table,
..
} in cross_table_lookups
{
for &challenges in &ctl_challenges.challenges {
2022-06-06 20:51:14 +02:00
for table in looking_tables {
let (looking_z, looking_z_next) = ctl_zs[table.table as usize].next().unwrap();
ctl_vars_per_table[table.table as usize].push(Self {
2022-06-01 18:53:19 +02:00
local_z: *looking_z,
next_z: *looking_z_next,
challenges,
2022-06-06 20:51:14 +02:00
columns: &table.columns,
2022-06-01 18:53:19 +02:00
});
}
2022-05-12 13:47:55 +02:00
2022-06-06 20:51:14 +02:00
let (looked_z, looked_z_next) = ctl_zs[looked_table.table as usize].next().unwrap();
ctl_vars_per_table[looked_table.table as usize].push(Self {
2022-05-18 09:22:58 +02:00
local_z: *looked_z,
next_z: *looked_z_next,
challenges,
2022-06-06 20:51:14 +02:00
columns: &looked_table.columns,
2022-05-18 09:22:58 +02:00
});
}
}
ctl_vars_per_table
2022-05-12 13:47:55 +02:00
}
2022-05-05 22:21:09 +02:00
}
2022-05-06 14:55:54 +02:00
pub(crate) fn eval_cross_table_lookup_checks<F, FE, P, C, S, const D: usize, const D2: usize>(
2022-05-13 14:16:28 +02:00
vars: StarkEvaluationVars<FE, P, { S::COLUMNS }, { S::PUBLIC_INPUTS }>,
2022-05-16 20:45:30 +02:00
ctl_vars: &[CtlCheckVars<F, FE, P, D2>],
2022-05-05 22:21:09 +02:00
consumer: &mut ConstraintConsumer<P>,
) where
F: RichField + Extendable<D>,
FE: FieldExtension<D2, BaseField = F>,
P: PackedField<Scalar = FE>,
C: GenericConfig<D, F = F>,
S: Stark<F, D>,
{
2022-05-13 11:20:29 +02:00
for lookup_vars in ctl_vars {
2022-05-16 20:45:30 +02:00
let CtlCheckVars {
2022-05-06 14:55:54 +02:00
local_z,
next_z,
challenges,
columns,
2022-05-13 11:20:29 +02:00
} = lookup_vars;
2022-05-18 09:22:58 +02:00
let combine = |v: &[P]| -> P { challenges.combine(columns.iter().map(|&i| &v[i])) };
2022-05-05 22:21:09 +02:00
2022-05-06 14:55:54 +02:00
// Check value of `Z(1)`
consumer.constraint_first_row(*local_z - combine(vars.local_values));
// Check `Z(gw) = combination * Z(w)`
consumer.constraint_transition(*next_z - *local_z * combine(vars.next_values));
2022-05-05 22:21:09 +02:00
}
}
2022-05-11 16:09:12 +02:00
2022-05-23 17:49:04 +02:00
#[derive(Clone)]
pub struct CtlCheckVarsTarget<'a, const D: usize> {
pub(crate) local_z: ExtensionTarget<D>,
pub(crate) next_z: ExtensionTarget<D>,
pub(crate) challenges: GrandProductChallenge<Target>,
pub(crate) columns: &'a [usize],
}
impl<'a, const D: usize> CtlCheckVarsTarget<'a, D> {
pub(crate) fn from_proofs<F: Field>(
proofs: &[StarkProofWithPublicInputsTarget<D>],
cross_table_lookups: &'a [CrossTableLookup<F>],
ctl_challenges: &'a GrandProductChallengeSet<Target>,
num_permutation_zs: &[usize],
) -> Vec<Vec<Self>> {
debug_assert_eq!(proofs.len(), num_permutation_zs.len());
let mut ctl_zs = proofs
.iter()
.zip(num_permutation_zs)
.map(|(p, &num_perms)| {
let openings = &p.proof.openings;
let ctl_zs = openings.permutation_ctl_zs.iter().skip(num_perms);
2022-06-02 23:55:56 +02:00
let ctl_zs_next = openings.permutation_ctl_zs_next.iter().skip(num_perms);
ctl_zs.zip(ctl_zs_next)
2022-05-23 17:49:04 +02:00
})
.collect::<Vec<_>>();
let mut ctl_vars_per_table = vec![vec![]; proofs.len()];
for CrossTableLookup {
2022-06-01 18:53:19 +02:00
looking_tables,
2022-05-23 17:49:04 +02:00
looked_table,
..
} in cross_table_lookups
{
for &challenges in &ctl_challenges.challenges {
2022-06-06 20:51:14 +02:00
for table in looking_tables {
let (looking_z, looking_z_next) = ctl_zs[table.table as usize].next().unwrap();
ctl_vars_per_table[table.table as usize].push(Self {
2022-06-01 18:53:19 +02:00
local_z: *looking_z,
next_z: *looking_z_next,
challenges,
2022-06-06 20:51:14 +02:00
columns: &table.columns,
2022-06-01 18:53:19 +02:00
});
}
2022-05-23 17:49:04 +02:00
2022-06-06 20:51:14 +02:00
let (looked_z, looked_z_next) = ctl_zs[looked_table.table as usize].next().unwrap();
ctl_vars_per_table[looked_table.table as usize].push(Self {
2022-05-23 17:49:04 +02:00
local_z: *looked_z,
next_z: *looked_z_next,
challenges,
2022-06-06 20:51:14 +02:00
columns: &looked_table.columns,
2022-05-23 17:49:04 +02:00
});
}
}
ctl_vars_per_table
}
}
2022-05-24 16:24:52 +02:00
pub(crate) fn eval_cross_table_lookup_checks_circuit<
S: Stark<F, D>,
F: RichField + Extendable<D>,
const D: usize,
>(
builder: &mut CircuitBuilder<F, D>,
vars: StarkEvaluationTargets<D, { S::COLUMNS }, { S::PUBLIC_INPUTS }>,
ctl_vars: &[CtlCheckVarsTarget<D>],
consumer: &mut RecursiveConstraintConsumer<F, D>,
) {
for lookup_vars in ctl_vars {
let CtlCheckVarsTarget {
local_z,
next_z,
challenges,
columns,
} = lookup_vars;
// Check value of `Z(1)`
let combined_local = challenges.combine_circuit(
builder,
&columns
.iter()
.map(|&i| vars.local_values[i])
.collect::<Vec<_>>(),
);
let first_row = builder.sub_extension(*local_z, combined_local);
consumer.constraint_first_row(builder, first_row);
// Check `Z(gw) = combination * Z(w)`
let combined_next = challenges.combine_circuit(
builder,
&columns
.iter()
.map(|&i| vars.next_values[i])
.collect::<Vec<_>>(),
);
let mut transition = builder.mul_extension(*local_z, combined_next);
transition = builder.sub_extension(*next_z, transition);
consumer.constraint_transition(builder, transition);
}
}
2022-05-11 16:09:12 +02:00
pub(crate) fn verify_cross_table_lookups<
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
const D: usize,
>(
2022-05-17 09:24:22 +02:00
cross_table_lookups: Vec<CrossTableLookup<F>>,
2022-05-19 09:41:15 +02:00
proofs: &[StarkProofWithPublicInputs<F, C, D>],
challenges: GrandProductChallengeSet<F>,
2022-05-11 16:09:12 +02:00
config: &StarkConfig,
) -> Result<()> {
let degrees_bits = proofs
.iter()
.map(|p| p.proof.recover_degree_bits(config))
.collect::<Vec<_>>();
2022-05-13 11:20:29 +02:00
let mut ctl_zs_openings = proofs
2022-05-11 16:09:12 +02:00
.iter()
2022-05-13 11:20:29 +02:00
.map(|p| p.proof.openings.ctl_zs_last.iter())
2022-05-11 16:09:12 +02:00
.collect::<Vec<_>>();
2022-05-12 13:47:55 +02:00
for (
i,
CrossTableLookup {
2022-06-01 18:53:19 +02:00
looking_tables,
2022-05-12 13:47:55 +02:00
looked_table,
2022-05-17 09:24:22 +02:00
default,
2022-05-12 13:47:55 +02:00
..
},
) in cross_table_lookups.into_iter().enumerate()
2022-05-11 16:09:12 +02:00
{
2022-06-01 18:53:19 +02:00
let looking_degrees_sum = looking_tables
.iter()
2022-06-06 20:51:14 +02:00
.map(|table| 1 << degrees_bits[table.table as usize])
2022-06-01 18:53:19 +02:00
.sum::<u64>();
2022-06-06 20:51:14 +02:00
let looked_degree = 1 << degrees_bits[looked_table.table as usize];
2022-06-01 18:53:19 +02:00
let looking_zs_prod = looking_tables
.into_iter()
2022-06-06 20:51:14 +02:00
.map(|table| *ctl_zs_openings[table.table as usize].next().unwrap())
2022-06-01 18:53:19 +02:00
.product::<F>();
2022-06-06 20:51:14 +02:00
let looked_z = *ctl_zs_openings[looked_table.table as usize].next().unwrap();
2022-05-18 09:22:58 +02:00
let challenge = challenges.challenges[i % config.num_challenges];
let combined_default = challenge.combine(default.iter());
2022-05-17 09:24:22 +02:00
2022-05-11 16:09:12 +02:00
ensure!(
2022-06-01 18:53:19 +02:00
looking_zs_prod
== looked_z * combined_default.exp_u64(looking_degrees_sum - looked_degree),
2022-05-11 16:09:12 +02:00
"Cross-table lookup verification failed."
);
}
Ok(())
}
2022-05-24 16:24:52 +02:00
pub(crate) fn verify_cross_table_lookups_circuit<
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
const D: usize,
>(
builder: &mut CircuitBuilder<F, D>,
cross_table_lookups: Vec<CrossTableLookup<F>>,
proofs: &[StarkProofWithPublicInputsTarget<D>],
challenges: GrandProductChallengeSet<Target>,
inner_config: &StarkConfig,
) {
let degrees_bits = proofs
.iter()
.map(|p| p.proof.recover_degree_bits(inner_config))
.collect::<Vec<_>>();
let mut ctl_zs_openings = proofs
.iter()
.map(|p| p.proof.openings.ctl_zs_last.iter())
.collect::<Vec<_>>();
for (
i,
CrossTableLookup {
2022-06-01 18:53:19 +02:00
looking_tables,
2022-05-24 16:24:52 +02:00
looked_table,
default,
..
},
) in cross_table_lookups.into_iter().enumerate()
{
2022-06-01 18:53:19 +02:00
let looking_degrees_sum = looking_tables
.iter()
2022-06-06 20:51:14 +02:00
.map(|table| 1 << degrees_bits[table.table as usize])
2022-06-01 18:53:19 +02:00
.sum::<u64>();
2022-06-06 20:51:14 +02:00
let looked_degree = 1 << degrees_bits[looked_table.table as usize];
2022-06-01 18:53:19 +02:00
let looking_zs_prod = builder.mul_many(
looking_tables
.into_iter()
2022-06-06 20:51:14 +02:00
.map(|table| *ctl_zs_openings[table.table as usize].next().unwrap()),
2022-06-01 18:53:19 +02:00
);
2022-06-06 20:51:14 +02:00
let looked_z = *ctl_zs_openings[looked_table.table as usize].next().unwrap();
2022-05-24 16:24:52 +02:00
let challenge = challenges.challenges[i % inner_config.num_challenges];
let default = default
.into_iter()
2022-05-26 20:44:59 +02:00
.map(|x| builder.constant(x))
2022-05-24 16:24:52 +02:00
.collect::<Vec<_>>();
let combined_default = challenge.combine_base_circuit(builder, &default);
2022-06-01 18:53:19 +02:00
let pad = builder.exp_u64(combined_default, looking_degrees_sum - looked_degree);
2022-05-24 16:24:52 +02:00
let padded_looked_z = builder.mul(looked_z, pad);
2022-06-01 18:53:19 +02:00
builder.connect(looking_zs_prod, padded_looked_z);
2022-05-24 16:24:52 +02:00
}
}