diff --git a/Cargo.lock b/Cargo.lock index 392cde259..e5130e438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4643,6 +4643,7 @@ dependencies = [ "rand", "rand_chacha", "rand_xorshift", + "rlp", "sha3 0.10.6", "strum", "strum_macros", diff --git a/gadgets/src/comparator.rs b/gadgets/src/comparator.rs new file mode 100644 index 000000000..cd39e38c5 --- /dev/null +++ b/gadgets/src/comparator.rs @@ -0,0 +1,108 @@ +//! Comparator can be used to compare LT, EQ (and indirectly GT) for two +//! expressions LHS and RHS. + +use eth_types::Field; +use halo2_proofs::{ + arithmetic::FieldExt, + circuit::{Chip, Region, Value}, + plonk::{ConstraintSystem, Error, Expression, VirtualCells}, + poly::Rotation, +}; + +use crate::{is_equal::IsEqualInstruction, less_than::LtInstruction}; + +use super::{is_equal::IsEqualChip, less_than::LtChip}; + +/// Instruction that the Comparator chip needs to implement. +pub trait ComparatorInstruction { + /// Assign the lhs and rhs witnesses to the Comparator chip's region. + fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + lhs: F, + rhs: F, + ) -> Result<(), Error>; +} + +/// Config for the Comparator chip. +#[derive(Clone, Debug)] +pub struct ComparatorConfig { + /// Config for the LessThan chip. + pub lt_chip: LtChip, + /// Config for the IsEqual chip. + pub eq_chip: IsEqualChip, +} + +impl ComparatorConfig { + /// Returns (lt, eq) for a comparison between lhs and rhs. + pub fn expr( + &self, + meta: &mut VirtualCells, + rotation: Option, + ) -> (Expression, Expression) { + ( + self.lt_chip.config.is_lt(meta, rotation), + self.eq_chip.config.is_equal_expression.clone(), + ) + } +} + +/// Chip to compare two expressions. +#[derive(Clone, Debug)] +pub struct ComparatorChip { + config: ComparatorConfig, +} + +impl ComparatorChip { + /// Configure the comparator chip. Returns the config. + pub fn configure( + meta: &mut ConstraintSystem, + q_enable: impl FnOnce(&mut VirtualCells) -> Expression + Clone, + lhs: impl FnOnce(&mut VirtualCells) -> Expression + Clone, + rhs: impl FnOnce(&mut VirtualCells) -> Expression + Clone, + ) -> ComparatorConfig { + let lt_config = LtChip::configure(meta, q_enable.clone(), lhs.clone(), rhs.clone()); + let eq_config = IsEqualChip::configure(meta, q_enable, lhs, rhs); + + ComparatorConfig { + lt_chip: LtChip::construct(lt_config), + eq_chip: IsEqualChip::construct(eq_config), + } + } + + /// Constructs a comparator chip given its config. + pub fn construct(config: ComparatorConfig) -> ComparatorChip { + ComparatorChip { config } + } +} + +impl ComparatorInstruction for ComparatorChip { + fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + lhs: F, + rhs: F, + ) -> Result<(), Error> { + self.config().lt_chip.assign(region, offset, lhs, rhs)?; + self.config() + .eq_chip + .assign(region, offset, Value::known(lhs), Value::known(rhs))?; + + Ok(()) + } +} + +impl Chip for ComparatorChip { + type Config = ComparatorConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} diff --git a/gadgets/src/is_equal.rs b/gadgets/src/is_equal.rs new file mode 100644 index 000000000..c6022d1e1 --- /dev/null +++ b/gadgets/src/is_equal.rs @@ -0,0 +1,248 @@ +//! IsEqual chip can be used to check equality of two expressions. + +use eth_types::Field; +use halo2_proofs::{ + arithmetic::FieldExt, + circuit::{Chip, Region, Value}, + plonk::{ConstraintSystem, Error, Expression, VirtualCells}, +}; + +use super::is_zero::{IsZeroChip, IsZeroInstruction}; + +/// Instruction that the IsEqual chip needs to implement. +pub trait IsEqualInstruction { + /// Assign lhs and rhs witnesses to the IsEqual chip's region. + fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + lhs: Value, + rhs: Value, + ) -> Result<(), Error>; +} + +/// Config for the IsEqual chip. +#[derive(Clone, Debug)] +pub struct IsEqualConfig { + /// Stores an IsZero chip. + pub is_zero_chip: IsZeroChip, + /// Expression that denotes whether the chip evaluated to equal or not. + pub is_equal_expression: Expression, +} + +/// Chip that compares equality between two expressions. +#[derive(Clone, Debug)] +pub struct IsEqualChip { + /// Config for the IsEqual chip. + pub(crate) config: IsEqualConfig, +} + +impl IsEqualChip { + /// Configure the IsEqual chip. + pub fn configure( + meta: &mut ConstraintSystem, + q_enable: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, + lhs: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, + rhs: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, + ) -> IsEqualConfig { + let value = |meta: &mut VirtualCells| lhs(meta) - rhs(meta); + let value_inv = meta.advice_column(); + + let is_zero_config = IsZeroChip::configure(meta, q_enable, value, value_inv); + let is_equal_expression = is_zero_config.is_zero_expression.clone(); + + IsEqualConfig { + is_zero_chip: IsZeroChip::construct(is_zero_config), + is_equal_expression, + } + } + + /// Construct an IsEqual chip given a config. + pub fn construct(config: IsEqualConfig) -> Self { + Self { config } + } +} + +impl IsEqualInstruction for IsEqualChip { + fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + lhs: Value, + rhs: Value, + ) -> Result<(), Error> { + self.config.is_zero_chip.assign(region, offset, lhs - rhs)?; + + Ok(()) + } +} + +impl Chip for IsEqualChip { + type Config = IsEqualConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + +#[cfg(test)] +mod tests { + use std::marker::PhantomData; + + use eth_types::Field; + use halo2_proofs::{ + arithmetic::FieldExt, + circuit::{Layouter, SimpleFloorPlanner, Value}, + dev::MockProver, + halo2curves::bn256::Fr as Fp, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Selector, VirtualCells}, + poly::Rotation, + }; + use rand::Rng; + + use super::{IsEqualChip, IsEqualConfig, IsEqualInstruction}; + use crate::util::Expr; + + #[derive(Clone, Debug)] + struct TestCircuitConfig { + q_enable: Selector, + value: Column, + check: Column, + is_equal: IsEqualConfig, + } + + #[derive(Default)] + struct TestCircuit { + values: Vec, + checks: Vec, + _marker: PhantomData, + } + + impl Circuit for TestCircuit { + type Config = TestCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let q_enable = meta.complex_selector(); + let value = meta.advice_column(); + let check = meta.advice_column(); + + let lhs = |meta: &mut VirtualCells| meta.query_advice(value, Rotation::cur()); + let rhs = |_meta: &mut VirtualCells| RHS.expr(); + + let is_equal = + IsEqualChip::configure(meta, |meta| meta.query_selector(q_enable), lhs, rhs); + + let config = Self::Config { + q_enable, + value, + check, + is_equal, + }; + + meta.create_gate("check is_equal", |meta| { + let q_enable = meta.query_selector(q_enable); + + let check = meta.query_advice(check, Rotation::cur()); + + vec![q_enable * (config.is_equal.is_equal_expression.clone() - check)] + }); + + config + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chip = IsEqualChip::construct(config.is_equal.clone()); + + layouter.assign_region( + || "witness", + |mut region| { + let checks = self.checks.clone(); + + for (idx, (value, check)) in self.values.iter().cloned().zip(checks).enumerate() + { + region.assign_advice( + || "value", + config.value, + idx + 1, + || Value::known(F::from(value)), + )?; + region.assign_advice( + || "check", + config.check, + idx + 1, + || Value::known(F::from(check as u64)), + )?; + config.q_enable.enable(&mut region, idx + 1)?; + chip.assign( + &mut region, + idx + 1, + Value::known(F::from(value)), + Value::known(F::from(RHS)), + )?; + } + + Ok(()) + }, + ) + } + } + + macro_rules! try_test { + ($values:expr, $checks:expr, $rhs:expr, $is_ok_or_err:ident) => { + let k = usize::BITS - $values.len().leading_zeros() + 2; + let circuit = TestCircuit:: { + values: $values, + checks: $checks, + _marker: PhantomData, + }; + let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); + assert!(prover.verify().$is_ok_or_err()); + }; + } + + fn random() -> u64 { + rand::thread_rng().gen::() + } + + #[test] + fn is_equal_gadget() { + try_test!( + vec![random(), 123, random(), 123, 123, random()], + vec![false, true, false, true, true, false], + 123, + is_ok + ); + try_test!( + vec![random(), 321321, 321321, random()], + vec![false, true, true, false], + 321321, + is_ok + ); + try_test!( + vec![random(), random(), random(), 1846123], + vec![false, false, false, true], + 1846123, + is_ok + ); + try_test!( + vec![123, 234, 345, 456], + vec![true, true, false, false], + 234, + is_err + ); + } +} diff --git a/gadgets/src/is_zero.rs b/gadgets/src/is_zero.rs index 2615e036f..71b629cad 100644 --- a/gadgets/src/is_zero.rs +++ b/gadgets/src/is_zero.rs @@ -4,6 +4,7 @@ //! - witnesses `inv0(value)`, where `inv0(x)` is 0 when `x` = 0, and //! `1/x` otherwise +use eth_types::Field; use halo2_proofs::{ arithmetic::FieldExt, circuit::{Chip, Region, Value}, @@ -38,7 +39,7 @@ pub struct IsZeroConfig { pub is_zero_expression: Expression, } -impl IsZeroConfig { +impl IsZeroConfig { /// Returns the is_zero expression pub fn expr(&self) -> Expression { self.is_zero_expression.clone() @@ -46,12 +47,13 @@ impl IsZeroConfig { } /// Wrapper arround [`IsZeroConfig`] for which [`Chip`] is implemented. +#[derive(Clone, Debug)] pub struct IsZeroChip { config: IsZeroConfig, } #[rustfmt::skip] -impl IsZeroChip { +impl IsZeroChip { /// Sets up the configuration of the chip by creating the required columns /// and defining the constraints that take part when using `is_zero` gate. /// @@ -101,7 +103,7 @@ impl IsZeroChip { } } -impl IsZeroInstruction for IsZeroChip { +impl IsZeroInstruction for IsZeroChip { fn assign( &self, region: &mut Region<'_, F>, @@ -122,7 +124,7 @@ impl IsZeroInstruction for IsZeroChip { } } -impl Chip for IsZeroChip { +impl Chip for IsZeroChip { type Config = IsZeroConfig; type Loaded = (); @@ -138,6 +140,8 @@ impl Chip for IsZeroChip { #[cfg(test)] mod test { use super::{IsZeroChip, IsZeroConfig, IsZeroInstruction}; + + use eth_types::Field; use halo2_proofs::{ arithmetic::FieldExt, circuit::{Layouter, SimpleFloorPlanner, Value}, @@ -200,7 +204,7 @@ mod test { _marker: PhantomData, } - impl Circuit for TestCircuit { + impl Circuit for TestCircuit { type Config = TestCircuitConfig; type FloorPlanner = SimpleFloorPlanner; @@ -332,7 +336,7 @@ mod test { _marker: PhantomData, } - impl Circuit for TestCircuit { + impl Circuit for TestCircuit { type Config = TestCircuitConfig; type FloorPlanner = SimpleFloorPlanner; diff --git a/gadgets/src/less_than.rs b/gadgets/src/less_than.rs index 01a158f7e..2d30c5b83 100644 --- a/gadgets/src/less_than.rs +++ b/gadgets/src/less_than.rs @@ -55,7 +55,7 @@ impl LtConfig { /// Chip that compares lhs < rhs. #[derive(Clone, Debug)] pub struct LtChip { - config: LtConfig, + pub(crate) config: LtConfig, } impl LtChip { diff --git a/gadgets/src/lib.rs b/gadgets/src/lib.rs index c093413f0..345704414 100644 --- a/gadgets/src/lib.rs +++ b/gadgets/src/lib.rs @@ -12,8 +12,10 @@ #![deny(clippy::debug_assert_with_mut_call)] pub mod binary_number; +pub mod comparator; pub mod comparison; pub mod evm_word; +pub mod is_equal; pub mod is_zero; pub mod less_than; pub mod monotone; diff --git a/mock/src/transaction.rs b/mock/src/transaction.rs index c96fd2382..86284e730 100644 --- a/mock/src/transaction.rs +++ b/mock/src/transaction.rs @@ -22,30 +22,53 @@ lazy_static! { pub static ref CORRECT_MOCK_TXS: Vec = { let mut rng = ChaCha20Rng::seed_from_u64(2u64); - vec![MockTransaction::default() - .from(AddrOrWallet::random(&mut rng)) - .to(MOCK_ACCOUNTS[0]) - .nonce(word!("0x103")) - .value(word!("0x3e8")) - .gas_price(word!("0x4d2")) - .input(Bytes::from(b"hello")) - .build(), + vec![ MockTransaction::default() - .from(AddrOrWallet::random(&mut rng)) - .to(MOCK_ACCOUNTS[1]) - .nonce(word!("0x104")) - .value(word!("0x3e8")) - .gas_price(word!("0x4d2")) - .input(Bytes::from(b"hello")) - .build(), + .transaction_idx(1u64) + .from(AddrOrWallet::random(&mut rng)) + .to(MOCK_ACCOUNTS[0]) + .nonce(word!("0x103")) + .value(word!("0x3e8")) + .gas_price(word!("0x4d2")) + .input(vec![1, 2, 3, 4, 5, 6, 7, 8, 9].into()) + .build(), MockTransaction::default() - .from(AddrOrWallet::random(&mut rng)) - .to(MOCK_ACCOUNTS[2]) - .nonce(word!("0x105")) - .value(word!("0x3e8")) - .gas_price(word!("0x4d2")) - .input(Bytes::from(b"hello")) - .build()] + .transaction_idx(2u64) + .from(AddrOrWallet::random(&mut rng)) + .to(MOCK_ACCOUNTS[1]) + .nonce(word!("0x104")) + .value(word!("0x3e8")) + .gas_price(word!("0x4d2")) + .input(Bytes::from(b"hello")) + .build(), + MockTransaction::default() + .transaction_idx(3u64) + .from(AddrOrWallet::random(&mut rng)) + .to(MOCK_ACCOUNTS[2]) + .nonce(word!("0x105")) + .value(word!("0x3e8")) + .gas_price(word!("0x4d2")) + .input(Bytes::from(b"hello")) + .build(), + MockTransaction::default() + .transaction_idx(4u64) + .from(AddrOrWallet::random(&mut rng)) + .to(MOCK_ACCOUNTS[3]) + .nonce(word!("0x106")) + .value(word!("0x3e8")) + .gas_price(word!("0x4d2")) + .input(Bytes::from(b"")) + .build(), + MockTransaction::default() + .transaction_idx(5u64) + .from(AddrOrWallet::random(&mut rng)) + .to(MOCK_ACCOUNTS[4]) + .nonce(word!("0x0")) + .value(word!("0x0")) + .gas_price(word!("0x4d2")) + .input(Bytes::from(b"hello")) + .build(), + ] }; } @@ -293,7 +316,7 @@ impl MockTransaction { } /// Set chain_id field for the MockTransaction. - pub(crate) fn chain_id(&mut self, chain_id: Word) -> &mut Self { + pub fn chain_id(&mut self, chain_id: Word) -> &mut Self { self.chain_id = chain_id; self } diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index c56005a6d..72c53ea69 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -33,6 +33,7 @@ maingate = { git = "https://github.com/privacy-scaling-explorations/halo2wrong" integer = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2022_09_09" } libsecp256k1 = "0.7" num-bigint = { version = "0.4" } +rlp = "0.5.1" subtle = "2.4" rand_chacha = "0.3" diff --git a/zkevm-circuits/src/lib.rs b/zkevm-circuits/src/lib.rs index b4bef6447..ef09210c7 100644 --- a/zkevm-circuits/src/lib.rs +++ b/zkevm-circuits/src/lib.rs @@ -22,6 +22,7 @@ pub mod evm_circuit; pub mod exp_circuit; pub mod keccak_circuit; pub mod pi_circuit; +pub mod rlp_circuit; pub mod state_circuit; pub mod super_circuit; pub mod table; diff --git a/zkevm-circuits/src/rlp_circuit.rs b/zkevm-circuits/src/rlp_circuit.rs new file mode 100644 index 000000000..051d084bb --- /dev/null +++ b/zkevm-circuits/src/rlp_circuit.rs @@ -0,0 +1,1704 @@ +//! Circuit implementation for RLP-encoding verification. Please refer: https://hackmd.io/@rohitnarurkar/S1zSz0KM9. + +use std::marker::PhantomData; + +use eth_types::Field; +use gadgets::binary_number::{BinaryNumberChip, BinaryNumberConfig}; +use gadgets::is_equal::{IsEqualChip, IsEqualConfig, IsEqualInstruction}; +use gadgets::util::{select, sum}; +use gadgets::{ + comparator::{ComparatorChip, ComparatorConfig, ComparatorInstruction}, + less_than::{LtChip, LtConfig, LtInstruction}, +}; +use halo2_proofs::plonk::SecondPhase; +use halo2_proofs::{ + circuit::{Layouter, Region, SimpleFloorPlanner, Value}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells}, + poly::Rotation, +}; + +// use crate::evm_circuit::table::FixedTableTag; +use crate::util::{Challenges, SubCircuit, SubCircuitConfig}; +use crate::witness::Block; +use crate::witness::RlpTxTag::{ + ChainId, DataPrefix, Gas, GasPrice, Nonce, Prefix, SigR, SigS, SigV, To, +}; +use crate::{ + evm_circuit::{ + util::{and, constraint_builder::BaseConstraintBuilder, not, or}, + witness::{RlpTxTag, RlpWitnessGen}, + }, + table::RlpTable, + util::Expr, + witness::{RlpDataType, SignedTransaction}, +}; + +#[derive(Clone, Debug)] +struct RlpTagROM { + // TODO: we can merge these three cols into one col using LC as + // as their type are (u8, u8, u8) + tag: Column, + tag_next: Column, + max_length: Column, +} + +#[derive(Clone, Debug)] +/// Config for the RLP circuit. +pub struct RlpCircuitConfig { + minimum_rows: usize, + /// Denotes whether or not the row is enabled. + q_usable: Column, + /// Denotes whether the row is the first row in the layout. + is_first: Column, + /// Denotes whether the row is the last byte in the RLP-encoded data. + is_last: Column, + /// Embedded lookup table in RLP circuit. + rlp_table: RlpTable, + /// Denotes the index of this row, starting from `1` and ending at `n` + /// where `n` is the byte length of the RLP-encoded data. + index: Column, + /// Denotes the index of this row, but reversed. It starts from `n` where + /// `n` is the byte length of the RLP-encoded data and ends at `1`. + rindex: Column, + /// Denotes the byte value at this row index from the RLP-encoded data. + byte_value: Column, + /// Denotes the RLC accumulator value used for call data bytes. + calldata_bytes_rlc_acc: Column, + /// Tag bits + tag_bits: BinaryNumberConfig, + /// Tag ROM + tag_rom: RlpTagROM, + /// Denotes the current tag's span in bytes. + tag_length: Column, + /// Denotes an accumulator for the length of data, in the case where len > + /// 55 and the length is represented in its big-endian form. + length_acc: Column, + /// Denotes an accumulator for the byte value field over all rows (bytes). + all_bytes_rlc_acc: Column, + /// Denotes if tag is simple + /// (the one that tag_bits provides has degree 4, which is too large) + is_simple_tag: Column, + /// Denotes if tag is Prefix + is_prefix_tag: Column, + /// Denotes if tag is DataPrefix + is_dp_tag: Column, + /// Comparison chip to check: 1 <= tag_index. + tag_index_cmp_1: ComparatorConfig, + /// Comparison chip to check: tag_index <= tag_length. + tag_index_length_cmp: ComparatorConfig, + /// Comparison chip to check: 1 <= tag_length. + tag_length_cmp_1: ComparatorConfig, + /// Lt chip to check: tag_index < 10. + tag_index_lt_10: LtConfig, + /// Lt chip to check: tag_index < 34. + tag_index_lt_34: LtConfig, + /// Lt chip to check: 127 < value. + value_gt_127: LtConfig, + /// Lt chip to check: 183 < value. + value_gt_183: LtConfig, + /// Lt chip to check: 191 < value. + value_gt_191: LtConfig, + /// Lt chip to check: 247 < value. + value_gt_247: LtConfig, + /// Lt chip to check: value < 129. + value_lt_129: LtConfig, + /// IsEq Chip to check: value == 128, + value_eq_128: IsEqualConfig, + /// Lt chip to check: value < 184. + value_lt_184: LtConfig, + /// Lt chip to check: value < 192. + value_lt_192: LtConfig, + /// Lt chip to check: value < 248. + value_lt_248: LtConfig, + /// Comparison chip to check: 0 <= length_acc. + length_acc_cmp_0: ComparatorConfig, +} + +impl RlpCircuitConfig { + pub(crate) fn configure( + meta: &mut ConstraintSystem, + rlp_table: &RlpTable, + challenges: &Challenges>, + ) -> Self { + let q_usable = meta.fixed_column(); + let is_first = meta.advice_column(); + let is_last = meta.advice_column(); + let index = meta.advice_column(); + let rindex = meta.advice_column(); + let byte_value = meta.advice_column(); + let calldata_bytes_rlc_acc = meta.advice_column_in(SecondPhase); + let tag_length = meta.advice_column(); + let length_acc = meta.advice_column(); + let all_bytes_rlc_acc = meta.advice_column_in(SecondPhase); + let evm_word_rand = challenges.evm_word(); + let keccak_input_rand = challenges.keccak_input(); + // these three columns are used to replace the degree-4 expressions with + // degree-1 expressions + let is_simple_tag = meta.advice_column(); + let is_prefix_tag = meta.advice_column(); + let is_dp_tag = meta.advice_column(); + let tag_bits = BinaryNumberChip::configure(meta, q_usable, Some(rlp_table.tag)); + let tag_rom = RlpTagROM { + tag: meta.fixed_column(), + tag_next: meta.fixed_column(), + max_length: meta.fixed_column(), + }; + + // Helper macro to declare booleans to check the current row tag. + macro_rules! is_tx_tag { + ($var:ident, $tag_variant:ident) => { + let $var = |meta: &mut VirtualCells| { + tag_bits.value_equals(RlpTxTag::$tag_variant, Rotation::cur())(meta) + }; + }; + } + is_tx_tag!(is_prefix, Prefix); + is_tx_tag!(is_nonce, Nonce); + is_tx_tag!(is_gas_price, GasPrice); + is_tx_tag!(is_gas, Gas); + is_tx_tag!(is_to, To); + is_tx_tag!(is_value, Value); + is_tx_tag!(is_data_prefix, DataPrefix); + is_tx_tag!(is_data, Data); + is_tx_tag!(is_chainid, ChainId); + is_tx_tag!(is_sig_v, SigV); + is_tx_tag!(is_sig_r, SigR); + is_tx_tag!(is_sig_s, SigS); + is_tx_tag!(is_padding, Padding); + + // Enable the comparator and lt chips if the current row is enabled. + let cmp_lt_enabled = |meta: &mut VirtualCells| { + let q_usable = meta.query_fixed(q_usable, Rotation::cur()); + let is_padding = is_padding(meta); + + q_usable * not::expr(is_padding) + }; + + let tag_index_cmp_1 = ComparatorChip::configure( + meta, + cmp_lt_enabled, + |_meta| 1.expr(), + |meta| meta.query_advice(rlp_table.tag_rindex, Rotation::cur()), + ); + let tag_index_length_cmp = ComparatorChip::configure( + meta, + cmp_lt_enabled, + |meta| meta.query_advice(rlp_table.tag_rindex, Rotation::cur()), + |meta| meta.query_advice(tag_length, Rotation::cur()), + ); + let tag_length_cmp_1 = ComparatorChip::configure( + meta, + cmp_lt_enabled, + |_meta| 1.expr(), + |meta| meta.query_advice(tag_length, Rotation::cur()), + ); + let tag_index_lt_10 = LtChip::configure( + meta, + cmp_lt_enabled, + |meta| meta.query_advice(rlp_table.tag_rindex, Rotation::cur()), + |_meta| 10.expr(), + ); + let tag_index_lt_34 = LtChip::configure( + meta, + cmp_lt_enabled, + |meta| meta.query_advice(rlp_table.tag_rindex, Rotation::cur()), + |_meta| 34.expr(), + ); + let value_gt_127 = LtChip::configure( + meta, + cmp_lt_enabled, + |_meta| 127.expr(), + |meta| meta.query_advice(byte_value, Rotation::cur()), + ); + let value_gt_183 = LtChip::configure( + meta, + cmp_lt_enabled, + |_meta| 183.expr(), + |meta| meta.query_advice(byte_value, Rotation::cur()), + ); + let value_gt_191 = LtChip::configure( + meta, + cmp_lt_enabled, + |_meta| 191.expr(), + |meta| meta.query_advice(byte_value, Rotation::cur()), + ); + let value_gt_247 = LtChip::configure( + meta, + cmp_lt_enabled, + |_meta| 247.expr(), + |meta| meta.query_advice(byte_value, Rotation::cur()), + ); + let value_lt_129 = LtChip::configure( + meta, + cmp_lt_enabled, + |meta| meta.query_advice(byte_value, Rotation::cur()), + |_meta| 129.expr(), + ); + let value_eq_128 = IsEqualChip::configure( + meta, + cmp_lt_enabled, + |meta| meta.query_advice(byte_value, Rotation::cur()), + |_| 128.expr(), + ); + let value_lt_184 = LtChip::configure( + meta, + cmp_lt_enabled, + |meta| meta.query_advice(byte_value, Rotation::cur()), + |_meta| 184.expr(), + ); + let value_lt_192 = LtChip::configure( + meta, + cmp_lt_enabled, + |meta| meta.query_advice(byte_value, Rotation::cur()), + |_meta| 192.expr(), + ); + let value_lt_248 = LtChip::configure( + meta, + cmp_lt_enabled, + |meta| meta.query_advice(byte_value, Rotation::cur()), + |_meta| 248.expr(), + ); + let length_acc_cmp_0 = ComparatorChip::configure( + meta, + cmp_lt_enabled, + |_meta| 0.expr(), + |meta| meta.query_advice(length_acc, Rotation::cur()), + ); + + meta.create_gate("is_simple_tag", |meta| { + let is_simple_tag = meta.query_advice(is_simple_tag, Rotation::cur()); + let is_prefix_tag = meta.query_advice(is_prefix_tag, Rotation::cur()); + let is_data_prefix_tag = meta.query_advice(is_dp_tag, Rotation::cur()); + let q_usable = meta.query_fixed(q_usable, Rotation::cur()); + let tags = sum::expr([ + is_nonce(meta), + is_gas_price(meta), + is_gas(meta), + is_to(meta), + is_value(meta), + is_sig_v(meta), + is_sig_r(meta), + is_sig_s(meta), + is_chainid(meta), + ]); + vec![ + q_usable.expr() * (is_simple_tag - tags), + q_usable.expr() * (is_prefix_tag - is_prefix(meta)), + q_usable.expr() * (is_data_prefix_tag - is_data_prefix(meta)), + ] + }); + + // TODO: add lookup for byte_value in the fixed byte table. + + meta.lookup_any("(tag, tag_next) in tag_ROM", |meta| { + let is_simple_tag = meta.query_advice(is_simple_tag, Rotation::cur()); + let tag = meta.query_advice(rlp_table.tag, Rotation::cur()); + let tag_next = meta.query_advice(rlp_table.tag, Rotation::next()); + let rom_tag = meta.query_fixed(tag_rom.tag, Rotation::cur()); + let rom_tag_next = meta.query_fixed(tag_rom.tag_next, Rotation::cur()); + let q_usable = meta.query_fixed(q_usable, Rotation::cur()); + let (_, tag_idx_eq_one) = tag_index_cmp_1.expr(meta, None); + let condition = q_usable * is_simple_tag * tag_idx_eq_one; + + vec![ + (condition.expr() * tag, rom_tag), + (condition * tag_next, rom_tag_next), + ] + }); + + meta.create_gate("Common constraints", |meta| { + let mut cb = BaseConstraintBuilder::new(9); + + let (tindex_lt, tindex_eq) = tag_index_cmp_1.expr(meta, None); + assert_eq!(tindex_lt.degree(), 1, "{}", tindex_lt.degree()); + assert_eq!(tindex_eq.degree(), 2, "{}", tindex_lt.degree()); + let (tlength_lt, tlength_eq) = tag_length_cmp_1.expr(meta, None); + let (tindex_lt_tlength, tindex_eq_tlength) = tag_index_length_cmp.expr(meta, None); + let (length_acc_gt_0, length_acc_eq_0) = length_acc_cmp_0.expr(meta, None); + let is_simple_tag = meta.query_advice(is_simple_tag, Rotation::cur()); + let is_prefix_tag = meta.query_advice(is_prefix_tag, Rotation::cur()); + let is_dp_tag = meta.query_advice(is_dp_tag, Rotation::cur()); + let is_tag_word = sum::expr([ + is_gas_price(meta), + is_value(meta), + is_sig_r(meta), + is_sig_s(meta), + ]); + + ////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////// RlpTxTag::Prefix ////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////// + cb.condition(is_prefix_tag.expr(), |cb| { + // tag_index < 10 + cb.require_equal( + "tag_index < 10", + tag_index_lt_10.is_lt(meta, None), + 1.expr(), + ); + + // tag_index >= 1 + cb.require_zero( + "tag_index >= 1", + not::expr(or::expr([tindex_lt.clone(), tindex_eq.clone()])), + ); + }); + + // if tag_index > 1 + cb.condition(is_prefix_tag.expr() * tindex_lt.clone(), |cb| { + cb.require_equal( + "tag::next == RlpTxTag::Prefix", + meta.query_advice(rlp_table.tag, Rotation::next()), + RlpTxTag::Prefix.expr(), + ); + cb.require_equal( + "tag_index::next == tag_index - 1", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + meta.query_advice(rlp_table.tag_rindex, Rotation::cur()) - 1.expr(), + ); + cb.require_equal( + "tag_length::next == tag_length", + meta.query_advice(tag_length, Rotation::next()), + meta.query_advice(tag_length, Rotation::cur()), + ); + }); + + // if tag_index == 1 + cb.condition(is_prefix_tag.expr() * tindex_eq.clone(), |cb| { + cb.require_equal( + "tag::next == RlpTxTag::Nonce", + meta.query_advice(rlp_table.tag, Rotation::next()), + RlpTxTag::Nonce.expr(), + ); + cb.require_equal( + "tag_index::next == tag_length::next", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + meta.query_advice(tag_length, Rotation::next()), + ); + // we add +1 to account for the 2 additional rows for RlpLength and Rlp. + cb.require_equal( + "rindex::next == length_acc + 1", + meta.query_advice(rindex, Rotation::next()), + meta.query_advice(length_acc, Rotation::cur()) + 1.expr(), + ); + }); + + // if tag_index == tag_length && tag_length > 1 + cb.condition( + is_prefix_tag.expr() * tindex_eq_tlength.clone() * tlength_lt.clone(), + |cb| { + cb.require_equal("247 < value", value_gt_247.is_lt(meta, None), 1.expr()); + // cb.require_equal("value < 256", value_lt_256.is_lt(meta, None), 1.expr()); + cb.require_equal( + "tag_index::next == value - 0xf7", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + meta.query_advice(byte_value, Rotation::cur()) - 247.expr(), + ); + cb.require_zero( + "length_acc == 0", + meta.query_advice(length_acc, Rotation::cur()), + ); + }, + ); + + // if tag_index == tag_length && tag_length == 1 + cb.condition( + is_prefix_tag.expr() * tindex_eq_tlength.clone() * tlength_eq.clone(), + |cb| { + cb.require_equal("191 < value", value_gt_191.is_lt(meta, None), 1.expr()); + cb.require_equal("value < 248", value_lt_248.is_lt(meta, None), 1.expr()); + cb.require_equal( + "length_acc == value - 0xc0 (1)", + meta.query_advice(length_acc, Rotation::cur()), + meta.query_advice(byte_value, Rotation::cur()) - 192.expr(), + ); + }, + ); + + // if tag_index < tag_length && tag_length > 1 + cb.condition( + is_prefix_tag.expr() * tindex_lt_tlength.clone() * tlength_lt.clone(), + |cb| { + cb.require_equal( + "length_acc == (length_acc::prev * 256) + value", + meta.query_advice(length_acc, Rotation::cur()), + meta.query_advice(length_acc, Rotation::prev()) * 256.expr() + + meta.query_advice(byte_value, Rotation::cur()), + ); + }, + ); + + ////////////////////////////////////////////////////////////////////////////////////// + //////// RlpTxTag::Nonce, GasPrice, Gas, To, Value, ChainID, SigV, SigR, SigS //////// + ////////////////////////////////////////////////////////////////////////////////////// + cb.condition(is_simple_tag.clone(), |cb| { + // TODO: add tag_length < max_length + + // tag_index >= 1 + cb.require_zero( + "1 <= tag_index", + not::expr(or::expr([tindex_lt.clone(), tindex_eq.clone()])), + ); + }); + + // cb.require_equal( + // "interm == is_simple_tag " + // is_simple_tag.clone() + // ) + // if tag_index == tag_length && tag_length == 1 + cb.condition( + is_simple_tag.clone() * tindex_eq_tlength.clone() * tlength_eq.clone(), + |cb| { + let value = select::expr( + value_eq_128.is_equal_expression.expr(), + 0.expr(), + meta.query_advice(byte_value, Rotation::cur()), + ); + cb.require_equal("byte_value < 129", value_lt_129.is_lt(meta, None), 1.expr()); + cb.require_equal( + "value == value_acc", + value, + meta.query_advice(rlp_table.value_acc, Rotation::cur()), + ); + }, + ); + + // if tag_index == tag_length && tag_length > 1 + cb.condition( + is_simple_tag.clone() * tindex_eq_tlength.clone() * tlength_lt.clone(), + |cb| { + cb.require_equal("127 < value", value_gt_127.is_lt(meta, None), 1.expr()); + cb.require_equal("value < 184", value_lt_184.is_lt(meta, None), 1.expr()); + cb.require_equal( + "length_acc == value - 0x80", + meta.query_advice(length_acc, Rotation::cur()), + meta.query_advice(byte_value, Rotation::cur()) - 128.expr(), + ); + cb.require_equal( + "tag_index::next == length_acc", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + meta.query_advice(length_acc, Rotation::cur()), + ); + cb.require_equal( + "value_acc == 0", + meta.query_advice(rlp_table.value_acc, Rotation::cur()), + 0.expr(), + ); + }, + ); + + // if tag_index > 1 + cb.condition(is_simple_tag.clone() * tindex_lt.clone(), |cb| { + cb.require_equal( + "tag::next == tag", + meta.query_advice(rlp_table.tag, Rotation::next()), + meta.query_advice(rlp_table.tag, Rotation::cur()), + ); + cb.require_equal( + "tag_index::next == tag_index - 1", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + meta.query_advice(rlp_table.tag_rindex, Rotation::cur()) - 1.expr(), + ); + cb.require_equal( + "tag_length::next == tag_length", + meta.query_advice(tag_length, Rotation::next()), + meta.query_advice(tag_length, Rotation::cur()), + ); + let power_base = select::expr( + is_tag_word, + evm_word_rand.expr(), + 256.expr(), + ); + cb.require_equal( + "[simple_tag] value_acc::next == value_acc::cur * power_base + value::next", + meta.query_advice(rlp_table.value_acc, Rotation::next()), + meta.query_advice(rlp_table.value_acc, Rotation::cur()) * power_base + + meta.query_advice(byte_value, Rotation::next()), + ); + }); + + // if tag_index == 1 + cb.condition(is_simple_tag * tindex_eq.clone(), |cb| { + cb.require_equal( + "tag_index::next == tag_length::next", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + meta.query_advice(tag_length, Rotation::next()), + ); + }); + + ////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////// RlpTxTag::DataPrefix /////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////// + cb.condition(is_dp_tag.expr(), |cb| { + // tag_index < 10 + cb.require_equal( + "tag_index < 10", + tag_index_lt_10.is_lt(meta, None), + 1.expr(), + ); + + // tag_index >= 1 + cb.require_zero( + "tag_index >= 1", + not::expr(or::expr([tindex_lt.clone(), tindex_eq.clone()])), + ); + }); + + // if tag_index > 1 + cb.condition(is_dp_tag.expr() * tindex_lt.clone(), |cb| { + cb.require_equal( + "tag::next == RlpTxTag::DataPrefix", + meta.query_advice(rlp_table.tag, Rotation::next()), + RlpTxTag::DataPrefix.expr(), + ); + cb.require_equal( + "tag_index::next == tag_index - 1", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + meta.query_advice(rlp_table.tag_rindex, Rotation::cur()) - 1.expr(), + ); + cb.require_equal( + "tag_length::next == tag_length", + meta.query_advice(tag_length, Rotation::next()), + meta.query_advice(tag_length, Rotation::cur()), + ); + }); + + // if length_acc == 0 + cb.condition( + is_dp_tag.expr() * tindex_eq.clone() * length_acc_eq_0, + |cb| { + let is_tx_hash = meta.query_advice(rlp_table.data_type, Rotation::cur()) + - RlpDataType::TxSign.expr(); + let tag_next = select::expr( + is_tx_hash, + SigV.expr(), + ChainId.expr(), + ); + cb.require_equal( + "tag::next == RlpTxTag::ChainId", + meta.query_advice(rlp_table.tag, Rotation::next()), + tag_next, + ); + cb.require_equal( + "tag_index::next == tag_length::next", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + meta.query_advice(tag_length, Rotation::next()), + ); + }, + ); + + // if length_acc > 0 + cb.condition( + is_dp_tag.expr() * tindex_eq.clone() * length_acc_gt_0, + |cb| { + cb.require_equal( + "tag::next == RlpTxTag::Data", + meta.query_advice(rlp_table.tag, Rotation::next()), + RlpTxTag::Data.expr(), + ); + cb.require_equal( + "tag_index::next == tag_length::next", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + meta.query_advice(tag_length, Rotation::next()), + ); + cb.require_equal( + "tag_length::next == length_acc", + meta.query_advice(tag_length, Rotation::next()), + meta.query_advice(length_acc, Rotation::cur()), + ); + }, + ); + + // if tag_index == tag_length && tag_length > 1 + cb.condition( + is_dp_tag.expr() * tindex_eq_tlength.clone() * tlength_lt.clone(), + |cb| { + cb.require_equal("value > 183", value_gt_183.is_lt(meta, None), 1.expr()); + cb.require_equal("value < 192", value_lt_192.is_lt(meta, None), 1.expr()); + cb.require_equal( + "tag_index == (value - 0xb7) + 1", + meta.query_advice(rlp_table.tag_rindex, Rotation::cur()), + meta.query_advice(byte_value, Rotation::cur()) - 182.expr(), + ); + cb.require_zero( + "length_acc == 0", + meta.query_advice(length_acc, Rotation::cur()), + ); + }, + ); + + // if tag_index < tag_length && tag_length > 1 + cb.condition( + is_dp_tag.expr() * tindex_lt_tlength * tlength_lt, + |cb| { + cb.require_equal( + "length_acc == (length_acc::prev * 256) + value", + meta.query_advice(length_acc, Rotation::cur()), + meta.query_advice(length_acc, Rotation::prev()) * 256.expr() + + meta.query_advice(byte_value, Rotation::cur()), + ); + }, + ); + + // if tag_index == tag_length && tag_length == 1 + cb.condition( + is_dp_tag.expr() * tindex_eq_tlength * tlength_eq, + |cb| { + cb.require_equal("127 < value", value_gt_127.is_lt(meta, None), 1.expr()); + cb.require_equal("value < 184", value_lt_184.is_lt(meta, None), 1.expr()); + cb.require_equal( + "length_acc == value - 0x80", + meta.query_advice(length_acc, Rotation::cur()), + meta.query_advice(byte_value, Rotation::cur()) - 128.expr(), + ); + }, + ); + + ////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////// RlpTxTag::Data ////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////// + cb.condition(is_data(meta), |cb| { + // tag_index >= 1 + cb.require_zero( + "tag_index >= 1", + not::expr(or::expr([tindex_lt.clone(), tindex_eq.clone()])), + ); + }); + + // if tag_index > 1 + cb.condition(is_data(meta) * tindex_lt.clone(), |cb| { + cb.require_equal( + "tag::next == RlpTxTag::Data", + meta.query_advice(rlp_table.tag, Rotation::next()), + RlpTxTag::Data.expr(), + ); + cb.require_equal( + "tag_rindex::next == tag_rindex - 1", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + meta.query_advice(rlp_table.tag_rindex, Rotation::cur()) - 1.expr(), + ); + cb.require_equal( + "tag_length::next == tag_length", + meta.query_advice(tag_length, Rotation::next()), + meta.query_advice(tag_length, Rotation::cur()), + ); + cb.require_equal( + "calldata_bytes_rlc_acc' == calldata_bytes_rlc_acc * r + byte_value'", + meta.query_advice(calldata_bytes_rlc_acc, Rotation::next()), + meta.query_advice(calldata_bytes_rlc_acc, Rotation::cur()) * keccak_input_rand.clone() + + meta.query_advice(byte_value, Rotation::next()), + ); + cb.require_equal( + "rlp_table.value_acc == byte_value", + meta.query_advice(rlp_table.value_acc, Rotation::cur()), + meta.query_advice(byte_value, Rotation::cur()), + ); + }); + + // if tag_index == 1 for TxSign + cb.condition( + and::expr(vec![ + is_data(meta), + tindex_eq.clone(), + not::expr(meta.query_advice(rlp_table.data_type, Rotation::cur())), + ]), + |cb| { + cb.require_equal( + "[data] tag::next == RlpTxTag::ChainId", + meta.query_advice(rlp_table.tag, Rotation::next()), + RlpTxTag::ChainId.expr(), + ); + cb.require_equal( + "tag_index::next == tag_length::next", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + meta.query_advice(tag_length, Rotation::next()), + ); + } + ); + + // if tag_index == 1 for TxHash + cb.condition( + and::expr(vec![ + is_data(meta), + tindex_eq.clone(), + meta.query_advice(rlp_table.data_type, Rotation::cur()), + ]), + |cb| { + cb.require_equal( + "tag::next == RlpTxTag::SigV", + meta.query_advice(rlp_table.tag, Rotation::next()), + RlpTxTag::SigV.expr(), + ); + cb.require_equal( + "tag_index::next == tag_length::next", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + meta.query_advice(tag_length, Rotation::next()), + ); + } + ); + + cb.gate(meta.query_fixed(q_usable, Rotation::cur())) + }); + + meta.create_gate("DataType::TxSign (unsigned transaction)", |meta| { + let mut cb = BaseConstraintBuilder::new(9); + + let (_, tindex_eq) = tag_index_cmp_1.expr(meta, None); + + ////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////// RlpTxTag::ChainID ///////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////// + + // if tag_index == 1 + cb.condition(is_chainid(meta) * tindex_eq, |cb| { + // checks for RlpTxTag::Zero on the next row. + cb.require_equal( + "next tag is Zero", + meta.query_advice(rlp_table.tag, Rotation::next()), + RlpTxTag::Zero.expr(), + ); + cb.require_equal( + "next tag is Zero => tag_rindex::next == 1", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + 1.expr(), + ); + cb.require_equal( + "next tag is Zero => value::next == 128", + meta.query_advice(byte_value, Rotation::next()), + 128.expr(), + ); + cb.require_equal( + "next tag is Zero => value_acc::next == 0", + meta.query_advice(rlp_table.value_acc, Rotation::next()), + 0.expr(), + ); + + // checks for RlpTxTag::Zero on the next(2) row. + cb.require_equal( + "tag::Rotation(2) == RlpTxTag::Zero", + meta.query_advice(rlp_table.tag, Rotation(2)), + RlpTxTag::Zero.expr(), + ); + cb.require_equal( + "tag_rindex::Rotation(2) == tag_length::Rotation(2)", + meta.query_advice(rlp_table.tag_rindex, Rotation(2)), + meta.query_advice(tag_length, Rotation(2)), + ); + cb.require_equal( + "next-to-next tag is Zero => tag_rindex::Rotation(2) == 1", + meta.query_advice(rlp_table.tag_rindex, Rotation(2)), + 1.expr(), + ); + cb.require_equal( + "next-to-next tag is Zero => value::Rotation(2) == 128", + meta.query_advice(byte_value, Rotation(2)), + 128.expr(), + ); + cb.require_equal( + "next-to-next tag is Zero => value_acc::Rotation(2) == 0", + meta.query_advice(rlp_table.value_acc, Rotation(2)), + 0.expr(), + ); + + // checks for RlpTxTag::RlpLength on the next(3) row. + cb.require_equal( + "tag::Rotation(3) == RlpTxTag::RlpLength", + meta.query_advice(rlp_table.tag, Rotation(3)), + RlpTxTag::RlpLength.expr(), + ); + cb.require_equal( + "tag_rindex::Rotation(3) == tag_length::Rotation(3)", + meta.query_advice(rlp_table.tag_rindex, Rotation(3)), + meta.query_advice(tag_length, Rotation(3)), + ); + cb.require_equal( + "tag_rindex::Rotation(3) == 1", + meta.query_advice(rlp_table.tag_rindex, Rotation(3)), + 1.expr(), + ); + cb.require_equal( + "value_acc::Rotation(3) == Rlp encoding length == index::Rotation(2)", + meta.query_advice(rlp_table.value_acc, Rotation(3)), + meta.query_advice(index, Rotation(2)), + ); + + // checks for RlpTxTag::Rlp on the next(4) row. + cb.require_equal( + "tag::Rotation(4) == RlpTxTag::Rlp", + meta.query_advice(rlp_table.tag, Rotation(4)), + RlpTxTag::Rlp.expr(), + ); + // reaches the end of an RLP(TxSign) instance + cb.require_zero( + "next(4) tag is Rlp => rindex == 0", + meta.query_advice(rindex, Rotation(4)), + ); + cb.require_equal( + "tag_rindex::Rotation(4) == tag_length::Rotation(4)", + meta.query_advice(rlp_table.tag_rindex, Rotation(4)), + meta.query_advice(tag_length, Rotation(4)), + ); + cb.require_equal( + "tag_rindex::Rotation(4) == 1", + meta.query_advice(rlp_table.tag_rindex, Rotation(4)), + 1.expr(), + ); + cb.require_equal( + "last tag is Rlp => value_acc::Rotation(4) == all_bytes_rlc_acc::Rotation(2)", + meta.query_advice(rlp_table.value_acc, Rotation(4)), + meta.query_advice(all_bytes_rlc_acc, Rotation(2)), + ); + cb.require_equal( + "last tag is Rlp => is_last::Rotation(4) == 1", + meta.query_advice(is_last, Rotation(4)), + 1.expr(), + ); + }); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_usable, Rotation::cur()), + not::expr(meta.query_advice(rlp_table.data_type, Rotation::cur())), + ])) + }); + + meta.create_gate("DataType::TxHash (signed transaction)", |meta| { + let mut cb = BaseConstraintBuilder::new(9); + + let (_, tindex_eq) = tag_index_cmp_1.expr(meta, None); + + // if tag_index == 1 + cb.condition(is_sig_s(meta) * tindex_eq, |cb| { + // rindex == 0 for the end of an RLP(TxHash) instance + cb.require_equal( + "next(2) tag is Rlp => rindex == 0", + meta.query_advice(rindex, Rotation(2)), + 0.expr(), + ); + // RlpTxTag::RlpLength checks. + cb.require_equal( + "next tag is RlpLength", + meta.query_advice(rlp_table.tag, Rotation::next()), + RlpTxTag::RlpLength.expr(), + ); + cb.require_equal( + "tag_rindex::next == 1", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + 1.expr(), + ); + cb.require_equal( + "tag::next == RlpLength => value_acc::next == index::cur", + meta.query_advice(rlp_table.value_acc, Rotation::next()), + meta.query_advice(index, Rotation::cur()), + ); + + // RlpTxTag::Rlp checks. + cb.require_equal( + "tag::Rotation(2) == RlpTxTag::Rlp", + meta.query_advice(rlp_table.tag, Rotation(2)), + RlpTxTag::Rlp.expr(), + ); + cb.require_equal( + "last tag is Rlp => value_acc::Rotation(2) == all_bytes_rlc_acc::cur()", + meta.query_advice(rlp_table.value_acc, Rotation(2)), + meta.query_advice(all_bytes_rlc_acc, Rotation::cur()), + ); + cb.require_equal( + "is_last::Rotation(2) == 1", + meta.query_advice(is_last, Rotation(2)), + 1.expr(), + ); + }); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_usable, Rotation::cur()), + meta.query_advice(rlp_table.data_type, Rotation::cur()), + ])) + }); + + // Constraints that always need to be satisfied. + meta.create_gate("always", |meta| { + let mut cb = BaseConstraintBuilder::new(9); + + cb.require_boolean( + "is_first is boolean", + meta.query_advice(is_first, Rotation::cur()), + ); + cb.require_boolean( + "is_last is boolean", + meta.query_advice(is_last, Rotation::cur()), + ); + cb.require_in_set( + "data_type is in set {TxHash, TxSign}", + meta.query_advice(rlp_table.data_type, Rotation::cur()), + vec![RlpDataType::TxHash.expr(), RlpDataType::TxSign.expr()], + ); + + cb.gate(meta.query_fixed(q_usable, Rotation::cur())) + }); + + // Constraints for the first row in the layout. + meta.create_gate("is_first == 1", |meta| { + let mut cb = BaseConstraintBuilder::new(9); + + cb.require_equal( + "value_rlc == value", + meta.query_advice(all_bytes_rlc_acc, Rotation::cur()), + meta.query_advice(byte_value, Rotation::cur()), + ); + cb.require_equal( + "index == 1", + meta.query_advice(index, Rotation::cur()), + 1.expr(), + ); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_usable, Rotation::cur()), + meta.query_advice(is_first, Rotation::cur()), + not::expr(is_padding(meta)), + ])) + }); + + // Constraints for every row except the last row in one RLP instance. + meta.create_gate("is_last == 0", |meta| { + let mut cb = BaseConstraintBuilder::new(9); + + cb.require_equal( + "index' == index + 1", + meta.query_advice(index, Rotation::next()), + meta.query_advice(index, Rotation::cur()) + 1.expr(), + ); + cb.require_equal( + "rindex' == rindex - 1", + meta.query_advice(rindex, Rotation::next()), + meta.query_advice(rindex, Rotation::cur()) - 1.expr(), + ); + cb.require_equal( + "tx_id' == tx_id", + meta.query_advice(rlp_table.tx_id, Rotation::next()), + meta.query_advice(rlp_table.tx_id, Rotation::cur()), + ); + cb.require_equal( + "data_type' == data_type", + meta.query_advice(rlp_table.data_type, Rotation::next()), + meta.query_advice(rlp_table.data_type, Rotation::cur()), + ); + cb.require_equal( + "all_bytes_rlc_acc' == (all_bytes_rlc_acc * r) + byte_value'", + meta.query_advice(all_bytes_rlc_acc, Rotation::next()), + meta.query_advice(all_bytes_rlc_acc, Rotation::cur()) * keccak_input_rand + + meta.query_advice(byte_value, Rotation::next()), + ); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_usable, Rotation::cur()), + not::expr(meta.query_advice(is_last, Rotation::cur())), + not::expr(is_padding(meta)), + ])) + }); + + // Constraints for the last row, i.e. RLP summary row. + meta.create_gate("is_last == 1", |meta| { + let mut cb = BaseConstraintBuilder::new(9); + + cb.require_equal( + "is_last == 1 then tag == RlpTxTag::Rlp", + meta.query_advice(rlp_table.tag, Rotation::cur()), + RlpTxTag::Rlp.expr(), + ); + + // if data_type::cur == TxHash + // - tx_id does not change. + // - TxSign rows follow. + cb.condition( + meta.query_advice(rlp_table.data_type, Rotation::cur()), + |cb| { + cb.require_equal( + "tx_id does not change", + meta.query_advice(rlp_table.tx_id, Rotation::cur()), + meta.query_advice(rlp_table.tx_id, Rotation::next()), + ); + cb.require_equal( + "TxSign rows follow TxHash rows", + meta.query_advice(rlp_table.data_type, Rotation::next()), + RlpDataType::TxSign.expr(), + ); + cb.require_equal( + "TxSign rows' first row is Prefix again", + meta.query_advice(rlp_table.tag, Rotation::next()), + RlpTxTag::Prefix.expr(), + ); + cb.require_equal( + "TxSign rows' first row starts with rlp_table.tag_rindex = tag_length", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + meta.query_advice(tag_length, Rotation::next()), + ); + }, + ); + + // if data_type::cur == TxSign and it was **not** + // the last tx in the layout (tag::next != Padding) + // - tx_id increments. + // - TxHash rows follow. + let is_tag_next_padding = + tag_bits.value_equals(RlpTxTag::Padding, Rotation::next())(meta); + cb.condition( + and::expr(vec![ + not::expr(meta.query_advice(rlp_table.data_type, Rotation::cur())), + not::expr(is_tag_next_padding), + ]), + |cb| { + cb.require_equal( + "tx_id increments", + meta.query_advice(rlp_table.tx_id, Rotation::cur()) + 1.expr(), + meta.query_advice(rlp_table.tx_id, Rotation::next()), + ); + cb.require_equal( + "TxHash rows follow TxSign rows", + meta.query_advice(rlp_table.data_type, Rotation::next()), + RlpDataType::TxHash.expr(), + ); + cb.require_equal( + "TxHash rows' first row is Prefix again", + meta.query_advice(rlp_table.tag, Rotation::next()), + RlpTxTag::Prefix.expr(), + ); + cb.require_equal( + "TxSign rows' first row starts with rlp_table.tag_rindex = tag_length", + meta.query_advice(rlp_table.tag_rindex, Rotation::next()), + meta.query_advice(tag_length, Rotation::next()), + ); + }, + ); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_usable, Rotation::cur()), + meta.query_advice(is_last, Rotation::cur()), + ])) + }); + + meta.create_gate("padding rows", |meta| { + let mut cb = BaseConstraintBuilder::new(9); + + cb.condition(is_padding(meta), |cb| { + cb.require_equal( + "tag_next == Padding", + meta.query_advice(rlp_table.tag, Rotation::next()), + RlpTxTag::Padding.expr(), + ); + + cb.require_zero( + "tx_id == 0", + meta.query_advice(rlp_table.tx_id, Rotation::cur()), + ); + cb.require_zero( + "data_type == 0", + meta.query_advice(rlp_table.data_type, Rotation::cur()), + ); + cb.require_zero( + "tag_rindex == 0", + meta.query_advice(rlp_table.tag_rindex, Rotation::cur()), + ); + cb.require_zero( + "value_acc == 0", + meta.query_advice(rlp_table.value_acc, Rotation::cur()), + ); + }); + + cb.gate(meta.query_fixed(q_usable, Rotation::cur())) + }); + + Self { + minimum_rows: meta.minimum_rows(), + q_usable, + is_first, + is_last, + rlp_table: *rlp_table, + index, + rindex, + byte_value, + calldata_bytes_rlc_acc, + tag_bits, + tag_rom, + tag_length, + length_acc, + all_bytes_rlc_acc, + is_simple_tag, + is_prefix_tag, + is_dp_tag, + tag_index_cmp_1, + tag_index_length_cmp, + tag_length_cmp_1, + tag_index_lt_10, + tag_index_lt_34, + value_gt_127, + value_gt_183, + value_gt_191, + value_gt_247, + value_lt_129, + value_eq_128, + value_lt_184, + value_lt_192, + value_lt_248, + length_acc_cmp_0, + } + } + + pub(crate) fn assign( + &self, + layouter: &mut impl Layouter, + signed_txs: &[SignedTransaction], + k: usize, + challenges: &Challenges>, + ) -> Result<(), Error> { + let keccak_input_rand = challenges.keccak_input(); + let tag_chip = BinaryNumberChip::construct(self.tag_bits); + let tag_index_cmp_1_chip = ComparatorChip::construct(self.tag_index_cmp_1.clone()); + let tag_index_length_cmp_chip = + ComparatorChip::construct(self.tag_index_length_cmp.clone()); + let tag_length_cmp_1_chip = ComparatorChip::construct(self.tag_length_cmp_1.clone()); + + let tag_index_lt_10_chip = LtChip::construct(self.tag_index_lt_10); + let tag_index_lt_34_chip = LtChip::construct(self.tag_index_lt_34); + + let value_gt_127_chip = LtChip::construct(self.value_gt_127); + let value_gt_183_chip = LtChip::construct(self.value_gt_183); + let value_gt_191_chip = LtChip::construct(self.value_gt_191); + let value_gt_247_chip = LtChip::construct(self.value_gt_247); + let value_lt_129_chip = LtChip::construct(self.value_lt_129); + let value_eq_128_chip = IsEqualChip::construct(self.value_eq_128.clone()); + let value_lt_184_chip = LtChip::construct(self.value_lt_184); + let value_lt_192_chip = LtChip::construct(self.value_lt_192); + let value_lt_248_chip = LtChip::construct(self.value_lt_248); + + let length_acc_cmp_0_chip = ComparatorChip::construct(self.length_acc_cmp_0.clone()); + + debug_assert!( + k >= self.minimum_rows, + "k: {}, minimum_rows: {}", + k, + self.minimum_rows, + ); + let padding_end_offset = k - self.minimum_rows + 1; + layouter.assign_region( + || "assign tag rom", + |mut region| { + for (i, (tag, tag_next, max_length)) in [ + (RlpTxTag::Nonce, RlpTxTag::GasPrice, 10), + (RlpTxTag::GasPrice, RlpTxTag::Gas, 34), + (RlpTxTag::Gas, RlpTxTag::To, 10), + (RlpTxTag::To, RlpTxTag::Value, 22), + (RlpTxTag::Value, RlpTxTag::DataPrefix, 34), + (RlpTxTag::ChainId, RlpTxTag::Zero, 10), + (RlpTxTag::SigV, RlpTxTag::SigR, 10), + (RlpTxTag::SigR, RlpTxTag::SigS, 34), + (RlpTxTag::SigS, RlpTxTag::RlpLength, 34), + ] + .into_iter() + .enumerate() + { + let offset = i; + region.assign_fixed( + || "tag", + self.tag_rom.tag, + offset, + || Value::known(F::from(tag as u64)), + )?; + region.assign_fixed( + || "tag_next", + self.tag_rom.tag_next, + offset, + || Value::known(F::from(tag_next as u64)), + )?; + region.assign_fixed( + || "max_length", + self.tag_rom.max_length, + offset, + || Value::known(F::from(max_length)), + )?; + } + + Ok(()) + }, + )?; + + layouter.assign_region( + || "assign RLP-encoded data", + |mut region| { + let mut offset = 0; + let simple_tags = [ + Nonce, + GasPrice, + Gas, + To, + RlpTxTag::Value, + SigV, + SigR, + SigS, + ChainId, + ]; + + for signed_tx in signed_txs.iter() { + // tx hash (signed tx) + let mut all_bytes_rlc_acc = Value::known(F::zero()); + let tx_hash_rows = signed_tx.gen_witness(challenges); + let n_rows = tx_hash_rows.len(); + for (idx, row) in tx_hash_rows + .iter() + .chain(signed_tx.rlp_rows(keccak_input_rand).iter()) + .enumerate() + { + // update value accumulator over the entire RLP encoding. + all_bytes_rlc_acc = all_bytes_rlc_acc + .zip(keccak_input_rand) + .map(|(acc, rand)| acc * rand + F::from(row.value as u64)); + + // q_usable + region.assign_fixed( + || format!("q_usable: {}", offset), + self.q_usable, + offset, + || Value::known(F::one()), + )?; + // is_first + region.assign_advice( + || format!("assign is_first {}", offset), + self.is_first, + offset, + || Value::known(F::from((idx == 0) as u64)), + )?; + // advices + let rindex = (n_rows + 2 - row.index) as u64; // rindex decreases from n_rows+1 to 0 + let rlp_table = &self.rlp_table; + let is_simple_tag = + simple_tags.iter().filter(|tag| **tag == row.tag).count(); + let is_prefix_tag = (row.tag == Prefix).into(); + let is_dp_tag = (row.tag == DataPrefix).into(); + + for (name, column, value) in [ + ("is_last", self.is_last, (row.index == n_rows + 2).into()), + ("tx_id", rlp_table.tx_id, row.tx_id as u64), + ("tag", rlp_table.tag, (row.tag as u64)), + ("is_simple_tag", self.is_simple_tag, is_simple_tag as u64), + ("is_prefix_tag", self.is_prefix_tag, is_prefix_tag), + ("is_dp_tag", self.is_dp_tag, is_dp_tag), + ("tag_index", rlp_table.tag_rindex, (row.tag_rindex as u64)), + ("data_type", rlp_table.data_type, (row.data_type as u64)), + ("index", self.index, (row.index as u64)), + ("rindex", self.rindex, (rindex)), + ("value", self.byte_value, (row.value as u64)), + ("tag_length", self.tag_length, (row.tag_length as u64)), + ("length_acc", self.length_acc, (row.length_acc)), + ] { + region.assign_advice( + || format!("assign {} {}", name, offset), + column, + offset, + || Value::known(F::from(value)), + )?; + } + for (name, column, value) in [ + ( + "rlp_table::value_acc", + self.rlp_table.value_acc, + row.value_acc, + ), + ( + "calldata_bytes_acc_rlc", + self.calldata_bytes_rlc_acc, + row.value_rlc_acc, + ), + ( + "all_bytes_rlc_acc", + self.all_bytes_rlc_acc, + all_bytes_rlc_acc, + ), + ] { + region.assign_advice( + || format!("assign {} {}", name, offset), + column, + offset, + || value, + )?; + } + + tag_chip.assign(&mut region, offset, &row.tag)?; + + for (chip, lhs, rhs) in [ + (&tag_index_cmp_1_chip, 1, row.tag_rindex as u64), + ( + &tag_index_length_cmp_chip, + row.tag_rindex, + row.tag_length as u64, + ), + (&tag_length_cmp_1_chip, 1, row.tag_length as u64), + (&length_acc_cmp_0_chip, 0, row.length_acc), + ] { + chip.assign(&mut region, offset, F::from(lhs as u64), F::from(rhs))?; + } + + value_eq_128_chip.assign( + &mut region, + offset, + Value::known(F::from(row.value as u64)), + Value::known(F::from(128u64)), + )?; + + for (chip, lhs, rhs) in [ + (&tag_index_lt_10_chip, row.tag_rindex, 10), + (&tag_index_lt_34_chip, row.tag_rindex, 34), + (&value_gt_127_chip, 127, row.value), + (&value_gt_183_chip, 183, row.value), + (&value_gt_191_chip, 191, row.value), + (&value_gt_247_chip, 247, row.value), + (&value_lt_129_chip, row.value as usize, 129), + (&value_lt_184_chip, row.value as usize, 184), + (&value_lt_192_chip, row.value as usize, 192), + (&value_lt_248_chip, row.value as usize, 248), + ] { + chip.assign( + &mut region, + offset, + F::from(lhs as u64), + F::from(rhs as u64), + )?; + } + + offset += 1; + } + + // tx sign (unsigned tx) + let mut all_bytes_rlc_acc = Value::known(F::zero()); + let tx_sign_rows = signed_tx.tx.gen_witness(challenges); + let n_rows = tx_sign_rows.len(); + for (idx, row) in tx_sign_rows + .iter() + .chain(signed_tx.tx.rlp_rows(challenges.keccak_input()).iter()) + .enumerate() + { + // update value accumulator over the entire RLP encoding. + all_bytes_rlc_acc = all_bytes_rlc_acc + .zip(keccak_input_rand) + .map(|(acc, rand)| acc * rand + F::from(row.value as u64)); + + // q_usable + region.assign_fixed( + || format!("q_usable: {}", offset), + self.q_usable, + offset, + || Value::known(F::one()), + )?; + // is_first + region.assign_advice( + || format!("assign is_first {}", offset), + self.is_first, + offset, + || Value::known(F::from((idx == 0) as u64)), + )?; + // advices + let rindex = (n_rows + 2 - row.index) as u64; // rindex decreases from n_rows+1 to 0 + let rlp_table = &self.rlp_table; + let is_simple_tag = + simple_tags.iter().filter(|tag| **tag == row.tag).count(); + let is_prefix_tag = (row.tag == Prefix).into(); + let is_dp_tag = (row.tag == DataPrefix).into(); + for (name, column, value) in [ + ("is_last", self.is_last, (row.index == n_rows + 2).into()), + ("tx_id", rlp_table.tx_id, row.tx_id as u64), + ("tag", rlp_table.tag, row.tag as u64), + ("is_simple_tag", self.is_simple_tag, is_simple_tag as u64), + ("is_prefix_tag", self.is_prefix_tag, is_prefix_tag), + ("is_dp_tag", self.is_dp_tag, is_dp_tag), + ("tag_rindex", rlp_table.tag_rindex, row.tag_rindex as u64), + ("data_type", rlp_table.data_type, row.data_type as u64), + ("index", self.index, row.index as u64), + ("rindex", self.rindex, rindex), + ("byte value", self.byte_value, row.value as u64), + ("tag_length", self.tag_length, row.tag_length as u64), + ("length_acc", self.length_acc, row.length_acc), + ] { + region.assign_advice( + || format!("assign {} {}", name, offset), + column, + offset, + || Value::known(F::from(value)), + )?; + } + for (name, column, value) in [ + ("rlp_table::value_acc", rlp_table.value_acc, row.value_acc), + ( + "calldata_bytes_rlc_acc", + self.calldata_bytes_rlc_acc, + row.value_rlc_acc, + ), + ( + "all_bytes_rlc_acc", + self.all_bytes_rlc_acc, + all_bytes_rlc_acc, + ), + ] { + region.assign_advice( + || format!("assign {} {}", name, offset), + column, + offset, + || value, + )?; + } + + tag_chip.assign(&mut region, offset, &row.tag)?; + + for (chip, lhs, rhs) in [ + (&tag_index_cmp_1_chip, 1, row.tag_rindex as u64), + ( + &tag_index_length_cmp_chip, + row.tag_rindex, + row.tag_length as u64, + ), + (&tag_length_cmp_1_chip, 1, row.tag_length as u64), + (&length_acc_cmp_0_chip, 0, row.length_acc), + ] { + chip.assign(&mut region, offset, F::from(lhs as u64), F::from(rhs))?; + } + + value_eq_128_chip.assign( + &mut region, + offset, + Value::known(F::from(row.value as u64)), + Value::known(F::from(128u64)), + )?; + + for (chip, lhs, rhs) in [ + (&tag_index_lt_10_chip, row.tag_rindex, 10), + (&tag_index_lt_34_chip, row.tag_rindex, 34), + (&value_gt_127_chip, 127, row.value), + (&value_gt_183_chip, 183, row.value), + (&value_gt_191_chip, 191, row.value), + (&value_gt_247_chip, 247, row.value), + (&value_lt_129_chip, row.value as usize, 129), + (&value_lt_184_chip, row.value as usize, 184), + (&value_lt_192_chip, row.value as usize, 192), + (&value_lt_248_chip, row.value as usize, 248), + ] { + chip.assign( + &mut region, + offset, + F::from(lhs as u64), + F::from(rhs as u64), + )?; + } + + offset += 1; + } + } + + // TODO: speed up the assignment of padding rows + let padding_start_offset = offset; + // end with padding rows. + for offset in padding_start_offset..padding_end_offset { + self.assign_padding_rows(&mut region, offset)?; + } + + Ok(()) + }, + ) + } + + fn assign_padding_rows(&self, region: &mut Region<'_, F>, offset: usize) -> Result<(), Error> { + for column in [ + self.rlp_table.tx_id, + self.rlp_table.tag_rindex, + self.rlp_table.value_acc, + self.rlp_table.data_type, + ] + .into_iter() + { + region.assign_advice( + || format!("padding row, offset: {}", offset), + column, + offset, + || Value::known(F::zero()), + )?; + } + region.assign_advice( + || format!("padding row, tag = Padding, offset: {}", offset), + self.rlp_table.tag, + offset, + || { + Value::known(F::from( + >::into(RlpTxTag::Padding) as u64 + )) + }, + )?; + region.assign_fixed( + || format!("padding row, offset: {}", offset), + self.q_usable, + offset, + || Value::known(F::one()), + )?; + + Ok(()) + } +} + +/// Circuit configuration arguments +pub struct RlpCircuitConfigArgs { + /// RlpTable + rlp_table: RlpTable, + /// Challenges + challenges: Challenges>, +} + +impl SubCircuitConfig for RlpCircuitConfig { + type ConfigArgs = RlpCircuitConfigArgs; + + fn new(meta: &mut ConstraintSystem, args: Self::ConfigArgs) -> Self { + RlpCircuitConfig::configure(meta, &args.rlp_table, &args.challenges) + } +} + +/// Circuit to verify RLP encoding is correct +#[derive(Clone, Debug)] +pub struct RlpCircuit { + /// Rlp encoding inputs + pub inputs: Vec, + /// Size of the circuit + pub size: usize, + _marker: PhantomData, +} + +impl Default for RlpCircuit { + fn default() -> Self { + Self { + inputs: vec![], + size: 0, + _marker: PhantomData, + } + } +} + +impl SubCircuit for RlpCircuit { + type Config = RlpCircuitConfig; + + fn new_from_block(block: &Block) -> Self { + let signed_txs = block + .txs + .iter() + .zip(block.sigs.iter()) + .map(|(tx, sig)| SignedTransaction { + tx: tx.clone(), + signature: *sig, + }) + .collect::>(); + + Self { + inputs: signed_txs, + // FIXME: this hard-coded size is used to pass unit test, we should use 1 << k instead. + size: 1 << 18, + _marker: Default::default(), + } + } + + fn synthesize_sub( + &self, + config: &Self::Config, + challenges: &Challenges>, + layouter: &mut impl Layouter, + ) -> Result<(), Error> { + config.assign(layouter, &self.inputs, self.size, challenges) + } +} + +impl Circuit for RlpCircuit { + type Config = (RlpCircuitConfig, Challenges); + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let rlp_table = RlpTable::construct(meta); + let challenges = Challenges::construct(meta); + let rand_exprs = challenges.exprs(meta); + let config = RlpCircuitConfig::configure(meta, &rlp_table, &rand_exprs); + + (config, challenges) + } + + fn synthesize( + &self, + (config, challenges): Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let challenges = challenges.values(&mut layouter); + config.assign( + &mut layouter, + self.inputs.as_slice(), + self.size, + &challenges, + ) + } +} + +#[cfg(test)] +mod tests { + use std::marker::PhantomData; + + use eth_types::Field; + use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; + use mock::CORRECT_MOCK_TXS; + + use crate::evm_circuit::witness::SignedTransaction; + + use super::RlpCircuit; + + fn verify_txs(k: u32, inputs: Vec, success: bool) { + let circuit = RlpCircuit:: { + inputs, + size: 1 << k, + _marker: PhantomData, + }; + + const NUM_BLINDING_ROWS: usize = 8; + let instance = vec![]; + let prover = MockProver::::run(k, &circuit, instance).unwrap(); + let err = prover.verify_par(); + let print_failures = true; + if err.is_err() && print_failures { + if let Some(e) = err.err() { + for s in e.iter() { + println!("{}", s); + } + } + } + let err = prover.verify_par(); + assert_eq!(err.is_ok(), success); + } + + #[test] + fn rlp_circuit_tx_1() { + verify_txs::(8, vec![CORRECT_MOCK_TXS[0].clone().into()], true); + verify_txs::(8, vec![CORRECT_MOCK_TXS[4].clone().into()], true); + } + + #[test] + fn rlp_circuit_tx_2() { + verify_txs::(8, vec![CORRECT_MOCK_TXS[1].clone().into()], true); + } + + #[test] + fn rlp_circuit_tx_3() { + verify_txs::(20, vec![CORRECT_MOCK_TXS[2].clone().into()], true); + } + + #[test] + fn rlp_circuit_multi_txs() { + verify_txs::( + 10, + vec![ + CORRECT_MOCK_TXS[0].clone().into(), + CORRECT_MOCK_TXS[1].clone().into(), + CORRECT_MOCK_TXS[2].clone().into(), + ], + true, + ); + } +} diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs index b54c7147c..12d6def1f 100644 --- a/zkevm-circuits/src/super_circuit.rs +++ b/zkevm-circuits/src/super_circuit.rs @@ -65,11 +65,12 @@ use crate::keccak_circuit::keccak_packed_multi::{ //use crate::pi_circuit::{PiCircuit, PiCircuitConfig, PiCircuitConfigArgs}; use crate::state_circuit::{StateCircuit, StateCircuitConfig, StateCircuitConfigArgs}; use crate::table::{ - BlockTable, BytecodeTable, CopyTable, ExpTable, KeccakTable, MptTable, RwTable, TxTable, + BlockTable, BytecodeTable, CopyTable, ExpTable, KeccakTable, MptTable, RlpTable, RwTable, + TxTable, }; use crate::util::{Challenges, SubCircuit, SubCircuitConfig}; -use crate::witness::{block_convert, Block, MptUpdates}; +use crate::witness::{block_convert, Block, MptUpdates, SignedTransaction}; use bus_mapping::circuit_input_builder::{CircuitInputBuilder, CircuitsParams}; use bus_mapping::mock::BlockData; use eth_types::geth_types::GethData; @@ -80,6 +81,7 @@ use halo2_proofs::{ plonk::{Circuit, ConstraintSystem, Error, Expression}, }; +use crate::rlp_circuit::{RlpCircuit, RlpCircuitConfig}; use std::array; use strum::IntoEnumIterator; @@ -98,6 +100,7 @@ pub struct SuperCircuitConfig< > { block_table: BlockTable, mpt_table: MptTable, + rlp_table: RlpTable, tx_table: TxTable, evm_circuit: EvmCircuitConfig, state_circuit: StateCircuitConfig, @@ -107,6 +110,7 @@ pub struct SuperCircuitConfig< keccak_circuit: KeccakCircuitConfig, //pi_circuit: PiCircuitConfig, exp_circuit: ExpCircuitConfig, + rlp_circuit: RlpCircuitConfig, } /// The Super Circuit contains all the zkEVM circuits @@ -133,6 +137,8 @@ pub struct SuperCircuit< pub exp_circuit: ExpCircuit, /// Keccak Circuit pub keccak_circuit: KeccakCircuit, + /// Rlp Circuit + pub rlp_circuit: RlpCircuit, } impl @@ -193,6 +199,8 @@ impl let copy_circuit = CopyCircuit::new_from_block(&block); let exp_circuit = ExpCircuit::new_from_block(&block); let keccak_circuit = KeccakCircuit::new_from_block(&block); + let rlp_circuit = RlpCircuit::new_from_block(&block); let circuit = SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, MAX_RWS> { evm_circuit, @@ -449,6 +465,7 @@ impl copy_circuit, exp_circuit, keccak_circuit, + rlp_circuit, }; let instance = circuit.instance(); diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index a6ba65f46..4cda0f8c6 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -7,7 +7,8 @@ use crate::impl_expr; use crate::util::build_tx_log_address; use crate::util::Challenges; use crate::witness::{ - Block, BlockContexts, Bytecode, MptUpdateRow, MptUpdates, Rw, RwMap, RwRow, Transaction, + Block, BlockContexts, Bytecode, MptUpdateRow, MptUpdates, RlpWitnessGen, Rw, RwMap, RwRow, + SignedTransaction, Transaction, }; use bus_mapping::circuit_input_builder::{CopyDataType, CopyEvent, CopyStep, ExpEvent}; use core::iter::once; @@ -58,7 +59,7 @@ impl> + Clone, const W: usize> LookupTable for /// Tag used to identify each field in the transaction in a row of the /// transaction table. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter)] pub enum TxFieldTag { /// Unused tag Null = 0, @@ -80,9 +81,17 @@ pub enum TxFieldTag { CallDataLength, /// Gas cost for transaction call data (4 for byte == 0, 16 otherwise) CallDataGasCost, + /// Signature field V. + SigV, + /// Signature field R. + SigR, + /// Signature field S. + SigS, /// TxSignHash: Hash of the transaction without the signature, used for /// signing. TxSignHash, + /// TxHash: Hash of the transaction with the signature + TxHash, /// CallData CallData, /// The block number in which this tx is included. @@ -90,6 +99,12 @@ pub enum TxFieldTag { } impl_expr!(TxFieldTag); +impl From for usize { + fn from(t: TxFieldTag) -> Self { + t as usize + } +} + /// Alias for TxFieldTag used by EVM Circuit pub type TxContextFieldTag = TxFieldTag; @@ -113,7 +128,7 @@ impl TxTable { tx_id: meta.advice_column(), tag: meta.advice_column(), index: meta.advice_column(), - value: meta.advice_column(), + value: meta.advice_column_in(SecondPhase), } } @@ -1327,3 +1342,115 @@ impl LookupTable for ExpTable { ] } } + +/// Lookup table embedded in the RLP circuit. +#[derive(Clone, Copy, Debug)] +pub struct RlpTable { + /// Transaction ID of the transaction. This is not the transaction hash, but + /// an incremental ID starting from 1 to indicate the position of the + /// transaction within the L2 block. + pub tx_id: Column, + /// Denotes the field/tag that this row represents. Example: nonce, gas, + /// gas_price, and so on. + pub tag: Column, + /// Denotes the decrementing index specific to this tag. The final value of + /// the field is accumulated in `value_acc` at `tag_index == 1`. + pub tag_rindex: Column, + /// Denotes the accumulator value for this field, which is a linear + /// combination or random linear combination of the field's bytes. + pub value_acc: Column, + /// Denotes the type of input assigned in this row. Type can either be + /// `TxSign` (transaction data that needs to be signed) or `TxHash` + /// (signed transaction's data). + pub data_type: Column, +} + +impl DynamicTableColumns for RlpTable { + fn columns(&self) -> Vec> { + vec![ + self.tx_id, + self.tag, + self.tag_rindex, + self.value_acc, + self.data_type, + ] + } +} + +impl RlpTable { + /// Construct the RLP table. + pub fn construct(meta: &mut ConstraintSystem) -> Self { + Self { + tx_id: meta.advice_column(), + tag: meta.advice_column(), + tag_rindex: meta.advice_column(), + value_acc: meta.advice_column_in(SecondPhase), + data_type: meta.advice_column(), + } + } + + /// Get assignments to the RLP table. Meant to be used for dev purposes. + pub fn dev_assignments( + txs: Vec, + challenges: &Challenges>, + ) -> Vec<[Value; 5]> { + let mut assignments = vec![]; + for signed_tx in txs { + for row in signed_tx + .gen_witness(challenges) + .iter() + .chain(signed_tx.rlp_rows(challenges.keccak_input()).iter()) + .chain(signed_tx.tx.gen_witness(challenges).iter()) + .chain(signed_tx.tx.rlp_rows(challenges.keccak_input()).iter()) + { + assignments.push([ + Value::known(F::from(row.tx_id as u64)), + Value::known(F::from(row.tag as u64)), + Value::known(F::from(row.tag_rindex as u64)), + row.value_acc, + Value::known(F::from(row.data_type as u64)), + ]); + } + } + assignments + } +} + +impl RlpTable { + /// Load witness into RLP table. Meant to be used for dev purposes. + pub fn dev_load( + &self, + layouter: &mut impl Layouter, + txs: Vec, + challenges: &Challenges>, + ) -> Result<(), Error> { + layouter.assign_region( + || "rlp table", + |mut region| { + let mut offset = 0; + for column in self.columns() { + region.assign_advice( + || format!("empty row: {}", offset), + column, + offset, + || Value::known(F::zero()), + )?; + } + + for row in Self::dev_assignments(txs.clone(), challenges) { + offset += 1; + for (column, value) in self.columns().iter().zip(row) { + region.assign_advice( + || format!("row: {}", offset), + *column, + offset, + || value, + )?; + } + } + + Ok(()) + }, + ) + } +} diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index 2f1d74418..13c62d025 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -6,9 +6,11 @@ pub mod sign_verify; +use crate::table::RlpTable; use crate::table::{KeccakTable, TxFieldTag, TxTable}; use crate::util::{random_linear_combine_word as rlc, Challenges, SubCircuit, SubCircuitConfig}; use crate::witness; +use crate::witness::signed_tx_from_geth_tx; use bus_mapping::circuit_input_builder::keccak_inputs_tx_circuit; use eth_types::{ sign_types::SignData, @@ -40,6 +42,7 @@ pub struct TxCircuitConfig { index: Column, value: Column, sign_verify: SignVerifyConfig, + rlp_table: RlpTable, _marker: PhantomData, // External tables keccak_table: KeccakTable, @@ -51,6 +54,8 @@ pub struct TxCircuitConfigArgs { pub tx_table: TxTable, /// KeccakTable pub keccak_table: KeccakTable, + /// RlpTable + pub rlp_table: RlpTable, /// Challenges pub challenges: Challenges>, } @@ -64,6 +69,7 @@ impl SubCircuitConfig for TxCircuitConfig { Self::ConfigArgs { tx_table, keccak_table, + rlp_table, challenges, }: Self::ConfigArgs, ) -> Self { @@ -82,6 +88,7 @@ impl SubCircuitConfig for TxCircuitConfig { value, sign_verify, keccak_table, + rlp_table, _marker: PhantomData, } } @@ -362,6 +369,7 @@ impl Circuit for TxCircuit { fn configure(meta: &mut ConstraintSystem) -> Self::Config { let tx_table = TxTable::construct(meta); let keccak_table = KeccakTable::construct(meta); + let rlp_table = RlpTable::construct(meta); let challenges = Challenges::construct(meta); let config = { @@ -371,6 +379,7 @@ impl Circuit for TxCircuit { TxCircuitConfigArgs { tx_table, keccak_table, + rlp_table, challenges, }, ) @@ -394,6 +403,11 @@ impl Circuit for TxCircuit { })?, &challenges, )?; + config.rlp_table.dev_load( + &mut layouter, + signed_tx_from_geth_tx(self.txs.as_slice(), self.chain_id), + &challenges, + )?; self.synthesize_sub(&config, &challenges, &mut layouter) } } diff --git a/zkevm-circuits/src/witness.rs b/zkevm-circuits/src/witness.rs index fe14ca1d6..3f49a40bd 100644 --- a/zkevm-circuits/src/witness.rs +++ b/zkevm-circuits/src/witness.rs @@ -10,9 +10,13 @@ mod call; pub use call::Call; mod mpt; pub use mpt::{MptUpdate, MptUpdateRow, MptUpdates}; +mod receipt; +pub use receipt::Receipt; +mod rlp_encode; +pub use rlp_encode::{RlpDataType, RlpTxTag, RlpWitnessGen, RlpWitnessRow, N_TX_TAGS}; mod rw; pub use rw::{Rw, RwMap, RwRow}; mod step; pub use step::ExecStep; mod tx; -pub use tx::Transaction; +pub use tx::{signed_tx_from_geth_tx, SignedTransaction, Transaction}; diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index c7611b0e0..be9cdb6ac 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -1,3 +1,4 @@ +use ethers_core::types::Signature; use std::collections::BTreeMap; use std::collections::HashMap; @@ -25,6 +26,8 @@ pub struct Block { pub randomness: F, /// Transactions in the block pub txs: Vec, + /// Signatures in the block + pub sigs: Vec, /// EndBlock step that is repeated after the last transaction and before /// reaching the last EVM row. pub end_block_not_last: ExecStep, @@ -226,6 +229,14 @@ pub fn block_convert( .next() .map(|(k, _)| *k) .unwrap_or_default(); + let chain_id = block + .headers + .values() + .into_iter() + .next() + .map(|header| header.chain_id.as_u64()) + .unwrap_or(1); + Ok(Block { randomness: Fr::from_u128(DEFAULT_RAND), context: block.into(), @@ -240,9 +251,10 @@ pub fn block_convert( } else { None }; - tx_convert(tx, idx + 1, next_tx) + tx_convert(tx, idx + 1, chain_id, next_tx) }) .collect(), + sigs: block.txs().iter().map(|tx| tx.signature).collect(), end_block_not_last: step_convert(&block.block_steps.end_block_not_last, last_block_num), end_block_last: step_convert(&block.block_steps.end_block_last, last_block_num), bytecodes: block diff --git a/zkevm-circuits/src/witness/receipt.rs b/zkevm-circuits/src/witness/receipt.rs new file mode 100644 index 000000000..324a49c6a --- /dev/null +++ b/zkevm-circuits/src/witness/receipt.rs @@ -0,0 +1,33 @@ +use ethers_core::types::{Bloom, Log}; +use rlp::Encodable; + +/// EVM log's receipt. +#[derive(Clone, Debug, Default)] +pub struct Receipt { + /// Denotes the ID of the tx. + pub id: usize, + /// Denotes whether or not the tx was executed successfully. + pub status: u8, + /// Denotes the cumulative gas used by the tx execution. + pub cumulative_gas_used: u64, + /// Represents the 256-bytes bloom filter. + pub bloom: Bloom, + /// List of logs generated by the tx. + pub logs: Vec, +} + +impl Encodable for Receipt { + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(4); + s.append(&self.status); + s.append(&self.cumulative_gas_used); + s.append(&self.bloom); + s.begin_list(self.logs.len()); + for log in self.logs.iter() { + s.begin_list(3); + s.append(&log.address); + s.append_list(&log.topics); + s.append(&log.data.0); + } + } +} diff --git a/zkevm-circuits/src/witness/rlp_encode.rs b/zkevm-circuits/src/witness/rlp_encode.rs new file mode 100644 index 000000000..698661280 --- /dev/null +++ b/zkevm-circuits/src/witness/rlp_encode.rs @@ -0,0 +1,7 @@ +mod common; + +mod tx; +pub use tx::{RlpTxTag, N_TX_TAGS}; + +mod witness_gen; +pub use witness_gen::{RlpDataType, RlpWitnessGen, RlpWitnessRow}; diff --git a/zkevm-circuits/src/witness/rlp_encode/common.rs b/zkevm-circuits/src/witness/rlp_encode/common.rs new file mode 100644 index 000000000..5a41bf4ea --- /dev/null +++ b/zkevm-circuits/src/witness/rlp_encode/common.rs @@ -0,0 +1,519 @@ +use crate::witness::RlpTxTag; +use eth_types::{Address, U256, U64}; +use halo2_proofs::arithmetic::FieldExt; +use halo2_proofs::circuit::Value; +use num::Zero; + +use super::witness_gen::{RlpDataType, RlpWitnessRow}; + +pub fn handle_prefix( + id: usize, + rlp_data: &[u8], + rows: &mut Vec>>, + data_type: RlpDataType, + tag: RlpTxTag, + mut idx: usize, +) -> usize { + if rlp_data[idx] > 0xf7 { + // length of length + let length_of_length = (rlp_data[idx] - 0xf7) as usize; + let tag_length = length_of_length + 1; + rows.push(RlpWitnessRow { + tx_id: id, + index: idx + 1, + data_type, + value: rlp_data[idx], + value_acc: Value::known(F::zero()), + value_rlc_acc: Value::known(F::zero()), + tag, + tag_length, + tag_rindex: tag_length, + length_acc: 0, + }); + idx += 1; + let mut length_acc = 0; + for (k, rlp_byte) in rlp_data[idx..].iter().take(length_of_length).enumerate() { + length_acc = (length_acc * 256) + (*rlp_byte as u64); + rows.push(RlpWitnessRow { + tx_id: id, + index: idx + 1, + data_type, + value: *rlp_byte, + value_acc: Value::known(F::from(length_acc as u64)), + value_rlc_acc: Value::known(F::zero()), + tag, + tag_length, + tag_rindex: tag_length - (1 + k), + length_acc, + }); + idx += 1; + } + } else { + // length + assert!( + rlp_data[idx] >= 0xc0 && rlp_data[idx] < 0xf8, + "RLP data mismatch({:?}): 0xc0 <= value < 0xf8, got: {:?} at idx: {:?}", + tag, + rlp_data[idx], + idx, + ); + rows.push(RlpWitnessRow { + tx_id: id, + index: idx + 1, + data_type, + value: rlp_data[idx], + value_acc: Value::known(F::from(rlp_data[idx] as u64)), + value_rlc_acc: Value::known(F::zero()), + tag, + tag_length: 1, + tag_rindex: 1, + length_acc: (rlp_data[idx] - 192) as u64, + }); + idx += 1; + } + idx +} + +pub fn handle_u8( + tx_id: usize, + rlp_data: &[u8], + rows: &mut Vec>>, + data_type: RlpDataType, + tag: RlpTxTag, + value: u8, + mut idx: usize, +) -> usize { + if value == 0 { + assert_eq!(rlp_data[idx], 0x80); + rows.push(RlpWitnessRow { + tx_id, + index: idx + 1, + data_type, + value: rlp_data[idx], + value_acc: Value::known(F::zero()), + value_rlc_acc: Value::known(F::zero()), + tag, + tag_length: 1, + tag_rindex: 1, + length_acc: 0, + }); + idx += 1; + } else { + // TODO: handle this case + panic!("should never happen"); + } + + idx +} + +#[allow(clippy::too_many_arguments)] +pub fn handle_u64( + tx_id: usize, + rlp_data: &[u8], + rows: &mut Vec>>, + data_type: RlpDataType, + tag: RlpTxTag, + value: U64, + mut idx: usize, +) -> usize { + let mut value_bytes = vec![0u8; 8]; + value.to_big_endian(&mut value_bytes); + let value_bytes = value_bytes + .iter() + .skip_while(|b| b.is_zero()) + .cloned() + .collect::>(); + + if value_bytes.len() == 1 && value_bytes[0] < 0x80 { + assert_eq!( + rlp_data[idx], value_bytes[0], + "RLP data mismatch({:?}): value < 0x80", + tag + ); + rows.push(RlpWitnessRow { + tx_id, + index: idx + 1, + data_type, + value: value_bytes[0], + value_acc: Value::known(F::from(value_bytes[0] as u64)), + value_rlc_acc: Value::known(F::zero()), + tag, + tag_length: 1, + tag_rindex: 1, + length_acc: 0, + }); + idx += 1; + } else { + assert_eq!( + rlp_data[idx] as usize, + 0x80 + value_bytes.len(), + "RLP data mismatch({:?}): len(value)", + tag + ); + let tag_length = 1 + value_bytes.len(); + rows.push(RlpWitnessRow { + tx_id, + index: idx + 1, + data_type, + value: rlp_data[idx], + value_acc: Value::known(F::zero()), + value_rlc_acc: Value::known(F::zero()), + tag, + tag_length, + tag_rindex: tag_length, + length_acc: tag_length as u64 - 1, + }); + idx += 1; + + let mut value_acc = F::zero(); + for (i, value_byte) in value_bytes.iter().enumerate() { + assert_eq!( + rlp_data[idx], *value_byte, + "RLP data mismatch({:?}): value[{}]", + tag, i + ); + value_acc = value_acc * F::from(256) + F::from(*value_byte as u64); + rows.push(RlpWitnessRow { + tx_id, + index: idx + 1, + data_type, + value: *value_byte, + value_acc: Value::known(value_acc), + value_rlc_acc: Value::known(F::zero()), + tag, + tag_length, + tag_rindex: tag_length - (1 + i), + length_acc: 0, + }); + idx += 1; + } + } + + idx +} + +#[allow(clippy::too_many_arguments)] +pub fn handle_u256( + randomness: Value, + id: usize, + rlp_data: &[u8], + rows: &mut Vec>>, + data_type: RlpDataType, + tag: RlpTxTag, + value: U256, + mut idx: usize, +) -> usize { + let mut value_bytes = vec![0u8; 32]; + value.to_big_endian(&mut value_bytes); + let value_bytes = value_bytes + .iter() + .skip_while(|b| b.is_zero()) + .cloned() + .collect::>(); + + if value_bytes.len() == 1 && value_bytes[0] < 0x80 { + assert_eq!( + rlp_data[idx], value_bytes[0], + "RLP data mismatch({:?}): value < 0x80", + tag + ); + rows.push(RlpWitnessRow { + tx_id: id, + index: idx + 1, + data_type, + value: value_bytes[0], + value_acc: Value::known(F::from(value_bytes[0] as u64)), + value_rlc_acc: Value::known(F::zero()), + tag, + tag_length: 1, + tag_rindex: 1, + length_acc: 0, + }); + idx += 1; + } else { + assert_eq!( + rlp_data[idx] as usize, + 0x80 + value_bytes.len(), + "RLP data mismatch({:?}): len(value)", + tag + ); + let tag_length = 1 + value_bytes.len(); + rows.push(RlpWitnessRow { + tx_id: id, + index: idx + 1, + data_type, + value: rlp_data[idx], + value_acc: Value::known(F::zero()), + value_rlc_acc: Value::known(F::zero()), + tag, + tag_length, + tag_rindex: tag_length, + length_acc: value_bytes.len() as u64, + }); + idx += 1; + + let mut value_acc = Value::known(F::zero()); + for (i, value_byte) in value_bytes.iter().enumerate() { + assert_eq!( + rlp_data[idx], *value_byte, + "RLP data mismatch({:?}): value[{}]", + tag, i + ); + value_acc = value_acc + .zip(randomness) + .map(|(value_acc, rand)| value_acc * rand + F::from(*value_byte as u64)); + rows.push(RlpWitnessRow { + tx_id: id, + index: idx + 1, + data_type, + value: *value_byte, + value_acc, + value_rlc_acc: Value::known(F::zero()), + tag, + tag_length, + tag_rindex: tag_length - (1 + i), + length_acc: 0, + }); + idx += 1; + } + } + + idx +} + +#[allow(clippy::too_many_arguments)] +pub fn handle_address( + tx_id: usize, + rlp_data: &[u8], + rows: &mut Vec>>, + data_type: RlpDataType, + tag: RlpTxTag, + value: Address, + mut idx: usize, +) -> usize { + let value_bytes = value.as_fixed_bytes(); + + if value == Address::zero() { + assert_eq!( + rlp_data[idx], 0x80, + "RLP data mismatch({:?}): value = {}", + tag, rlp_data[idx] + ); + rows.push(RlpWitnessRow { + tx_id, + index: idx + 1, + data_type, + value: rlp_data[idx], + value_acc: Value::known(F::zero()), + value_rlc_acc: Value::known(F::zero()), + tag, + tag_length: 1, + tag_rindex: 1, + length_acc: 0, + }); + } else { + assert_eq!( + rlp_data[idx], 0x94, + "RLP data mismatch({:?}): value = {}", + tag, rlp_data[idx] + ); + rows.push(RlpWitnessRow { + tx_id, + index: idx + 1, + data_type, + value: rlp_data[idx], + value_acc: Value::known(F::zero()), + value_rlc_acc: Value::known(F::zero()), + tag, + tag_length: 21, + tag_rindex: 21, + length_acc: 20, + }); + idx += 1; + let mut value_acc = F::zero(); + assert_eq!(value_bytes.len(), 20); + for (i, value_byte) in value_bytes.iter().enumerate() { + assert_eq!( + rlp_data[idx], *value_byte, + "RLP data mismatch({:?}): value[{}]", + tag, i + ); + value_acc = value_acc * F::from(256) + F::from(*value_byte as u64); + rows.push(RlpWitnessRow { + tx_id, + index: idx + 1, + data_type, + value: *value_byte, + value_acc: Value::known(value_acc), + value_rlc_acc: Value::known(F::zero()), + tag, + tag_length: 21, + tag_rindex: 21 - (i + 1), + length_acc: 0, + }); + idx += 1; + } + } + + idx +} + +#[allow(clippy::too_many_arguments)] +pub fn handle_bytes( + randomness: Value, + tx_id: usize, + rlp_data: &[u8], + rows: &mut Vec>>, + data_type: RlpDataType, + prefix_tag: RlpTxTag, + tag: RlpTxTag, + call_data: &[u8], + mut idx: usize, +) -> usize { + let length = call_data.len(); + + if length == 1 && call_data[0] < 0x80 { + assert_eq!(rlp_data[idx], call_data[0]); + rows.push(RlpWitnessRow { + tx_id, + index: idx + 1, + data_type, + value: call_data[0], + value_acc: Value::known(F::from(call_data[0] as u64)), + value_rlc_acc: Value::known(F::from(call_data[0] as u64)), + tag, + tag_length: 1, + tag_rindex: 1, + length_acc: 0, + }); + idx += 1; + } else if length < 56 { + assert_eq!( + rlp_data[idx] as usize, + 0x80 + length, + "RLP data mismatch({:?}): len(call_data) + 128", + prefix_tag + ); + rows.push(RlpWitnessRow { + tx_id, + index: idx + 1, + data_type, + value: (0x80 + length) as u8, + value_acc: Value::known(F::from((128 + length) as u64)), + value_rlc_acc: Value::known(F::zero()), + tag: prefix_tag, + tag_length: 1, + tag_rindex: 1, + length_acc: length as u64, + }); + idx += 1; + + let mut value_acc_rlc = Value::known(F::zero()); + for (i, data_byte) in call_data.iter().enumerate() { + assert_eq!( + rlp_data[idx], *data_byte, + "RLP data mismatch({:?}): value[{}]", + tag, i + ); + value_acc_rlc = value_acc_rlc + .zip(randomness) + .map(|(acc, rand)| acc * rand + F::from(*data_byte as u64)); + rows.push(RlpWitnessRow { + tx_id, + index: idx + 1, + data_type, + value: *data_byte, + value_acc: Value::known(F::from(*data_byte as u64)), + value_rlc_acc: value_acc_rlc, + tag, + tag_length: length, + tag_rindex: length - i, + length_acc: 0, + }); + idx += 1; + } + } else { + // length > 55. + let length_of_length = 8 - length.leading_zeros() as usize / 8; + assert_eq!( + rlp_data[idx] as usize, + 0xb7 + length_of_length, + "RLP data mismatch({:?}): len_of_len(call_data) + 0xb7", + prefix_tag + ); + let tag_length = 1 + length_of_length; + rows.push(RlpWitnessRow { + tx_id, + index: idx + 1, + data_type, + value: rlp_data[idx], + value_acc: Value::known(F::zero()), + value_rlc_acc: Value::known(F::zero()), + tag: prefix_tag, + tag_length, + tag_rindex: tag_length, + length_acc: 0, + }); + idx += 1; + + let length_bytes = length.to_be_bytes(); + let length_bytes = length_bytes + .iter() + .skip_while(|b| b.is_zero()) + .cloned() + .collect::>(); + assert_eq!(length_bytes.len(), length_of_length); + + let mut length_acc = 0; + for (i, length_byte) in length_bytes.iter().enumerate() { + assert_eq!( + rlp_data[idx], *length_byte, + "RLP data mismatch({:?}): length[{}]", + prefix_tag, i + ); + length_acc = length_acc * 256 + (*length_byte as u64); + rows.push(RlpWitnessRow { + tx_id, + index: idx + 1, + data_type, + value: *length_byte, + value_acc: Value::known(F::from(length_acc as u64)), + value_rlc_acc: Value::known(F::zero()), + tag: prefix_tag, + tag_length, + tag_rindex: tag_length - (1 + i), + length_acc, + }); + idx += 1; + } + assert_eq!(length_acc as usize, length); + + let tag_length = call_data.len(); + let mut value_rlc_acc = Value::known(F::zero()); + for (i, data_byte) in call_data.iter().enumerate() { + assert_eq!( + rlp_data[idx], *data_byte, + "RLP data mismatch({:?}): data[{}]", + tag, i + ); + value_rlc_acc = value_rlc_acc + .zip(randomness) + .map(|(acc, rand)| acc * rand + F::from(*data_byte as u64)); + rows.push(RlpWitnessRow { + tx_id, + index: idx + 1, + data_type, + value: *data_byte, + value_acc: Value::known(F::from(*data_byte as u64)), + value_rlc_acc, + tag, + tag_length, + tag_rindex: tag_length - i, + length_acc: 0, + }); + idx += 1; + } + } + + idx +} diff --git a/zkevm-circuits/src/witness/rlp_encode/receipt.rs b/zkevm-circuits/src/witness/rlp_encode/receipt.rs new file mode 100644 index 000000000..e4b3786c8 --- /dev/null +++ b/zkevm-circuits/src/witness/rlp_encode/receipt.rs @@ -0,0 +1,204 @@ +use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; + +use crate::{evm_circuit::witness::Receipt, impl_expr}; + +use super::{ + common::{handle_address, handle_bytes, handle_prefix, handle_u256}, + witness_gen::{RlpDataType, RlpWitnessGen, RlpWitnessRow}, +}; + +/// Tags used to tags rows in the RLP circuit for a tx receipt. +#[derive(Clone, Copy, Debug)] +pub enum RlpReceiptTag { + /// Denotes the prefix bytes indicating the "length of length" and/or + /// "length" of the tx receipt's RLP-encoding. + Prefix = 1, + /// Denotes the byte for the receipt's status. + Status, + /// Denotes the bytes representing the cumulative gas used. + CumulativeGasUsed, + /// Denotes the bytes prefixing the bloom filter bytes. + BloomPrefix, + /// Denotes the 256-bytes representing bloom filter. + Bloom, + /// Denotes the bytes prefixing the list of logs. + LogsPrefix, + /// Denotes the bytes prefixing a single log. + LogPrefix, + /// Denotes the byte prefixing the log.address. + LogAddressPrefix, + /// Denotes the 20-bytes representing the log.address. + LogAddress, + /// Denotes the bytes prefixing log.topics. + LogTopicsPrefix, + /// Denotes the bytes prefixing a single log.topic. + LogTopicPrefix, + /// Denotes the bytes representing a single log.topic. + LogTopic, + /// Denotes the bytes prefixing log.data. + LogDataPrefix, + /// Denotes the bytes representing log.data. + LogData, +} + +impl_expr!(RlpReceiptTag); + +/// Denotes the number of possible tag values for a tx receipt row. +pub const N_RECEIPT_TAGS: usize = 14; + +impl RlpWitnessGen for Receipt { + fn gen_witness(&self, randomness: F) -> Vec> { + let rlp_data = rlp::encode(self); + + let mut rows = Vec::with_capacity(rlp_data.len()); + + let idx = handle_prefix( + self.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::Receipt, + RlpReceiptTag::Prefix as u8, + 0, + ); + let idx = handle_u256( + randomness, + self.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::Receipt, + RlpReceiptTag::Status as u8, + self.status.into(), + idx, + ); + let idx = handle_u256( + randomness, + self.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::Receipt, + RlpReceiptTag::CumulativeGasUsed as u8, + self.cumulative_gas_used.into(), + idx, + ); + let idx = handle_bytes( + randomness, + self.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::Receipt, + RlpReceiptTag::BloomPrefix as u8, + RlpReceiptTag::Bloom as u8, + self.bloom.as_bytes(), + idx, + ); + let idx = self.handle_logs(randomness, rlp_data.as_ref(), &mut rows, idx); + + assert!( + idx == rlp_data.len(), + "RLP data mismatch: idx != len(rlp_data)" + ); + rows + } +} + +impl Receipt { + fn handle_logs( + &self, + randomness: F, + rlp_data: &[u8], + rows: &mut Vec>, + mut idx: usize, + ) -> usize { + idx = handle_prefix( + self.id, + rlp_data, + rows, + RlpDataType::Receipt, + RlpReceiptTag::LogsPrefix as u8, + idx, + ); + for log in self.logs.iter() { + idx = handle_prefix( + self.id, + rlp_data, + rows, + RlpDataType::Receipt, + RlpReceiptTag::LogPrefix as u8, + idx, + ); + + // start of aux tag index and length assignment (0). + let aux_tag_start0 = idx; + idx = handle_address( + self.id, + rlp_data, + rows, + RlpDataType::Receipt, + RlpReceiptTag::LogAddressPrefix as u8, + RlpReceiptTag::LogAddress as u8, + log.address, + idx, + ); + idx = handle_prefix( + self.id, + rlp_data, + rows, + RlpDataType::Receipt, + RlpReceiptTag::LogTopicsPrefix as u8, + idx, + ); + + // start of aux tag index and length assignment (1). + let aux_tag_start1 = idx; + for topic in log.topics.iter() { + idx = handle_bytes( + randomness, + self.id, + rlp_data, + rows, + RlpDataType::Receipt, + RlpReceiptTag::LogTopicPrefix as u8, + RlpReceiptTag::LogTopic as u8, + topic.as_bytes(), + idx, + ); + } + // assign aux tag index and length (1). + let aux_tag_length1 = idx - aux_tag_start1; + for (aux_idx, row) in rows + .iter_mut() + .skip(aux_tag_start1) + .take(aux_tag_length1) + .enumerate() + { + row.aux_tag_length[1] = aux_tag_length1; + row.aux_tag_index[1] = aux_tag_length1 - aux_idx; + } + + idx = handle_bytes( + randomness, + self.id, + rlp_data, + rows, + RlpDataType::Receipt, + RlpReceiptTag::LogDataPrefix as u8, + RlpReceiptTag::LogData as u8, + log.data.as_ref(), + idx, + ); + + // assign aux tag index and length (0). + let aux_tag_length0 = idx - aux_tag_start0; + for (aux_idx, row) in rows + .iter_mut() + .skip(aux_tag_start0) + .take(aux_tag_length0) + .enumerate() + { + row.aux_tag_length[0] = aux_tag_length0; + row.aux_tag_index[0] = aux_tag_length0 - aux_idx; + } + } + idx + } +} diff --git a/zkevm-circuits/src/witness/rlp_encode/tx.rs b/zkevm-circuits/src/witness/rlp_encode/tx.rs new file mode 100644 index 000000000..a8a8f204d --- /dev/null +++ b/zkevm-circuits/src/witness/rlp_encode/tx.rs @@ -0,0 +1,566 @@ +use halo2_proofs::circuit::Value; +use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; +use strum_macros::EnumIter; + +use crate::util::Challenges; +use crate::witness::rlp_encode::common::handle_u8; +use crate::{evm_circuit::witness::Transaction, impl_expr, witness::tx::SignedTransaction}; + +use super::{ + common::{handle_address, handle_bytes, handle_prefix, handle_u256, handle_u64}, + witness_gen::{RlpDataType, RlpWitnessGen, RlpWitnessRow}, +}; + +/// Tags used to tag rows in the RLP circuit for a transaction. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, EnumIter)] +pub enum RlpTxTag { + /// This tag is reserved for padding purpose. + #[default] + Padding = 0, + /// Denotes the prefix bytes indicating the “length of length” and/or + /// “length” of the tx’s RLP-encoding. + Prefix, + /// Denotes the byte(s) for the tx’s nonce. + Nonce, + /// Denotes the byte(s) for the tx’s gas price. + GasPrice, + /// Denotes the byte(s) for the tx’s gas. + Gas, + /// Denotes the bytes for the tx’s to. + To, + /// Denotes the byte(s) for the tx’s value. + Value, + /// Denotes the prefix byte(s) indicating the “length of length” and/or + /// “length” of the tx’s data. + DataPrefix, + /// Denotes the bytes for the tx’s data. + Data, + /// Denotes the chain ID, as per EIP-155. + ChainId, + /// Denotes a placeholder zero for unsigned transactions, as per EIP-155. + Zero, + /// Used to indicate the {0, 1}-parity of the ECDSA signature. + SigV, + /// ECDSA signature's X-coordinate. + SigR, + /// ECDSA signature's Y-coordinate. + SigS, + /// The RlpLength tag is reserved to hold RLP-encoding's length. + RlpLength, + /// The RLP tag is reserved to hold the RLP-encoding's random linear + /// combination in its accumulator value. Its used to support a lookup + /// for rlc(rlp(tx)). + Rlp, +} + +impl_expr!(RlpTxTag); + +impl From for usize { + fn from(t: RlpTxTag) -> Self { + t as usize + } +} + +/// Denotes the number of tag values in a transaction's RLP trace. +pub const N_TX_TAGS: usize = 16; + +impl RlpWitnessGen for Transaction { + fn gen_witness(&self, challenges: &Challenges>) -> Vec>> { + let rlp_data = rlp::encode(self); + let mut rows = Vec::with_capacity(rlp_data.len()); + + let idx = handle_prefix( + self.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxSign, + RlpTxTag::Prefix, + 0, + ); + let idx = handle_u64( + self.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxSign, + RlpTxTag::Nonce, + self.nonce.into(), + idx, + ); + let idx = handle_u256( + challenges.evm_word(), + self.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxSign, + RlpTxTag::GasPrice, + self.gas_price, + idx, + ); + let idx = handle_u64( + self.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxSign, + RlpTxTag::Gas, + self.gas.into(), + idx, + ); + let idx = handle_address( + self.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxSign, + RlpTxTag::To, + self.callee_address, + idx, + ); + let idx = handle_u256( + challenges.evm_word(), + self.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxSign, + RlpTxTag::Value, + self.value, + idx, + ); + let idx = handle_bytes( + challenges.keccak_input(), + self.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxSign, + RlpTxTag::DataPrefix, + RlpTxTag::Data, + &self.call_data, + idx, + ); + let idx = handle_u64( + self.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxSign, + RlpTxTag::ChainId, + self.chain_id.into(), + idx, + ); + let idx = handle_u8( + self.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxSign, + RlpTxTag::Zero, + 0, + idx, + ); + let idx = handle_u8( + self.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxSign, + RlpTxTag::Zero, + 0, + idx, + ); + + assert_eq!( + idx, + rlp_data.len(), + "RLP data mismatch: idx != len(rlp_data)" + ); + rows + } + + fn rlp_rows(&self, randomness: Value) -> [RlpWitnessRow>; 2] { + let rlp_out = rlp::encode(self); + let rlc_rlp_out = randomness.map(|rand| { + rlp_out + .as_ref() + .iter() + .fold(F::zero(), |acc, value| acc * rand + F::from(*value as u64)) + }); + + [ + RlpWitnessRow { + tx_id: self.id, + index: rlp_out.len() + 1, + data_type: RlpDataType::TxSign, + value: 0, + value_acc: Value::known(F::from(rlp_out.len() as u64)), + value_rlc_acc: Value::known(F::zero()), + tag: RlpTxTag::RlpLength, + tag_length: 1, + tag_rindex: 1, + length_acc: 0, + }, + RlpWitnessRow { + tx_id: self.id, + index: rlp_out.len() + 2, + data_type: RlpDataType::TxSign, + value: 0, + value_acc: rlc_rlp_out, + value_rlc_acc: Value::known(F::zero()), + tag: RlpTxTag::Rlp, + tag_length: 1, + tag_rindex: 1, + length_acc: 0, + }, + ] + } +} + +impl RlpWitnessGen for SignedTransaction { + fn gen_witness(&self, challenges: &Challenges>) -> Vec>> { + let rlp_data = rlp::encode(self); + let mut rows = Vec::with_capacity(rlp_data.len()); + + let idx = handle_prefix( + self.tx.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxHash, + RlpTxTag::Prefix, + 0, + ); + let idx = handle_u64( + self.tx.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxHash, + RlpTxTag::Nonce, + self.tx.nonce.into(), + idx, + ); + let idx = handle_u256( + challenges.evm_word(), + self.tx.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxHash, + RlpTxTag::GasPrice, + self.tx.gas_price, + idx, + ); + let idx = handle_u64( + self.tx.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxHash, + RlpTxTag::Gas, + self.tx.gas.into(), + idx, + ); + let idx = handle_address( + self.tx.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxHash, + RlpTxTag::To, + self.tx.callee_address, + idx, + ); + let idx = handle_u256( + challenges.evm_word(), + self.tx.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxHash, + RlpTxTag::Value, + self.tx.value, + idx, + ); + let idx = handle_bytes( + challenges.keccak_input(), + self.tx.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxHash, + RlpTxTag::DataPrefix, + RlpTxTag::Data, + &self.tx.call_data, + idx, + ); + let idx = handle_u64( + self.tx.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxHash, + RlpTxTag::SigV, + self.signature.v.into(), + idx, + ); + let idx = handle_u256( + challenges.evm_word(), + self.tx.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxHash, + RlpTxTag::SigR, + self.signature.r, + idx, + ); + let idx = handle_u256( + challenges.evm_word(), + self.tx.id, + rlp_data.as_ref(), + &mut rows, + RlpDataType::TxHash, + RlpTxTag::SigS, + self.signature.s, + idx, + ); + + assert_eq!( + idx, + rlp_data.len(), + "RLP data mismatch: idx != len(rlp_data)" + ); + rows + } + + fn rlp_rows(&self, randomness: Value) -> [RlpWitnessRow>; 2] { + let rlp_out = rlp::encode(self); + let rlc_rlp_out = randomness.map(|rand| { + rlp_out + .as_ref() + .iter() + .fold(F::zero(), |acc, value| acc * rand + F::from(*value as u64)) + }); + + [ + RlpWitnessRow { + tx_id: self.tx.id, + index: rlp_out.len() + 1, + data_type: RlpDataType::TxHash, + value: 0, + value_acc: Value::known(F::from(rlp_out.len() as u64)), + value_rlc_acc: Value::known(F::zero()), + tag: RlpTxTag::RlpLength, + tag_length: 1, + tag_rindex: 1, + length_acc: 0, + }, + RlpWitnessRow { + tx_id: self.tx.id, + index: rlp_out.len() + 2, + data_type: RlpDataType::TxHash, + value: 0, + value_acc: rlc_rlp_out, + value_rlc_acc: Value::known(F::zero()), + tag: RlpTxTag::Rlp, + tag_length: 1, + tag_rindex: 1, + length_acc: 0, + }, + ] + } +} + +#[cfg(test)] +mod tests { + use halo2_proofs::circuit::Value; + use halo2_proofs::{arithmetic::Field, halo2curves::bn256::Fr}; + use num::Zero; + + use crate::evm_circuit::{ + test::rand_bytes, + witness::{RlpTxTag, RlpWitnessGen, Transaction}, + }; + use crate::util::Challenges; + + #[test] + fn tx_rlp_witgen_a() { + let r = Fr::random(rand::thread_rng()); + let challenges = + Challenges::>::mock(Value::known(r), Value::known(r + Fr::one())); + + let callee_address = mock::MOCK_ACCOUNTS[0]; + let call_data = rand_bytes(55); + let tx = Transaction { + nonce: 1, + gas_price: 2u64.into(), + gas: 3, + callee_address, + value: 4u64.into(), + call_data: call_data.clone(), + chain_id: 4, + ..Default::default() + }; + + let tx_rlp = rlp::encode(&tx); + let witness_rows = tx.gen_witness(&challenges); + + assert_eq!(tx_rlp.len(), witness_rows.len()); + + // prefix verification + assert_eq!(witness_rows[0].tag, RlpTxTag::Prefix); + assert_eq!(witness_rows[0].tag_rindex, 2); + assert_eq!(witness_rows[0].tag_length, 2); + assert_eq!(witness_rows[0].length_acc, 0); + assert_eq!(witness_rows[0].value, 248); + assert_eq!(witness_rows[1].tag, RlpTxTag::Prefix); + assert_eq!(witness_rows[1].tag_rindex, 1); + assert_eq!(witness_rows[1].tag_length, 2); + assert_eq!(witness_rows[1].length_acc, 84); + assert_eq!(witness_rows[1].value, 84); + + // nonce verification + assert_eq!(witness_rows[2].tag, RlpTxTag::Nonce); + assert_eq!(witness_rows[2].tag_rindex, 1); + assert_eq!(witness_rows[2].tag_length, 1); + assert_eq!(witness_rows[2].value, 1); + + // gas price verification + assert_eq!(witness_rows[3].tag, RlpTxTag::GasPrice); + assert_eq!(witness_rows[3].tag_rindex, 1); + assert_eq!(witness_rows[3].tag_length, 1); + assert_eq!(witness_rows[3].value, 2); + + // gas verification + assert_eq!(witness_rows[4].tag, RlpTxTag::Gas); + assert_eq!(witness_rows[4].tag_rindex, 1); + assert_eq!(witness_rows[4].tag_length, 1); + assert_eq!(witness_rows[4].value, 3); + + // to prefix verification + assert_eq!(witness_rows[5].tag, RlpTxTag::To); + assert_eq!(witness_rows[5].tag_rindex, 21); + assert_eq!(witness_rows[5].tag_length, 21); + assert_eq!(witness_rows[5].value, 148); + + // to verification + for (i, row) in witness_rows.iter().skip(6).take(20).enumerate() { + assert_eq!(row.tag, RlpTxTag::To); + assert_eq!(row.tag_rindex, 21 - (i + 1)); + assert_eq!(row.tag_length, 21); + assert_eq!(row.value, mock::MOCK_ACCOUNTS[0][i]); + } + + // value verification + assert_eq!(witness_rows[26].tag, RlpTxTag::Value); + assert_eq!(witness_rows[26].tag_rindex, 1); + assert_eq!(witness_rows[26].tag_length, 1); + assert_eq!(witness_rows[26].value, 4); + + // data prefix verification + assert_eq!(witness_rows[27].tag, RlpTxTag::DataPrefix); + assert_eq!(witness_rows[27].tag_rindex, 1); + assert_eq!(witness_rows[27].tag_length, 1); + assert_eq!(witness_rows[27].value, 128 + 55); + assert_eq!(witness_rows[27].length_acc, 55); + + // data verification + for (i, row) in witness_rows.iter().skip(28).take(55).enumerate() { + assert_eq!(row.tag, RlpTxTag::Data); + assert_eq!(row.tag_rindex, 55 - i); + assert_eq!(row.tag_length, 55); + assert_eq!(row.value, call_data[i]); + } + } + + #[test] + fn tx_rlp_witgen_b() { + let r = Fr::random(rand::thread_rng()); + let challenges = + Challenges::>::mock(Value::known(r), Value::known(r + Fr::one())); + + let nonce = 0x123456u64; + let gas_price = 0x234567u64.into(); + let gas = 0x345678u64; + let callee_address = mock::MOCK_ACCOUNTS[1]; + let value = 0x456789u64.into(); + let call_data = rand_bytes(2048); + let tx = Transaction { + nonce, + gas_price, + gas, + callee_address, + value, + call_data: call_data.clone(), + chain_id: 1, + ..Default::default() + }; + + let tx_rlp = rlp::encode(&tx); + let witness_rows = tx.gen_witness(&challenges); + + assert_eq!(tx_rlp.len(), witness_rows.len()); + + // prefix verification + assert_eq!(witness_rows[0].tag, RlpTxTag::Prefix); + assert_eq!(witness_rows[0].tag_rindex, 3); + assert_eq!(witness_rows[0].tag_length, 3); + assert_eq!(witness_rows[0].length_acc, 0); + assert_eq!(witness_rows[0].value, 249); + assert_eq!(witness_rows[1].tag, RlpTxTag::Prefix); + assert_eq!(witness_rows[1].tag_rindex, 2); + assert_eq!(witness_rows[1].tag_length, 3); + assert_eq!(witness_rows[1].length_acc, 8); + assert_eq!(witness_rows[1].value, 8); + assert_eq!(witness_rows[2].tag, RlpTxTag::Prefix); + assert_eq!(witness_rows[2].tag_rindex, 1); + assert_eq!(witness_rows[2].tag_length, 3); + assert_eq!(witness_rows[2].length_acc, 2091); + assert_eq!(witness_rows[2].value, 43); + + // nonce verification + let nonce_bytes = nonce + .to_be_bytes() + .iter() + .skip_while(|b| b.is_zero()) + .cloned() + .collect::>(); + assert_eq!(nonce_bytes.len(), 3); + assert_eq!(witness_rows[3].tag, RlpTxTag::Nonce); + assert_eq!(witness_rows[3].tag_length, 4); + assert_eq!(witness_rows[3].tag_rindex, 4); + assert_eq!(witness_rows[3].value, 128 + 3); + assert_eq!(witness_rows[3].length_acc, 3); + for (i, row) in witness_rows.iter().skip(4).take(3).enumerate() { + assert_eq!(row.tag, RlpTxTag::Nonce); + assert_eq!(row.tag_length, 4); + assert_eq!(row.tag_rindex, 3 - i); + assert_eq!(row.value, nonce_bytes[i]); + assert_eq!(row.length_acc, 0); + } + + const START_DATA_PREFIX: usize = 3 + // prefix + 4 + // nonce + 4 + // gas price + 4 + // gas + 21 + // callee address + 4; // value + assert_eq!(witness_rows[START_DATA_PREFIX - 1].tag, RlpTxTag::Value); + + // data prefix verification + // 2048 -> 0x0800 -> len_of_len == 2 + assert_eq!(witness_rows[START_DATA_PREFIX].tag, RlpTxTag::DataPrefix); + assert_eq!(witness_rows[START_DATA_PREFIX].tag_rindex, 3); + assert_eq!(witness_rows[START_DATA_PREFIX].tag_length, 3); + assert_eq!(witness_rows[START_DATA_PREFIX].value, 183 + 2); + assert_eq!(witness_rows[START_DATA_PREFIX].length_acc, 0); + assert_eq!( + witness_rows[START_DATA_PREFIX + 1].tag, + RlpTxTag::DataPrefix + ); + assert_eq!(witness_rows[START_DATA_PREFIX + 1].tag_rindex, 2); + assert_eq!(witness_rows[START_DATA_PREFIX + 1].tag_length, 3); + assert_eq!(witness_rows[START_DATA_PREFIX + 1].value, 8); + assert_eq!(witness_rows[START_DATA_PREFIX + 1].length_acc, 8); + assert_eq!( + witness_rows[START_DATA_PREFIX + 2].tag, + RlpTxTag::DataPrefix + ); + assert_eq!(witness_rows[START_DATA_PREFIX + 2].tag_rindex, 1); + assert_eq!(witness_rows[START_DATA_PREFIX + 2].tag_length, 3); + assert_eq!(witness_rows[START_DATA_PREFIX + 2].value, 0); + assert_eq!(witness_rows[START_DATA_PREFIX + 2].length_acc, 2048); + + // data verification + assert_eq!(witness_rows[START_DATA_PREFIX + 3].tag, RlpTxTag::Data); + assert_eq!(witness_rows[START_DATA_PREFIX + 3].tag_rindex, 2048); + assert_eq!(witness_rows[START_DATA_PREFIX + 3].tag_length, 2048); + assert_eq!(witness_rows[START_DATA_PREFIX + 3].value, call_data[0]); + assert_eq!(witness_rows[START_DATA_PREFIX + 3].length_acc, 0); + } +} diff --git a/zkevm-circuits/src/witness/rlp_encode/witness_gen.rs b/zkevm-circuits/src/witness/rlp_encode/witness_gen.rs new file mode 100644 index 000000000..7774f8c95 --- /dev/null +++ b/zkevm-circuits/src/witness/rlp_encode/witness_gen.rs @@ -0,0 +1,67 @@ +use halo2_proofs::circuit::Value; +use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; +use rlp::Encodable; + +use crate::impl_expr; +use crate::util::Challenges; +use crate::witness::RlpTxTag; + +/// Data types that are supported by the RLP circuit. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum RlpDataType { + /// Data type for an RLP-encoded unsigned transaction. The encoding consists + /// of: [nonce, gas_price, gas, to, value, input, chain_id, 0, 0]. + #[default] + TxSign = 0, + /// Data type for an RLP-encoded signed transaction. The encoding consists + /// of: [nonce, gas_price, gas, to, value, input, v, r, s]. + /// where the fields `v`, `r` and `s` represent the compressed form of an + /// ECDSA signature. + TxHash, +} + +impl_expr!(RlpDataType); + +/// Represents the witness in a single row of the RLP circuit. +#[derive(Clone, Debug, Default)] +pub struct RlpWitnessRow { + /// Denotes the transaction ID. + pub tx_id: usize, + /// Denotes the index of this row, starting from 1 and ending at n where n + /// is the byte length of the RLP-encoded data. + pub index: usize, + /// Denotes the data type, whether this circuit encodes a + /// RlpDataType::TxSign or a RlpDataType::TxHash. + pub data_type: RlpDataType, + /// Denotes the byte value in the RLP-encoded data. + pub value: u8, + /// Accumulator value to represent the full value of the tag. + pub value_acc: F, + /// RLC accumulator value used for call data bytes. + pub value_rlc_acc: F, + /// Denotes the row’s tag, which can be a field from the data type encoded. + pub tag: RlpTxTag, + /// Denotes the current tag's length in bytes. + pub tag_length: usize, + /// Denotes a decrementing index specific to the tag in in the current block + /// of bytes. + pub tag_rindex: usize, + /// Denotes an accumulator for the length of data, in the case where len > + /// 55 and the length is represented in its big-endian form. + pub length_acc: u64, + // Optional field to hold the calldata length. + // pub call_data_length: Option, + // Optional field to hold the calldata gas cost. + // pub call_data_gas_cost: Option, +} + +/// The RlpWitnessGen trait is implemented by data types who's RLP encoding can +/// be verified by the RLP-encoding circuit. +pub trait RlpWitnessGen: Encodable + Sized { + /// Generate witness to the RLP-encoding verifier circuit, as a vector of + /// RlpWitnessRow. + fn gen_witness(&self, challenges: &Challenges>) -> Vec>>; + + /// Generate the RLP summary row. + fn rlp_rows(&self, randomness: Value) -> [RlpWitnessRow>; 2]; +} diff --git a/zkevm-circuits/src/witness/tx.rs b/zkevm-circuits/src/witness/tx.rs index 1127b4b65..a14e15440 100644 --- a/zkevm-circuits/src/witness/tx.rs +++ b/zkevm-circuits/src/witness/tx.rs @@ -1,12 +1,13 @@ +use crate::evm_circuit::step::ExecutionState; use crate::util::Challenges; use crate::{evm_circuit::util::RandomLinearCombination, table::TxContextFieldTag}; use bus_mapping::circuit_input_builder; -use eth_types::H256; -use eth_types::{Address, Field, ToLittleEndian, ToScalar, ToWord, Word}; +use eth_types::{Address, Field, Signature, ToLittleEndian, ToScalar, ToWord, Word, H256}; use halo2_proofs::circuit::Value; +use mock::MockTransaction; +use rlp::Encodable; use super::{step::step_convert, Call, ExecStep}; -use crate::evm_circuit::step::ExecutionState; /// Transaction in a witness block #[derive(Debug, Default, Clone, PartialEq, Eq)] @@ -37,6 +38,8 @@ pub struct Transaction { pub call_data_length: usize, /// The gas cost for transaction call data pub call_data_gas_cost: u64, + /// Chain ID as per EIP-155. + pub chain_id: u64, /// The calls made in the transaction pub calls: Vec, /// The steps executioned in the transaction @@ -139,9 +142,79 @@ impl Transaction { } } +impl Encodable for Transaction { + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(9); + s.append(&Word::from(self.nonce)); + s.append(&self.gas_price); + s.append(&Word::from(self.gas)); + s.append(&self.callee_address); + s.append(&self.value); + s.append(&self.call_data); + s.append(&Word::from(self.chain_id)); + s.append(&Word::zero()); + s.append(&Word::zero()); + } +} + +/// Signed transaction in a witness block +#[derive(Debug, Clone)] +pub struct SignedTransaction { + /// Transaction data. + pub tx: Transaction, + /// ECDSA signature on the transaction. + pub signature: Signature, +} + +impl Encodable for SignedTransaction { + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(9); + s.append(&Word::from(self.tx.nonce)); + s.append(&self.tx.gas_price); + s.append(&Word::from(self.tx.gas)); + s.append(&self.tx.callee_address); + s.append(&self.tx.value); + s.append(&self.tx.call_data); + s.append(&self.signature.v); + s.append(&self.signature.r); + s.append(&self.signature.s); + } +} + +impl From for SignedTransaction { + fn from(mock_tx: MockTransaction) -> Self { + let is_create = mock_tx.to.is_none(); + Self { + tx: Transaction { + id: mock_tx.transaction_index.as_usize(), + nonce: mock_tx.nonce.as_u64(), + gas: mock_tx.gas.as_u64(), + gas_price: mock_tx.gas_price, + caller_address: mock_tx.from.address(), + callee_address: match mock_tx.to { + Some(to) => to.address(), + None => Address::zero(), + }, + is_create, + value: mock_tx.value, + call_data: mock_tx.input.to_vec(), + call_data_length: mock_tx.input.len(), + // chain_id: mock_tx.chain_id.as_u64(), + ..Default::default() + }, + signature: Signature { + r: mock_tx.r.expect("tx expected to be signed"), + s: mock_tx.s.expect("tx expected to be signed"), + v: mock_tx.v.expect("tx expected to be signed").as_u64(), + }, + } + } +} + pub(super) fn tx_convert( tx: &circuit_input_builder::Transaction, id: usize, + chain_id: u64, next_tx: Option<&circuit_input_builder::Transaction>, ) -> Transaction { Transaction { @@ -161,6 +234,7 @@ pub(super) fn tx_convert( .input .iter() .fold(0, |acc, byte| acc + if *byte == 0 { 4 } else { 16 }), + chain_id, calls: tx .calls() .iter() @@ -222,3 +296,40 @@ pub(super) fn tx_convert( .collect(), } } + +/// Convert eth_types::geth_types::Transaction to SignedTransaction that can be +/// used as witness in TxCircuit and RLP Circuit. +pub fn signed_tx_from_geth_tx( + txs: &[eth_types::geth_types::Transaction], + chain_id: u64, +) -> Vec { + let mut signed_txs = Vec::with_capacity(txs.len()); + for (i, geth_tx) in txs.iter().enumerate() { + signed_txs.push(SignedTransaction { + tx: Transaction { + id: i + 1, + nonce: geth_tx.nonce.as_u64(), + gas: geth_tx.gas_limit.as_u64(), + gas_price: geth_tx.gas_price, + caller_address: geth_tx.from, + callee_address: geth_tx.to.unwrap_or(Address::zero()), + is_create: geth_tx.to.is_none(), + value: geth_tx.value, + call_data: geth_tx.call_data.to_vec(), + call_data_length: geth_tx.call_data.len(), + call_data_gas_cost: geth_tx + .call_data + .iter() + .fold(0, |acc, byte| acc + if *byte == 0 { 4 } else { 16 }), + chain_id, + ..Default::default() + }, + signature: Signature { + r: geth_tx.r, + s: geth_tx.s, + v: geth_tx.v, + }, + }); + } + signed_txs +}