From eabb10a34cf72d65e05a768e87176cbfc68d985d Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Thu, 9 Jun 2022 11:48:11 -0700 Subject: [PATCH] lookup argument for range check --- evm/src/memory/memory_stark.rs | 54 ++++++++++++++++++++++--- evm/src/memory/registers.rs | 4 +- evm/src/util.rs | 72 +++++++++++++++++++++++++++++++++- 3 files changed, 123 insertions(+), 7 deletions(-) diff --git a/evm/src/memory/memory_stark.rs b/evm/src/memory/memory_stark.rs index b196a21d..c4422fde 100644 --- a/evm/src/memory/memory_stark.rs +++ b/evm/src/memory/memory_stark.rs @@ -8,12 +8,14 @@ use plonky2::hash::hash_types::RichField; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::memory::registers::{ memory_value_limb, sorted_memory_value_limb, MEMORY_ADDR_CONTEXT, MEMORY_ADDR_SEGMENT, - MEMORY_ADDR_VIRTUAL, MEMORY_CONTEXT_FIRST_CHANGE, MEMORY_COUNTER, MEMORY_IS_READ, - MEMORY_RANGE_CHECK, MEMORY_SEGMENT_FIRST_CHANGE, MEMORY_TIMESTAMP, MEMORY_VIRTUAL_FIRST_CHANGE, - NUM_REGISTERS, SORTED_MEMORY_ADDR_CONTEXT, SORTED_MEMORY_ADDR_SEGMENT, - SORTED_MEMORY_ADDR_VIRTUAL, SORTED_MEMORY_IS_READ, SORTED_MEMORY_TIMESTAMP, + MEMORY_ADDR_VIRTUAL, MEMORY_CONTEXT_FIRST_CHANGE, MEMORY_COUNTER, MEMORY_COUNTER_PERMUTED, + MEMORY_IS_READ, MEMORY_RANGE_CHECK, MEMORY_RANGE_CHECK_PERMUTED, MEMORY_SEGMENT_FIRST_CHANGE, + MEMORY_TIMESTAMP, MEMORY_VIRTUAL_FIRST_CHANGE, NUM_REGISTERS, SORTED_MEMORY_ADDR_CONTEXT, + SORTED_MEMORY_ADDR_SEGMENT, SORTED_MEMORY_ADDR_VIRTUAL, SORTED_MEMORY_IS_READ, + SORTED_MEMORY_TIMESTAMP, }; use crate::stark::Stark; +use crate::util::permuted_cols; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; #[derive(Default)] @@ -197,7 +199,14 @@ impl, const D: usize> MemoryStark { trace_cols[MEMORY_VIRTUAL_FIRST_CHANGE] = virtual_first_change; trace_cols[MEMORY_RANGE_CHECK] = range_check_value; - trace_cols[MEMORY_COUNTER] = (0..trace_cols.len()).map(|i| F::from_canonical_usize(i)).collect(); + trace_cols[MEMORY_COUNTER] = (0..trace_cols.len()) + .map(|i| F::from_canonical_usize(i)) + .collect(); + + let (permuted_inputs, permuted_table) = + permuted_cols(&trace_cols[MEMORY_RANGE_CHECK], &trace_cols[MEMORY_COUNTER]); + trace_cols[MEMORY_RANGE_CHECK_PERMUTED] = permuted_inputs; + trace_cols[MEMORY_COUNTER_PERMUTED] = permuted_table; } } @@ -271,6 +280,23 @@ impl, const D: usize> Stark for MemoryStark, const D: usize> Stark for MemoryStark usize { diff --git a/evm/src/memory/registers.rs b/evm/src/memory/registers.rs index 0a9f4f8e..885fda19 100644 --- a/evm/src/memory/registers.rs +++ b/evm/src/memory/registers.rs @@ -30,5 +30,7 @@ pub(crate) const MEMORY_VIRTUAL_FIRST_CHANGE: usize = MEMORY_SEGMENT_FIRST_CHANG pub(crate) const MEMORY_RANGE_CHECK: usize = MEMORY_VIRTUAL_FIRST_CHANGE + 1; pub(crate) const MEMORY_COUNTER: usize = MEMORY_RANGE_CHECK + 1; +pub(crate) const MEMORY_RANGE_CHECK_PERMUTED: usize = MEMORY_COUNTER + 1; +pub(crate) const MEMORY_COUNTER_PERMUTED: usize = MEMORY_RANGE_CHECK_PERMUTED + 1; -pub(crate) const NUM_REGISTERS: usize = MEMORY_COUNTER + 1; +pub(crate) const NUM_REGISTERS: usize = MEMORY_COUNTER_PERMUTED + 1; diff --git a/evm/src/util.rs b/evm/src/util.rs index 011a1add..f8fba89f 100644 --- a/evm/src/util.rs +++ b/evm/src/util.rs @@ -1,5 +1,7 @@ +use std::cmp::Ordering; + use itertools::Itertools; -use plonky2::field::field_types::Field; +use plonky2::field::field_types::{Field, PrimeField64}; use plonky2::field::polynomial::PolynomialValues; use plonky2::util::transpose; @@ -14,3 +16,71 @@ pub fn trace_rows_to_poly_values( .map(|column| PolynomialValues::new(column)) .collect() } + +/// Given an input column and a table column, generate the permuted input and permuted table columns +/// used in the Halo2 permutation argument. +pub fn permuted_cols(inputs: &[F], table: &[F]) -> (Vec, Vec) { + let n = inputs.len(); + + // The permuted inputs do not have to be ordered, but we found that sorting was faster than + // hash-based grouping. We also sort the table, as this helps us identify "unused" table + // elements efficiently. + + // To compare elements, e.g. for sorting, we first need them in canonical form. It would be + // wasteful to canonicalize in each comparison, as a single element may be involved in many + // comparisons. So we will canonicalize once upfront, then use `to_noncanonical_u64` when + // comparing elements. + + let sorted_inputs = inputs + .iter() + .map(|x| x.to_canonical()) + .sorted_unstable_by_key(|x| x.to_noncanonical_u64()) + .collect_vec(); + let sorted_table = table + .iter() + .map(|x| x.to_canonical()) + .sorted_unstable_by_key(|x| x.to_noncanonical_u64()) + .collect_vec(); + + let mut unused_table_inds = Vec::with_capacity(n); + let mut unused_table_vals = Vec::with_capacity(n); + let mut permuted_table = vec![F::ZERO; n]; + let mut i = 0; + let mut j = 0; + while (j < n) && (i < n) { + let input_val = sorted_inputs[i].to_noncanonical_u64(); + let table_val = sorted_table[j].to_noncanonical_u64(); + match input_val.cmp(&table_val) { + Ordering::Greater => { + unused_table_vals.push(sorted_table[j]); + j += 1; + } + Ordering::Less => { + if let Some(x) = unused_table_vals.pop() { + permuted_table[i] = x; + } else { + unused_table_inds.push(i); + } + i += 1; + } + Ordering::Equal => { + permuted_table[i] = sorted_table[j]; + i += 1; + j += 1; + } + } + } + + #[allow(clippy::needless_range_loop)] // indexing is just more natural here + for jj in j..n { + unused_table_vals.push(sorted_table[jj]); + } + for ii in i..n { + unused_table_inds.push(ii); + } + for (ind, val) in unused_table_inds.into_iter().zip_eq(unused_table_vals) { + permuted_table[ind] = val; + } + + (sorted_inputs, permuted_table) +} \ No newline at end of file