mod param;
#[cfg(any(test, feature = "test-circuits"))]
mod dev;
#[cfg(test)]
mod test;
use std::{cmp::min, iter, marker::PhantomData};
#[cfg(feature = "test-circuits")]
pub use PiCircuit as TestPiCircuit;
use bus_mapping::circuit_input_builder::Withdrawal;
use eth_types::{self, Field, ToLittleEndian};
use halo2_proofs::plonk::{Expression, Instance, SecondPhase};
use itertools::Itertools;
use param::*;
use crate::{
evm_circuit::{
param::{
N_BYTES_BLOCK, N_BYTES_EXTRA_VALUE, N_BYTES_HALF_WORD, N_BYTES_TX, N_BYTES_U64,
N_BYTES_WITHDRAWAL, N_BYTES_WORD,
},
util::{
constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon},
from_bytes,
},
},
instance::{
public_data_convert, BlockValues, ExtraValues, PublicData, TxValues, NONZERO_BYTE_GAS_COST,
ZERO_BYTE_GAS_COST,
},
table::{BlockTable, KeccakTable, LookupTable, TxFieldTag, TxTable, WdTable},
tx_circuit::TX_LEN,
util::{word::WordLoHi, Challenges, SubCircuit, SubCircuitConfig},
witness::{self, Chunk},
};
use gadgets::{
is_zero::IsZeroChip,
util::{not, or, Expr},
};
use halo2_proofs::{
circuit::{AssignedCell, Layouter, Region, Value},
plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Selector},
poly::Rotation,
};
#[derive(Clone, Debug)]
pub struct PiCircuitConfig<F: Field> {
max_txs: usize,
max_withdrawals: usize,
max_calldata: usize,
q_digest_last: Selector,
q_bytes_last: Selector,
q_tx_table: Selector,
q_tx_calldata: Selector,
q_calldata_start: Selector,
q_rpi_keccak_lookup: Selector,
q_rpi_value_start: Column<Fixed>,
q_digest_value_start: Column<Fixed>,
tx_id_inv: Column<Advice>,
tx_value_lo_inv: Column<Advice>,
tx_id_diff_inv: Column<Advice>,
fixed_u16: Column<Fixed>,
calldata_gas_cost: Column<Advice>,
is_final: Column<Advice>,
rpi_bytes: Column<Advice>,
rpi_bytes_keccak_rlc: Column<Advice>,
rpi_value_lc: Column<Advice>,
rpi_digest_bytes: Column<Advice>,
rpi_digest_bytes_limbs: Column<Advice>,
q_rpi_byte_enable: Selector,
pi_instance: Column<Instance>, _marker: PhantomData<F>,
block_table: BlockTable,
tx_table: TxTable,
wd_table: WdTable,
keccak_table: KeccakTable,
}
pub struct PiCircuitConfigArgs<F: Field> {
pub max_txs: usize,
pub max_withdrawals: usize,
pub max_calldata: usize,
pub tx_table: TxTable,
pub wd_table: WdTable,
pub block_table: BlockTable,
pub keccak_table: KeccakTable,
pub challenges: Challenges<Expression<F>>,
}
impl<F: Field> SubCircuitConfig<F> for PiCircuitConfig<F> {
type ConfigArgs = PiCircuitConfigArgs<F>;
fn new(
meta: &mut ConstraintSystem<F>,
Self::ConfigArgs {
max_txs,
max_withdrawals,
max_calldata,
block_table,
tx_table,
wd_table,
keccak_table,
challenges,
}: Self::ConfigArgs,
) -> Self {
let q_tx_table = meta.complex_selector();
let q_tx_calldata = meta.complex_selector();
let q_calldata_start = meta.complex_selector();
let q_rpi_keccak_lookup = meta.complex_selector();
let tx_id = tx_table.tx_id;
let tx_value = tx_table.value;
let tag = tx_table.tag;
let index = tx_table.index;
let tx_id_inv = meta.advice_column();
let tx_value_lo_inv = meta.advice_column();
let tx_id_diff_inv = meta.advice_column();
let fixed_u16 = meta.fixed_column();
let calldata_gas_cost = meta.advice_column_in(SecondPhase);
let is_final = meta.advice_column();
let q_digest_last = meta.complex_selector();
let q_bytes_last = meta.complex_selector();
let q_rpi_byte_enable = meta.complex_selector();
let q_rpi_value_start = meta.fixed_column();
let q_digest_value_start = meta.fixed_column();
let rpi_bytes = meta.advice_column();
let rpi_bytes_keccak_rlc = meta.advice_column_in(SecondPhase);
let rpi_value_lc = meta.advice_column();
let rpi_digest_bytes = meta.advice_column();
let rpi_digest_bytes_limbs = meta.advice_column();
let pi_instance = meta.instance_column();
tx_table.annotate_columns(meta);
wd_table.annotate_columns(meta);
block_table.annotate_columns(meta);
meta.enable_equality(block_table.value.lo());
meta.enable_equality(block_table.value.hi());
meta.enable_equality(tx_table.tx_id);
meta.enable_equality(tx_table.index);
meta.enable_equality(tx_table.value.lo());
meta.enable_equality(tx_table.value.hi());
meta.enable_equality(wd_table.id);
meta.enable_equality(wd_table.validator_id);
meta.enable_equality(wd_table.address.lo());
meta.enable_equality(wd_table.address.hi());
meta.enable_equality(wd_table.amount);
meta.enable_equality(rpi_value_lc);
meta.enable_equality(rpi_bytes_keccak_rlc);
meta.enable_equality(rpi_digest_bytes_limbs);
meta.enable_equality(pi_instance);
meta.create_gate("rpi_bytes_keccak_rlc[last] = rpi_bytes[last]", |meta| {
let mut cb = BaseConstraintBuilder::default();
cb.require_equal(
"rpi_bytes_keccak_rlc[last] = rpi_bytes[last]",
meta.query_advice(rpi_bytes_keccak_rlc, Rotation::cur()),
meta.query_advice(rpi_bytes, Rotation::cur()),
);
cb.gate(meta.query_selector(q_bytes_last) * meta.query_selector(q_rpi_byte_enable))
});
meta.create_gate(
"rpi_bytes_keccak_rlc[i] = keccak_rand * rpi_bytes_keccak_rlc[i+1] + rpi_bytes[i]",
|meta| {
let mut cb = BaseConstraintBuilder::default();
let rpi_bytes_keccakrlc_cur =
meta.query_advice(rpi_bytes_keccak_rlc, Rotation::cur());
let rpi_bytes_keccakrlc_next =
meta.query_advice(rpi_bytes_keccak_rlc, Rotation::next());
let rpi_bytes_cur = meta.query_advice(rpi_bytes, Rotation::cur());
let keccak_rand = challenges.keccak_input();
cb.require_equal(
"rpi_bytes_keccak_rlc[i] = keccak_rand * rpi_bytes_keccak_rlc[i+1] + rpi_bytes[i]",
rpi_bytes_keccakrlc_cur,
rpi_bytes_keccakrlc_next * keccak_rand + rpi_bytes_cur,
);
cb.gate(
not::expr(meta.query_selector(q_bytes_last)) *
meta.query_selector(q_rpi_byte_enable)
)
},
);
meta.create_gate(
"rpi_value_lc[i] = rpi_value_lc[i-1] * byte_pow_base + rpi_bytes[i]",
|meta| {
let mut cb = BaseConstraintBuilder::default();
let q_rpi_value_start_cur = meta.query_fixed(q_rpi_value_start, Rotation::cur());
let rpi_value_lc_next = meta.query_advice(rpi_value_lc, Rotation::next());
let rpi_value_lc_cur = meta.query_advice(rpi_value_lc, Rotation::cur());
let rpi_bytes_cur = meta.query_advice(rpi_bytes, Rotation::cur());
cb.require_equal(
"rpi_value_lc[i] = rpi_value_lc[i+1] * r + rpi_bytes[i]",
rpi_value_lc_cur,
rpi_value_lc_next * BYTE_POW_BASE.expr() + rpi_bytes_cur,
);
cb.gate(not::expr(q_rpi_value_start_cur) * meta.query_selector(q_rpi_byte_enable))
},
);
meta.create_gate("rpi_value_lc[i] = rpi_bytes[i]", |meta| {
let mut cb = BaseConstraintBuilder::default();
let q_rpi_value_start_cur = meta.query_fixed(q_rpi_value_start, Rotation::cur());
cb.require_equal(
"rpi_value_lc[i] = rpi_bytes[i]",
meta.query_advice(rpi_bytes, Rotation::cur()),
meta.query_advice(rpi_value_lc, Rotation::cur()),
);
cb.gate(q_rpi_value_start_cur * meta.query_selector(q_rpi_byte_enable))
});
meta.lookup_any(
"lookup rpi_bytes_keccak_rlc against rpi_digest_bytes_limbs",
|meta| {
let circuit_len =
PiCircuitConfig::<F>::circuit_len_all(max_txs, max_withdrawals, max_calldata)
.expr();
let is_enabled = meta.query_advice(keccak_table.is_enabled, Rotation::cur());
let input_rlc = meta.query_advice(keccak_table.input_rlc, Rotation::cur());
let input_len = meta.query_advice(keccak_table.input_len, Rotation::cur());
let output_lo = meta.query_advice(keccak_table.output.lo(), Rotation::cur());
let output_hi = meta.query_advice(keccak_table.output.hi(), Rotation::cur());
let q_rpi_keccak_lookup = meta.query_selector(q_rpi_keccak_lookup);
let rpi_bytes_keccakrlc_cur =
meta.query_advice(rpi_bytes_keccak_rlc, Rotation::cur());
let rpi_digest_lo = meta.query_advice(rpi_digest_bytes_limbs, Rotation::cur());
let rpi_digest_hi = meta.query_advice(rpi_digest_bytes_limbs, Rotation::next());
vec![
(q_rpi_keccak_lookup.expr() * 1.expr(), is_enabled),
(
q_rpi_keccak_lookup.expr() * rpi_bytes_keccakrlc_cur,
input_rlc,
),
(q_rpi_keccak_lookup.expr() * circuit_len, input_len),
(q_rpi_keccak_lookup.expr() * rpi_digest_lo, output_lo),
(q_rpi_keccak_lookup * rpi_digest_hi, output_hi),
]
},
);
let tx_id_is_zero_config = IsZeroChip::configure(
meta,
|meta| meta.query_selector(q_tx_calldata),
|meta| meta.query_advice(tx_table.tx_id, Rotation::cur()),
tx_id_inv,
);
let tx_value_is_zero_lo_config = IsZeroChip::configure(
meta,
|meta| {
or::expr([
meta.query_selector(q_tx_table),
meta.query_selector(q_tx_calldata),
])
},
|meta| meta.query_advice(tx_value.lo(), Rotation::cur()),
tx_value_lo_inv,
);
let tx_value_is_zero_config = tx_value_is_zero_lo_config.expr();
let _tx_id_diff_is_zero_config = IsZeroChip::configure(
meta,
|meta| meta.query_selector(q_tx_calldata),
|meta| {
meta.query_advice(tx_table.tx_id, Rotation::next())
- meta.query_advice(tx_table.tx_id, Rotation::cur())
},
tx_id_diff_inv,
);
meta.lookup_any("tx_id_diff", |meta| {
let tx_id_next = meta.query_advice(tx_id, Rotation::next());
let tx_id = meta.query_advice(tx_id, Rotation::cur());
let tx_id_inv_next = meta.query_advice(tx_id_inv, Rotation::next());
let tx_id_diff_inv = meta.query_advice(tx_id_diff_inv, Rotation::cur());
let fixed_u16_table = meta.query_fixed(fixed_u16, Rotation::cur());
let tx_id_next_nonzero = tx_id_next.expr() * tx_id_inv_next;
let tx_id_not_equal_to_next = (tx_id_next.expr() - tx_id.expr()) * tx_id_diff_inv;
let tx_id_diff_minus_one = tx_id_next - tx_id - 1.expr();
vec![(
tx_id_diff_minus_one * tx_id_next_nonzero * tx_id_not_equal_to_next,
fixed_u16_table,
)]
});
meta.create_gate("calldata constraints", |meta| {
let q_is_calldata = meta.query_selector(q_tx_calldata);
let q_calldata_start = meta.query_selector(q_calldata_start);
let tx_idx = meta.query_advice(tx_id, Rotation::cur());
let tx_idx_next = meta.query_advice(tx_id, Rotation::next());
let tx_idx_inv_next = meta.query_advice(tx_id_inv, Rotation::next());
let tx_idx_diff_inv = meta.query_advice(tx_id_diff_inv, Rotation::cur());
let idx = meta.query_advice(index, Rotation::cur());
let idx_next = meta.query_advice(index, Rotation::next());
let value_next_lo = meta.query_advice(tx_value.lo(), Rotation::next());
let value_inv_next_lo = meta.query_advice(tx_value_lo_inv, Rotation::next());
let gas_cost = meta.query_advice(calldata_gas_cost, Rotation::cur());
let gas_cost_next = meta.query_advice(calldata_gas_cost, Rotation::next());
let is_final = meta.query_advice(is_final, Rotation::cur());
let is_tx_id_nonzero = not::expr(tx_id_is_zero_config.expr());
let is_tx_id_next_nonzero = tx_idx_next.expr() * tx_idx_inv_next.expr();
let is_value_zero = tx_value_is_zero_config.expr();
let is_value_nonzero = not::expr(tx_value_is_zero_config.expr());
let is_value_next_nonzero = value_next_lo.expr() * value_inv_next_lo.expr();
let is_value_next_zero = not::expr(is_value_next_nonzero.expr());
let gas = ZERO_BYTE_GAS_COST.expr() * is_value_zero.expr()
+ NONZERO_BYTE_GAS_COST.expr() * is_value_nonzero.expr();
let gas_next = ZERO_BYTE_GAS_COST.expr() * is_value_next_zero
+ NONZERO_BYTE_GAS_COST.expr() * is_value_next_nonzero;
let default_calldata_row_constraint1 = tx_id_is_zero_config.expr() * idx.expr();
let default_calldata_row_constraint2 = tx_id_is_zero_config.expr() * tx_idx_next.expr();
let default_calldata_row_constraint3 = tx_id_is_zero_config.expr() * is_final.expr();
let default_calldata_row_constraint4 = tx_id_is_zero_config.expr() * gas_cost.expr();
let tx_id_equal_to_next =
1.expr() - (tx_idx_next.expr() - tx_idx.expr()) * tx_idx_diff_inv.expr();
let idx_of_same_tx_constraint =
tx_id_equal_to_next.clone() * (idx_next.expr() - idx.expr() - 1.expr());
let idx_of_next_tx_constraint = (tx_idx_next.expr() - tx_idx.expr()) * idx_next.expr();
let gas_cost_of_same_tx_constraint = tx_id_equal_to_next.clone()
* (gas_cost_next.expr() - gas_cost.expr() - gas_next.expr());
let gas_cost_of_next_tx_constraint = is_tx_id_next_nonzero.expr()
* (tx_idx_next.expr() - tx_idx.expr())
* (gas_cost_next.expr() - gas_next.expr());
let is_final_of_same_tx_constraint = tx_id_equal_to_next * is_final.expr();
let is_final_of_next_tx_constraint =
(tx_idx_next.expr() - tx_idx.expr()) * (is_final.expr() - 1.expr());
vec![
q_is_calldata.expr() * default_calldata_row_constraint1,
q_is_calldata.expr() * default_calldata_row_constraint2,
q_is_calldata.expr() * default_calldata_row_constraint3,
q_is_calldata.expr() * default_calldata_row_constraint4,
q_is_calldata.expr() * is_tx_id_nonzero.expr() * idx_of_same_tx_constraint,
q_is_calldata.expr() * is_tx_id_nonzero.expr() * idx_of_next_tx_constraint,
q_is_calldata.expr() * is_tx_id_nonzero.expr() * gas_cost_of_same_tx_constraint,
q_is_calldata.expr() * is_tx_id_nonzero.expr() * gas_cost_of_next_tx_constraint,
q_is_calldata.expr() * is_tx_id_nonzero.expr() * is_final_of_same_tx_constraint,
q_is_calldata.expr() * is_tx_id_nonzero.expr() * is_final_of_next_tx_constraint,
q_calldata_start.expr() * is_tx_id_nonzero.expr() * (idx - 0.expr()),
q_calldata_start.expr() * is_tx_id_nonzero.expr() * (gas_cost - gas),
]
});
let tx_tag_is_cdl_config = IsZeroChip::configure(
meta,
|meta| meta.query_selector(q_tx_table),
|meta| meta.query_fixed(tag, Rotation::cur()) - TxFieldTag::CallDataLength.expr(),
tx_id_inv,
);
meta.create_gate(
"call_data_gas_cost should be zero if call_data_length is zero",
|meta| {
let q_tx_table = meta.query_selector(q_tx_table);
let is_calldata_length_zero = tx_value_is_zero_config.expr();
let is_calldata_length_row = tx_tag_is_cdl_config.expr();
let calldata_cost = meta.query_advice(tx_value.lo(), Rotation::next());
vec![q_tx_table * is_calldata_length_row * is_calldata_length_zero * calldata_cost]
},
);
meta.lookup_any("gas_cost in tx table", |meta| {
let q_tx_table = meta.query_selector(q_tx_table);
let is_final = meta.query_advice(is_final, Rotation::cur());
let tx_id = meta.query_advice(tx_id, Rotation::cur());
let calldata_cost_assigned = meta.query_advice(tx_value.lo(), Rotation::next());
let calldata_cost_calc = meta.query_advice(calldata_gas_cost, Rotation::cur());
let is_calldata_length_row = tx_tag_is_cdl_config.expr();
let is_calldata_length_nonzero = not::expr(tx_value_is_zero_config.expr());
let condition = q_tx_table * is_calldata_length_nonzero * is_calldata_length_row;
vec![
(condition.expr() * tx_id.expr(), tx_id),
(condition.expr() * 1.expr(), is_final),
(
condition.expr() * calldata_cost_assigned,
calldata_cost_calc,
),
]
});
Self {
max_txs,
max_withdrawals,
max_calldata,
block_table,
q_digest_last,
q_bytes_last,
q_tx_calldata,
q_calldata_start,
q_rpi_keccak_lookup,
q_rpi_value_start,
q_tx_table,
q_digest_value_start,
tx_table,
wd_table,
keccak_table,
tx_id_inv,
tx_value_lo_inv,
tx_id_diff_inv,
fixed_u16,
calldata_gas_cost,
is_final,
rpi_bytes,
rpi_bytes_keccak_rlc,
rpi_value_lc,
rpi_digest_bytes,
rpi_digest_bytes_limbs,
q_rpi_byte_enable,
pi_instance,
_marker: PhantomData,
}
}
}
impl<F: Field> PiCircuitConfig<F> {
#[inline]
fn circuit_len(&self) -> usize {
Self::circuit_len_all(self.max_txs, self.max_withdrawals, self.max_calldata)
}
#[inline]
fn circuit_len_all(txs: usize, wds: usize, calldata: usize) -> usize {
N_BYTES_ONE
+ N_BYTES_BLOCK
+ N_BYTES_EXTRA_VALUE
+ Self::circuit_len_tx_id(txs)
+ Self::circuit_len_tx_index(txs)
+ Self::circuit_len_tx_values(txs)
+ calldata
+ Self::circuit_len_withdrawal(wds)
}
#[inline]
fn circuit_len_tx_values(txs: usize) -> usize {
N_BYTES_TX * (txs) + N_BYTES_ONE
}
#[inline]
fn circuit_len_tx_id(txs: usize) -> usize {
N_BYTES_U64 * TX_LEN * txs + N_BYTES_U64 }
#[inline]
fn circuit_len_tx_index(txs: usize) -> usize {
N_BYTES_U64 * TX_LEN * txs + N_BYTES_U64 }
#[inline]
fn circuit_len_withdrawal(withdrawals: usize) -> usize {
N_BYTES_WITHDRAWAL * withdrawals
}
fn assign_empty_txtable_row(
&self,
region: &mut Region<'_, F>,
offset: usize,
) -> Result<(), Error> {
region.assign_advice(
|| "tx_id_inv",
self.tx_id_inv,
offset,
|| Value::known(F::ZERO),
)?;
region.assign_advice(
|| "tx_value_lo_inv",
self.tx_value_lo_inv,
offset,
|| Value::known(F::ZERO),
)?;
region.assign_advice(
|| "is_final",
self.is_final,
offset,
|| Value::known(F::ZERO),
)?;
region.assign_advice(
|| "gas_cost",
self.calldata_gas_cost,
offset,
|| Value::known(F::ZERO),
)?;
region.assign_advice(
|| "tx_id",
self.tx_table.tx_id,
offset,
|| Value::known(F::ZERO),
)?;
region.assign_fixed(
|| "tag",
self.tx_table.tag,
offset,
|| Value::known(F::from(TxFieldTag::Null as u64)),
)?;
region.assign_advice(
|| "index",
self.tx_table.index,
offset,
|| Value::known(F::ZERO),
)?;
WordLoHi::default().into_value().assign_advice(
region,
|| "tx_value",
self.tx_table.value,
offset,
)?;
Ok(())
}
fn reset_rpi_digest_row(&self, region: &mut Region<'_, F>, offset: usize) -> Result<(), Error> {
region.assign_advice(
|| "rpi_digest_bytes_limbs",
self.rpi_digest_bytes_limbs,
offset,
|| Value::known(F::ZERO),
)?;
Ok(())
}
fn reset_rpi_bytes_row(&self, region: &mut Region<'_, F>, offset: usize) -> Result<(), Error> {
region.assign_fixed(
|| "q_rpi_value_start",
self.q_rpi_value_start,
offset,
|| Value::known(F::ZERO),
)?;
region.assign_advice(
|| "rpi_bytes",
self.rpi_bytes,
offset,
|| Value::known(F::ZERO),
)?;
region.assign_advice(
|| "rpi_bytes_keccak_rlc",
self.rpi_bytes_keccak_rlc,
offset,
|| Value::known(F::ZERO),
)?;
region.assign_advice(
|| "rpi_value_lc",
self.rpi_value_lc,
offset,
|| Value::known(F::ZERO),
)?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn assign_tx_row(
&self,
region: &mut Region<'_, F>,
offset: usize,
tx_id: u64,
tag: TxFieldTag,
index: u64,
tx_value_bytes_le: &[u8],
rpi_bytes_keccak_rlc: &mut Value<F>,
challenges: &Challenges<Value<F>>,
current_rpi_offset: &mut usize,
rpi_bytes: &mut [u8],
zero_cell: AssignedCell<F, F>,
) -> Result<(), Error> {
let tx_id_inv = if tag != TxFieldTag::CallDataLength {
let x = F::from(tag as u64) - F::from(TxFieldTag::CallDataLength as u64);
x.invert().unwrap_or(F::ZERO)
} else {
F::ZERO
};
let tag = F::from(tag as u64);
let tx_value = WordLoHi::new([
from_bytes::value(
&tx_value_bytes_le[..min(N_BYTES_HALF_WORD, tx_value_bytes_le.len())],
),
if tx_value_bytes_le.len() > N_BYTES_HALF_WORD {
from_bytes::value(&tx_value_bytes_le[N_BYTES_HALF_WORD..])
} else {
F::ZERO
},
])
.into_value();
let tx_value_inv = tx_value.map(|t| t.map(|x| x.invert().unwrap_or(F::ZERO)));
self.q_tx_table.enable(region, offset)?;
let tx_id_assignedcell = region.assign_advice(
|| "tx_id",
self.tx_table.tx_id,
offset,
|| Value::known(F::from(tx_id)),
)?;
region.assign_fixed(|| "tag", self.tx_table.tag, offset, || Value::known(tag))?;
let tx_index_assignedcell = region.assign_advice(
|| "index",
self.tx_table.index,
offset,
|| Value::known(F::from(index)),
)?;
let tx_value_assignedcell =
tx_value.assign_advice(region, || "tx_value", self.tx_table.value, offset)?;
let (_, raw_tx_id) = self.assign_raw_bytes(
region,
&tx_id.to_le_bytes(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
region.constrain_equal(tx_id_assignedcell.cell(), raw_tx_id.lo().cell())?;
let (_, raw_tx_index) = self.assign_raw_bytes(
region,
&index.to_le_bytes(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
region.constrain_equal(tx_index_assignedcell.cell(), raw_tx_index.lo().cell())?;
let (_, raw_tx_value) = self.assign_raw_bytes(
region,
tx_value_bytes_le,
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell,
)?;
region.constrain_equal(tx_value_assignedcell.lo().cell(), raw_tx_value.lo().cell())?;
region.constrain_equal(tx_value_assignedcell.hi().cell(), raw_tx_value.hi().cell())?;
region.assign_advice(
|| "tx_id_inv",
self.tx_id_inv,
offset,
|| Value::known(tx_id_inv),
)?;
region.assign_advice(
|| "tx_value_lo_inv",
self.tx_value_lo_inv,
offset,
|| tx_value_inv.lo(),
)?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn assign_tx_calldata_row(
&self,
region: &mut Region<'_, F>,
offset: usize,
tx_id: usize,
tx_id_next: usize,
index: usize,
tx_value_byte: u8,
rpi_bytes_keccak_rlc: &mut Value<F>,
challenges: &Challenges<Value<F>>,
current_rpi_offset: &mut usize,
rpi_bytes: &mut [u8],
is_final: bool,
gas_cost: F,
zero_cell: AssignedCell<F, F>,
) -> Result<AssignedCell<F, F>, Error> {
let tx_id = F::from(tx_id as u64);
let tx_id_inv = tx_id.invert().unwrap_or(F::ZERO);
let tx_id_diff = F::from(tx_id_next as u64) - tx_id;
let tx_id_diff_inv = tx_id_diff.invert().unwrap_or(F::ZERO);
let tag = F::from(TxFieldTag::CallData as u64);
let index = F::from(index as u64);
let tx_value: WordLoHi<Value<F>> = WordLoHi::from(tx_value_byte).into_value();
let tx_value_inv = tx_value.map(|t| t.map(|x| x.invert().unwrap_or(F::ZERO)));
let is_final = if is_final { F::ONE } else { F::ZERO };
self.q_tx_calldata.enable(region, offset)?;
region.assign_advice(
|| "tx_id",
self.tx_table.tx_id,
offset,
|| Value::known(tx_id),
)?;
region.assign_advice(
|| "tx_id_inv",
self.tx_id_inv,
offset,
|| Value::known(tx_id_inv),
)?;
region.assign_fixed(|| "tag", self.tx_table.tag, offset, || Value::known(tag))?;
region.assign_advice(
|| "index",
self.tx_table.index,
offset,
|| Value::known(index),
)?;
let tx_value_cell =
tx_value.assign_advice(region, || "tx_value", self.tx_table.value, offset)?;
region.assign_advice(
|| "tx_value_lo_inv",
self.tx_value_lo_inv,
offset,
|| tx_value_inv.lo(),
)?;
region.assign_advice(
|| "tx_id_diff_inv",
self.tx_id_diff_inv,
offset,
|| Value::known(tx_id_diff_inv),
)?;
region.assign_advice(
|| "is_final",
self.is_final,
offset,
|| Value::known(is_final),
)?;
region.assign_advice(
|| "gas_cost",
self.calldata_gas_cost,
offset,
|| Value::known(gas_cost),
)?;
let (rpi_bytes_keccakrlc_cell, rpi_value_lc_cell) = self.assign_raw_bytes(
region,
&[tx_value_byte],
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell,
)?;
region.constrain_equal(rpi_value_lc_cell.lo().cell(), tx_value_cell.lo().cell())?;
region.constrain_equal(rpi_value_lc_cell.hi().cell(), tx_value_cell.hi().cell())?;
Ok(rpi_bytes_keccakrlc_cell)
}
#[allow(clippy::too_many_arguments)]
fn assign_wd_table_row(
&self,
region: &mut Region<'_, F>,
offset: usize,
wd: &Withdrawal,
rpi_bytes_keccak_rlc: &mut Value<F>,
challenges: &Challenges<Value<F>>,
current_rpi_offset: &mut usize,
rpi_bytes: &mut [u8],
zero_cell: AssignedCell<F, F>,
) -> Result<(), Error> {
let id_assigned_cell = region.assign_advice(
|| "withdrawal_id",
self.wd_table.id,
offset,
|| Value::known(F::from(wd.id)),
)?;
let vid_assigned_cell = region.assign_advice(
|| "validator_id",
self.wd_table.validator_id,
offset,
|| Value::known(F::from(wd.validator_id)),
)?;
let address_assigned_cell = WordLoHi::<F>::from(wd.address).into_value().assign_advice(
region,
|| "address",
self.wd_table.address,
offset,
)?;
let amount_assigned_cell = region.assign_advice(
|| "amount",
self.wd_table.amount,
offset,
|| Value::known(F::from(wd.amount)),
)?;
let (_, raw_id) = self.assign_raw_bytes(
region,
&wd.id.to_le_bytes(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
region.constrain_equal(id_assigned_cell.cell(), raw_id.lo().cell())?;
let (_, raw_vid) = self.assign_raw_bytes(
region,
&wd.validator_id.to_le_bytes(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
region.constrain_equal(vid_assigned_cell.cell(), raw_vid.lo().cell())?;
let (_, raw_address) = self.assign_raw_bytes(
region,
&wd.address
.to_fixed_bytes()
.iter()
.copied()
.rev()
.collect_vec(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
region.constrain_equal(address_assigned_cell.lo().cell(), raw_address.lo().cell())?;
region.constrain_equal(address_assigned_cell.hi().cell(), raw_address.hi().cell())?;
let (_, raw_amount) = self.assign_raw_bytes(
region,
&wd.amount.to_le_bytes(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell,
)?;
region.constrain_equal(amount_assigned_cell.cell(), raw_amount.lo().cell())?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn assign_raw_bytes(
&self,
region: &mut Region<'_, F>,
value_bytes_le: &[u8],
rpi_bytes_keccak_rlc: &mut Value<F>,
rpi_bytes: &mut [u8],
current_rpi_offset: &mut usize,
challenges: &Challenges<Value<F>>,
zero_cell: AssignedCell<F, F>,
) -> Result<AssignedByteCells<F>, Error> {
assert!(!value_bytes_le.is_empty());
assert!(value_bytes_le.len() <= N_BYTES_WORD);
let keccak_rand = challenges.keccak_input();
let mut rpi_value_lc_cells: Vec<AssignedCell<F, F>> = vec![];
let mut rpi_bytes_keccakrlc_cells: Vec<AssignedCell<F, F>> = vec![];
let start_offset = *current_rpi_offset;
let value_bytes_be: Vec<u8> = value_bytes_le.iter().rev().copied().collect_vec();
let value_bytes_chunk: Vec<Vec<u8>> = value_bytes_be
.rchunks(N_BYTES_HALF_WORD)
.rev()
.map(|x| x.to_vec())
.collect();
*current_rpi_offset = value_bytes_chunk.iter().try_fold(
start_offset,
|mut offset, bytes| -> Result<usize, Error> {
bytes.iter().enumerate().try_fold(
Value::known(F::ZERO),
|rpi_value_lc, (i, byte)| -> Result<Value<F>, Error> {
region.assign_fixed(
|| "q_rpi_value_start",
self.q_rpi_value_start,
offset,
|| Value::known(if i == 0 { F::ONE } else { F::ZERO }),
)?;
let rpi_value_lc = if i == 0 {
Value::known(F::ZERO)
} else {
rpi_value_lc
}
.zip(Value::known(F::from(BYTE_POW_BASE)))
.and_then(|(acc, rand)| Value::known(acc * rand + F::from(*byte as u64)));
let rpi_value_lc_cell = region.assign_advice(
|| "rpi_value_lc",
self.rpi_value_lc,
offset,
|| rpi_value_lc,
)?;
if i == bytes.len() - 1 {
rpi_value_lc_cells.push(rpi_value_lc_cell);
}
rpi_bytes[offset] = *byte;
*rpi_bytes_keccak_rlc =
rpi_bytes_keccak_rlc
.zip(keccak_rand)
.and_then(|(acc, rand)| {
Value::known(acc * rand + F::from(*byte as u64))
});
self.q_rpi_byte_enable.enable(region, offset)?;
region.assign_advice(
|| "rpi_bytes",
self.rpi_bytes,
offset,
|| Value::known(F::from(*byte as u64)),
)?;
let rpi_bytes_keccakrlc_cell = region.assign_advice(
|| "rpi_bytes_keccak_rlc",
self.rpi_bytes_keccak_rlc,
offset,
|| *rpi_bytes_keccak_rlc,
)?;
if start_offset - offset == value_bytes_le.len() - 1 {
rpi_bytes_keccakrlc_cells.push(rpi_bytes_keccakrlc_cell);
}
offset = offset.saturating_sub(1);
Ok(rpi_value_lc)
},
)?;
Ok(offset)
},
)?;
assert!(rpi_value_lc_cells.len() <= 2); rpi_value_lc_cells.reverse(); assert!(rpi_bytes_keccakrlc_cells.len() == 1); Ok((
rpi_bytes_keccakrlc_cells[0].clone(),
WordLoHi::new(
(0..2) .map(|i| rpi_value_lc_cells.get(i).unwrap_or(&zero_cell).clone())
.collect_vec()
.try_into()
.unwrap(),
),
))
}
#[allow(clippy::too_many_arguments)]
fn assign_block_table(
&self,
region: &mut Region<'_, F>,
block_table_offset: &mut usize,
block_values: BlockValues,
rpi_bytes_keccak_rlc: &mut Value<F>,
challenges: &Challenges<Value<F>>,
current_rpi_offset: &mut usize,
rpi_bytes: &mut [u8],
zero_cell: AssignedCell<F, F>,
) -> Result<(), Error> {
let mut block_copy_cells = vec![];
let block_value = WordLoHi::from(block_values.coinbase)
.into_value()
.assign_advice(
region,
|| "coinbase",
self.block_table.value,
*block_table_offset,
)?;
let (_, word) = self.assign_raw_bytes(
region,
&block_values
.coinbase
.to_fixed_bytes()
.iter()
.rev()
.copied()
.collect_vec(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
block_copy_cells.push((block_value, word));
*block_table_offset += 1;
let block_value = WordLoHi::from(block_values.gas_limit)
.into_value()
.assign_advice(
region,
|| "gas_limit",
self.block_table.value,
*block_table_offset,
)?;
let (_, word) = self.assign_raw_bytes(
region,
&block_values.gas_limit.to_le_bytes(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
block_copy_cells.push((block_value, word));
*block_table_offset += 1;
let block_value = WordLoHi::from(block_values.number)
.into_value()
.assign_advice(
region,
|| "number",
self.block_table.value,
*block_table_offset,
)?;
let (_, word) = self.assign_raw_bytes(
region,
&block_values.number.to_le_bytes(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
block_copy_cells.push((block_value, word));
*block_table_offset += 1;
let block_value = WordLoHi::from(block_values.timestamp)
.into_value()
.assign_advice(
region,
|| "timestamp",
self.block_table.value,
*block_table_offset,
)?;
let (_, word) = self.assign_raw_bytes(
region,
&block_values.timestamp.to_le_bytes(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
block_copy_cells.push((block_value, word));
*block_table_offset += 1;
let block_value = WordLoHi::from(block_values.difficulty)
.into_value()
.assign_advice(
region,
|| "difficulty",
self.block_table.value,
*block_table_offset,
)?;
let (_, word) = self.assign_raw_bytes(
region,
&block_values.difficulty.to_le_bytes(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
block_copy_cells.push((block_value, word));
*block_table_offset += 1;
let block_value = WordLoHi::from(block_values.base_fee)
.into_value()
.assign_advice(
region,
|| "base_fee",
self.block_table.value,
*block_table_offset,
)?;
let (_, word) = self.assign_raw_bytes(
region,
&block_values.base_fee.to_le_bytes(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
block_copy_cells.push((block_value, word));
*block_table_offset += 1;
let block_value = WordLoHi::from(block_values.chain_id)
.into_value()
.assign_advice(
region,
|| "chain_id",
self.block_table.value,
*block_table_offset,
)?;
let (_, word) = self.assign_raw_bytes(
region,
&block_values.chain_id.to_le_bytes(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
block_copy_cells.push((block_value, word));
*block_table_offset += 1;
let block_value = WordLoHi::from(block_values.withdrawals_root)
.into_value()
.assign_advice(
region,
|| "withdrawals_root",
self.block_table.value,
*block_table_offset,
)?;
let (_, word) = self.assign_raw_bytes(
region,
&block_values.withdrawals_root.to_le_bytes(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
block_copy_cells.push((block_value, word));
*block_table_offset += 1;
for prev_hash in block_values.history_hashes {
let block_value = WordLoHi::from(prev_hash).into_value().assign_advice(
region,
|| "prev_hash",
self.block_table.value,
*block_table_offset,
)?;
let (_, word) = self.assign_raw_bytes(
region,
&prev_hash
.to_fixed_bytes()
.iter()
.rev()
.copied()
.collect_vec(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
block_copy_cells.push((block_value, word));
*block_table_offset += 1;
}
block_copy_cells.iter().try_for_each(|(left, right)| {
region.constrain_equal(left.lo().cell(), right.lo().cell())?;
region.constrain_equal(left.hi().cell(), right.hi().cell())?;
Ok::<(), Error>(())
})?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn assign_extra_fields(
&self,
region: &mut Region<'_, F>,
extra: ExtraValues,
rpi_bytes_keccak_rlc: &mut Value<F>,
challenges: &Challenges<Value<F>>,
current_rpi_offset: &mut usize,
rpi_bytes: &mut [u8],
zero_cell: AssignedCell<F, F>,
) -> Result<(), Error> {
self.assign_raw_bytes(
region,
&extra
.block_hash
.to_fixed_bytes()
.iter()
.copied()
.rev()
.collect_vec(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
self.assign_raw_bytes(
region,
&extra
.state_root
.to_fixed_bytes()
.iter()
.copied()
.rev()
.collect_vec(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
self.assign_raw_bytes(
region,
&extra
.prev_state_root
.to_fixed_bytes()
.iter()
.copied()
.rev()
.collect_vec(),
rpi_bytes_keccak_rlc,
rpi_bytes,
current_rpi_offset,
challenges,
zero_cell,
)?;
Ok(())
}
fn assign_rpi_digest_word(
&self,
region: &mut Region<'_, F>,
digest_word: WordLoHi<F>,
) -> Result<WordLoHi<AssignedCell<F, F>>, Error> {
let lo_assigned_cell = region.assign_advice(
|| "rpi_digest_bytes_limbs_lo",
self.rpi_digest_bytes_limbs,
0,
|| digest_word.into_value().lo(),
)?;
let hi_assigned_cell = region.assign_advice(
|| "rpi_digest_bytes_limbs_hi",
self.rpi_digest_bytes_limbs,
1,
|| digest_word.into_value().hi(),
)?;
Ok(WordLoHi::new([lo_assigned_cell, hi_assigned_cell]))
}
}
#[derive(Clone, Default, Debug)]
pub struct PiCircuit<F: Field> {
max_txs: usize,
max_withdrawals: usize,
max_calldata: usize,
pub public_data: PublicData,
_marker: PhantomData<F>,
}
impl<F: Field> PiCircuit<F> {
pub fn new(
max_txs: usize,
max_withdrawals: usize,
max_calldata: usize,
public_data: PublicData,
) -> Self {
Self {
max_txs,
max_withdrawals,
max_calldata,
public_data,
_marker: PhantomData,
}
}
}
impl<F: Field> SubCircuit<F> for PiCircuit<F> {
type Config = PiCircuitConfig<F>;
fn unusable_rows() -> usize {
6
}
fn new_from_block(block: &witness::Block<F>, chunk: &Chunk<F>) -> Self {
let public_data = public_data_convert(block);
PiCircuit::new(
chunk.fixed_param.max_txs,
chunk.fixed_param.max_withdrawals,
chunk.fixed_param.max_calldata,
public_data,
)
}
fn min_num_rows_block(block: &witness::Block<F>, chunk: &Chunk<F>) -> (usize, usize) {
let calldata_len = block.txs.iter().map(|tx| tx.call_data.len()).sum();
(
Self::Config::circuit_len_all(block.txs.len(), block.withdrawals().len(), calldata_len),
Self::Config::circuit_len_all(
chunk.fixed_param.max_txs,
chunk.fixed_param.max_withdrawals,
chunk.fixed_param.max_calldata,
),
)
}
fn instance(&self) -> Vec<Vec<F>> {
let rpi_digest_byte_field = self.public_data.get_rpi_digest_word(
self.max_txs,
self.max_withdrawals,
self.max_calldata,
);
vec![vec![rpi_digest_byte_field.lo(), rpi_digest_byte_field.hi()]]
}
fn synthesize_sub(
&self,
config: &Self::Config,
challenges: &Challenges<Value<F>>,
layouter: &mut impl Layouter<F>,
) -> Result<(), Error> {
layouter.assign_region(
|| "fixed u16 table",
|mut region| {
for i in 0..(1 << 16) {
region.assign_fixed(
|| format!("row_{}", i),
config.fixed_u16,
i,
|| Value::known(F::from(i as u64)),
)?;
}
Ok(())
},
)?;
let digest_word_assigned = layouter.assign_region(
|| "region 0",
|mut region| {
config.tx_table.annotate_columns_in_region(&mut region);
config.wd_table.annotate_columns_in_region(&mut region);
config.block_table.annotate_columns_in_region(&mut region);
config.keccak_table.annotate_columns_in_region(&mut region);
region.name_column(|| "q_rpi_value_start", config.q_rpi_value_start);
region.name_column(|| "rpi_bytes", config.rpi_bytes);
region.name_column(|| "rpi_bytes_keccak_rlc", config.rpi_bytes_keccak_rlc);
region.name_column(|| "rpi_value_lc", config.rpi_value_lc);
region.name_column(|| "q_digest_value_start", config.q_digest_value_start);
region.name_column(|| "rpi_digest_bytes", config.rpi_digest_bytes);
region.name_column(|| "rpi_digest_bytes_lc", config.rpi_digest_bytes_limbs);
region.name_column(|| "tx_id_inv", config.tx_id_inv);
region.name_column(|| "tx_value_lo_inv", config.tx_value_lo_inv);
region.name_column(|| "tx_id_diff_inv", config.tx_id_diff_inv);
region.name_column(|| "fixed_u16", config.fixed_u16);
region.name_column(|| "calldata_gas_cost", config.calldata_gas_cost);
region.name_column(|| "is_final", config.is_final);
region.name_column(|| "Public_Inputs", config.pi_instance);
let circuit_len = config.circuit_len();
let mut rpi_bytes = vec![0u8; circuit_len];
let mut rpi_bytes_keccak_rlc = Value::known(F::ZERO);
let mut current_rpi_offset: usize = circuit_len - 1;
let start_offset = current_rpi_offset;
config.q_digest_last.enable(&mut region, N_BYTES_WORD - 1)?; config.q_bytes_last.enable(&mut region, start_offset)?;
config.reset_rpi_bytes_row(&mut region, start_offset + 1)?;
config.reset_rpi_digest_row(&mut region, N_BYTES_WORD)?;
let block_values = self.public_data.get_block_table_values();
let mut block_table_offset = 0;
let zero_word = WordLoHi::default().into_value().assign_advice(
&mut region,
|| "zero",
config.block_table.value,
block_table_offset,
)?;
let zero_cell = zero_word.hi();
let (_, _) = config.assign_raw_bytes(
&mut region,
&0u8.to_le_bytes(),
&mut rpi_bytes_keccak_rlc,
&mut rpi_bytes,
&mut current_rpi_offset,
challenges,
zero_cell.clone(),
)?;
block_table_offset += 1;
config.assign_block_table(
&mut region,
&mut block_table_offset,
block_values,
&mut rpi_bytes_keccak_rlc,
challenges,
&mut current_rpi_offset,
&mut rpi_bytes,
zero_cell.clone(),
)?;
assert_eq!(
start_offset - current_rpi_offset,
N_BYTES_ONE + N_BYTES_BLOCK
);
let extra_vals = self.public_data.get_extra_values();
config.assign_extra_fields(
&mut region,
extra_vals,
&mut rpi_bytes_keccak_rlc,
challenges,
&mut current_rpi_offset,
&mut rpi_bytes,
zero_cell,
)?;
assert_eq!(
start_offset - current_rpi_offset,
N_BYTES_ONE + N_BYTES_BLOCK + N_BYTES_EXTRA_VALUE
);
let mut tx_table_offset = 0;
let txs = self.public_data.get_tx_table_values();
assert!(txs.len() <= config.max_txs);
let tx_default = TxValues::default();
let zero_cell = WordLoHi::default()
.into_value()
.assign_advice(&mut region, || "tx_value", config.tx_table.value, 0)?
.hi();
config.assign_tx_row(
&mut region,
tx_table_offset,
0u64,
TxFieldTag::Null,
0u64,
&[0u8; 1],
&mut rpi_bytes_keccak_rlc,
challenges,
&mut current_rpi_offset,
&mut rpi_bytes,
zero_cell.clone(),
)?;
tx_table_offset += 1;
iter::empty()
.chain(&txs)
.chain((0..(config.max_txs - txs.len())).map(|_| &tx_default))
.enumerate()
.try_for_each(|(i, tx)| -> Result<(), Error> {
for (tag, value_bytes) in &[
(TxFieldTag::Nonce, tx.nonce.to_le_bytes().to_vec()),
(TxFieldTag::Gas, tx.gas_limit.to_le_bytes().to_vec()),
(TxFieldTag::GasPrice, tx.gas_price.to_le_bytes().to_vec()),
(
TxFieldTag::CallerAddress,
tx.from_addr
.as_fixed_bytes()
.iter()
.copied()
.rev()
.collect_vec(),
),
(
TxFieldTag::CalleeAddress,
tx.to_addr
.as_fixed_bytes()
.iter()
.copied()
.rev()
.collect_vec(),
),
(TxFieldTag::IsCreate, tx.is_create.to_le_bytes().to_vec()),
(TxFieldTag::Value, tx.value.to_le_bytes().to_vec()),
(
TxFieldTag::CallDataLength,
tx.call_data_len.to_le_bytes().to_vec(),
),
(
TxFieldTag::CallDataGasCost,
tx.call_data_gas_cost.to_le_bytes().to_vec(),
),
(TxFieldTag::TxSignHash, tx.tx_sign_hash.to_vec()),
] {
let i: u64 = i.try_into().unwrap();
config.assign_tx_row(
&mut region,
tx_table_offset,
i + 1,
*tag,
0,
value_bytes,
&mut rpi_bytes_keccak_rlc,
challenges,
&mut current_rpi_offset,
&mut rpi_bytes,
zero_cell.clone(),
)?;
tx_table_offset += 1;
}
Ok(())
})?;
assert_eq!(
start_offset - current_rpi_offset,
N_BYTES_ONE
+ N_BYTES_BLOCK
+ N_BYTES_EXTRA_VALUE
+ Self::Config::circuit_len_tx_id(config.max_txs)
+ Self::Config::circuit_len_tx_index(config.max_txs)
+ Self::Config::circuit_len_tx_values(config.max_txs)
);
let mut calldata_count = 0;
config
.q_calldata_start
.enable(&mut region, tx_table_offset)?;
let mut call_data_offset = TX_LEN * self.max_txs + EMPTY_TX_ROW_COUNT;
let txs = self.public_data.transactions.clone();
for (i, tx) in self.public_data.transactions.iter().enumerate() {
let call_data_length = tx.call_data.0.len();
let mut gas_cost = F::ZERO;
for (index, byte) in tx.call_data.0.iter().enumerate() {
assert!(calldata_count < config.max_calldata);
let is_final = index == call_data_length - 1;
gas_cost += if *byte == 0 {
F::from(ZERO_BYTE_GAS_COST)
} else {
F::from(NONZERO_BYTE_GAS_COST)
};
let tx_id_next = if is_final {
let mut j = i + 1;
while j < txs.len() && txs[j].call_data.0.is_empty() {
j += 1;
}
if j >= txs.len() {
0
} else {
j + 1
}
} else {
i + 1
};
config.assign_tx_calldata_row(
&mut region,
call_data_offset,
i + 1,
tx_id_next,
index,
*byte,
&mut rpi_bytes_keccak_rlc,
challenges,
&mut current_rpi_offset,
&mut rpi_bytes,
is_final,
gas_cost,
zero_cell.clone(),
)?;
call_data_offset += 1;
calldata_count += 1;
}
}
for _ in calldata_count..config.max_calldata {
config.assign_tx_calldata_row(
&mut region,
call_data_offset,
0, 0,
0,
0u8,
&mut rpi_bytes_keccak_rlc,
challenges,
&mut current_rpi_offset,
&mut rpi_bytes,
false,
F::ZERO,
zero_cell.clone(),
)?;
call_data_offset += 1;
}
assert_eq!(
start_offset - current_rpi_offset,
N_BYTES_ONE
+ N_BYTES_BLOCK
+ N_BYTES_EXTRA_VALUE
+ Self::Config::circuit_len_tx_id(config.max_txs)
+ Self::Config::circuit_len_tx_index(config.max_txs)
+ Self::Config::circuit_len_tx_values(config.max_txs)
+ config.max_calldata
);
config.assign_empty_txtable_row(&mut region, call_data_offset)?;
let mut withdrawal_offset = 0;
let wd_default = Withdrawal::default();
iter::empty()
.chain(&self.public_data.withdrawals)
.chain(
(0..(config.max_withdrawals - self.public_data.withdrawals.len()))
.map(|_| &wd_default),
)
.enumerate()
.try_for_each(|(_, wd)| -> Result<(), Error> {
config.assign_wd_table_row(
&mut region,
withdrawal_offset,
wd,
&mut rpi_bytes_keccak_rlc,
challenges,
&mut current_rpi_offset,
&mut rpi_bytes,
zero_cell.clone(),
)?;
withdrawal_offset += 1;
Ok(())
})?;
assert_eq!(current_rpi_offset, 0);
let digest_word = self.public_data.get_rpi_digest_word::<F>(
config.max_txs,
config.max_withdrawals,
config.max_calldata,
);
let digest_word_assigned =
config.assign_rpi_digest_word(&mut region, digest_word)?;
config.q_rpi_keccak_lookup.enable(&mut region, 0)?;
Ok(digest_word_assigned)
},
)?;
layouter.constrain_instance(digest_word_assigned.lo().cell(), config.pi_instance, 0)?;
layouter.constrain_instance(digest_word_assigned.hi().cell(), config.pi_instance, 1)?;
Ok(())
}
}