diff --git a/Cargo.lock b/Cargo.lock index f6978b884..2741a792b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -389,6 +389,7 @@ dependencies = [ "eth-types", "ethers-core", "ethers-providers", + "ethers-signers", "gadgets", "halo2_proofs", "hex", diff --git a/bus-mapping/Cargo.toml b/bus-mapping/Cargo.toml index a35d1dab2..e4ae80bbe 100644 --- a/bus-mapping/Cargo.toml +++ b/bus-mapping/Cargo.toml @@ -12,6 +12,7 @@ keccak256 = { path = "../keccak256" } mock = { path = "../mock", optional = true } ethers-core = "0.17.0" +ethers-signers = "0.17.0" ethers-providers = "0.17.0" halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2022_09_10" } itertools = "0.10" diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index 3a2c06967..62903fc9d 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -22,19 +22,24 @@ pub use block::{Block, BlockContext}; pub use call::{Call, CallContext, CallKind}; use core::fmt::Debug; use eth_types::evm_types::{GasCost, OpcodeId}; -use eth_types::geth_types; use eth_types::sign_types::{pk_bytes_le, pk_bytes_swap_endianness, SignData}; -use eth_types::{self, Address, GethExecStep, GethExecTrace, ToWord, Word, H256}; +use eth_types::{self, Address, GethExecStep, GethExecTrace, ToWord, Word, H256, U256}; +use eth_types::{geth_types, ToBigEndian}; +use ethers_core::k256::ecdsa::SigningKey; +use ethers_core::types::transaction::eip2718::TypedTransaction; +use ethers_core::types::{Bytes, Signature, TransactionRequest}; use ethers_providers::JsonRpcClient; pub use execution::{ CopyDataType, CopyEvent, CopyStep, ExecState, ExecStep, ExpEvent, ExpStep, NumberOrHash, }; use hex::decode_to_slice; +use ethers_core::utils::keccak256; pub use input_state_ref::CircuitInputStateRef; use itertools::Itertools; use log::warn; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; +use std::iter; pub use transaction::{Transaction, TransactionContext}; /// Circuit Setup Parameters @@ -49,6 +54,8 @@ pub struct CircuitsParams { pub max_txs: usize, /// Maximum number of bytes from all txs calldata in the Tx Circuit pub max_calldata: usize, + /// Maximum number of inner blocks in a batch + pub max_inner_blocks: usize, /// Maximum number of bytes supported in the Bytecode Circuit pub max_bytecode: usize, // TODO: Rename for consistency @@ -64,6 +71,7 @@ impl Default for CircuitsParams { max_rws: 1000, max_txs: 1, max_calldata: 256, + max_inner_blocks: 64, max_bytecode: 512, keccak_padding: None, } @@ -379,12 +387,12 @@ impl<'a> CircuitInputBuilder { ) } else if matches!(geth_step.op, OpcodeId::MLOAD) { format!( - "{:?}", + "{:?}", geth_step.stack.nth_last(0), ) } else if matches!(geth_step.op, OpcodeId::MSTORE | OpcodeId::MSTORE8) { format!( - "{:?} {:?}", + "{:?} {:?}", geth_step.stack.nth_last(0), geth_step.stack.nth_last(1), ) @@ -425,6 +433,13 @@ pub fn keccak_inputs(block: &Block, code_db: &CodeDB) -> Result>, Er "keccak total len after txs: {}", keccak_inputs.iter().map(|i| i.len()).sum::() ); + // PI circuit + keccak_inputs.push(keccak_inputs_pi_circuit( + block.chain_id().as_u64(), + &block.headers, + block.txs(), + block.circuits_params.max_txs, + )); // Bytecode Circuit for _bytecode in code_db.0.values() { //keccak_inputs.push(bytecode.clone()); @@ -452,6 +467,7 @@ pub fn keccak_inputs_sign_verify(sigs: &[SignData]) -> Vec> { let pk_le = pk_bytes_le(&sig.pk); let pk_be = pk_bytes_swap_endianness(&pk_le); inputs.push(pk_be.to_vec()); + inputs.push(sig.msg.to_vec()); } // Padding signature let pk_le = pk_bytes_le(&SignData::default().pk); @@ -460,12 +476,122 @@ pub fn keccak_inputs_sign_verify(sigs: &[SignData]) -> Vec> { inputs } +/// Generate a dummy tx in which +/// (nonce=0, gas=0, gas_price=0, to=0, value=0, data="", chain_id) +/// using the dummy private key = 1 +pub fn get_dummy_tx(chain_id: u64) -> (TypedTransaction, Signature) { + let mut sk_be_scalar = [0u8; 32]; + sk_be_scalar[31] = 1_u8; + + let sk = SigningKey::from_bytes(&sk_be_scalar).expect("sign key = 1"); + let wallet = ethers_signers::Wallet::from(sk); + + let tx_req = TransactionRequest::new() + .nonce(0) + .gas(0) + .gas_price(U256::zero()) + .to(Address::zero()) + .value(U256::zero()) + .data(Bytes::default()) + .chain_id(chain_id); + + let tx = tx_req.into(); + let sig = wallet.sign_transaction_sync(&tx); + + (tx, sig) +} + +/// Get the tx hash of the dummy tx (nonce=0, gas=0, gas_price=0, to=0, value=0, +/// data="") for any chain_id +pub fn get_dummy_tx_hash(chain_id: u64) -> H256 { + let (tx, sig) = get_dummy_tx(chain_id); + + let tx_hash = keccak256(tx.rlp_signed(&sig)); + log::debug!("tx hash: {}", hex::encode(tx_hash)); + + H256(tx_hash) +} + +fn keccak_inputs_pi_circuit( + chain_id: u64, + block_headers: &BTreeMap, + transactions: &[Transaction], + max_txs: usize, +) -> Vec { + // TODO: add history hashes and state roots + let dummy_tx_hash = get_dummy_tx_hash(chain_id); + + let result = block_headers + .iter() + .flat_map(|(block_num, block)| { + let num_txs = transactions + .iter() + .filter(|tx| tx.block_num == *block_num) + .count() as u64; + + iter::empty() + // Block Values + .chain(block.coinbase.to_fixed_bytes()) + .chain(block.timestamp.as_u64().to_be_bytes()) + .chain(block.number.as_u64().to_be_bytes()) + .chain(block.difficulty.to_be_bytes()) + .chain(block.gas_limit.to_be_bytes()) + .chain(block.base_fee.to_be_bytes()) + .chain(block.chain_id.to_be_bytes()) + .chain(num_txs.to_be_bytes()) + }) + // history_hashes + // .chain( + // block + // .history_hashes + // .iter() + // .flat_map(|tx_hash| tx_hash.to_fixed_bytes()), + // ) + // state roots + // .chain( + // extra.state_root.to_fixed_bytes() + // ) + // .chain( + // extra.prev_state_root.to_fixed_bytes() + // ) + // Tx Hashes + .chain(transactions.iter().flat_map(|tx| tx.hash.to_fixed_bytes())) + .chain( + (0..(max_txs - transactions.len())) + .into_iter() + .flat_map(|_| dummy_tx_hash.to_fixed_bytes()), + ) + .collect::>(); + + result +} + /// Generate the keccak inputs required by the Tx Circuit from the transactions. pub fn keccak_inputs_tx_circuit( txs: &[geth_types::Transaction], chain_id: u64, ) -> Result>, Error> { let mut inputs = Vec::new(); + + let hash_datas = txs + .iter() + .map(|tx| { + let sig = Signature { + r: tx.r, + s: tx.s, + v: tx.v, + }; + let tx: TransactionRequest = tx.into(); + tx.rlp_signed(&sig).to_vec() + }) + .collect::>>(); + let dummy_hash_data = { + let (dummy_tx, dummy_sig) = get_dummy_tx(chain_id); + dummy_tx.rlp_signed(&dummy_sig).to_vec() + }; + inputs.extend_from_slice(&hash_datas); + inputs.push(dummy_hash_data); + let sign_datas: Vec = txs .iter() .enumerate() diff --git a/circuit-benchmarks/src/pi_circuit.rs b/circuit-benchmarks/src/pi_circuit.rs index 5a1ce3a40..bc0475c10 100644 --- a/circuit-benchmarks/src/pi_circuit.rs +++ b/circuit-benchmarks/src/pi_circuit.rs @@ -2,8 +2,9 @@ #[cfg(test)] mod tests { use ark_std::{end_timer, start_timer}; - use eth_types::Word; - use halo2_proofs::arithmetic::Field; + use bus_mapping::mock::BlockData; + use eth_types::bytecode; + use eth_types::geth_types::GethData; use halo2_proofs::plonk::{create_proof, keygen_pk, keygen_vk, verify_proof}; use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG, ParamsVerifierKZG}; use halo2_proofs::poly::kzg::multiopen::{ProverSHPLONK, VerifierSHPLONK}; @@ -15,13 +16,13 @@ mod tests { Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, }, }; + use mock::TestContext; use rand::SeedableRng; - use rand_chacha::ChaCha20Rng; use rand_xorshift::XorShiftRng; use std::env::var; - use zkevm_circuits::pi_circuit::{PiCircuit, PiTestCircuit, PublicData}; - use zkevm_circuits::test_util::rand_tx; + use zkevm_circuits::pi_circuit::{PiCircuit, PiTestCircuit}; use zkevm_circuits::util::SubCircuit; + use zkevm_circuits::witness::{block_convert, Block}; #[cfg_attr(not(feature = "benches"), ignore)] #[test] @@ -33,18 +34,12 @@ mod tests { const MAX_TXS: usize = 10; const MAX_CALLDATA: usize = 128; + const MAX_INNER_BLOCKS: usize = 64; - let mut rng = ChaCha20Rng::seed_from_u64(2); - let randomness = Fr::random(&mut rng); - let rand_rpi = Fr::random(&mut rng); - let public_data = generate_publicdata::(); - let circuit = PiTestCircuit::(PiCircuit::::new( - MAX_TXS, - MAX_CALLDATA, - randomness, - rand_rpi, - public_data, - )); + let block = generate_block::(); + let circuit = PiTestCircuit::( + PiCircuit::::new(MAX_TXS, MAX_CALLDATA, MAX_INNER_BLOCKS, &block), + ); let public_inputs = circuit.0.instance(); let instance: Vec<&[Fr]> = public_inputs.iter().map(|input| &input[..]).collect(); let instances = &[&instance[..]][..]; @@ -76,7 +71,7 @@ mod tests { Challenge255, XorShiftRng, Blake2bWrite, G1Affine, Challenge255>, - PiTestCircuit, + PiTestCircuit, >( &general_params, &pk, @@ -111,17 +106,16 @@ mod tests { end_timer!(start3); } - fn generate_publicdata() -> PublicData { - let mut rng = ChaCha20Rng::seed_from_u64(2); - let mut public_data = PublicData::default(); - let chain_id = 1337u64; - public_data.chain_id = Word::from(chain_id); - - let n_tx = MAX_TXS; - for _ in 0..n_tx { - let eth_tx = eth_types::Transaction::from(&rand_tx(&mut rng, chain_id, true)); - public_data.transactions.push(eth_tx); - } - public_data + fn generate_block() -> Block { + let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode! { + STOP + }) + .unwrap(); + let block: GethData = test_ctx.into(); + let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + block_convert(&builder.block, &builder.code_db).unwrap() } } diff --git a/circuit-benchmarks/src/super_circuit.rs b/circuit-benchmarks/src/super_circuit.rs index aa68bc195..dda8d3016 100644 --- a/circuit-benchmarks/src/super_circuit.rs +++ b/circuit-benchmarks/src/super_circuit.rs @@ -71,7 +71,7 @@ mod tests { block.sign(&wallets); - let (_, circuit, instance, _) = SuperCircuit::<_, 1, 32, 512>::build(block).unwrap(); + let (_, circuit, instance, _) = SuperCircuit::<_, 1, 32, 64, 512>::build(block).unwrap(); let instance_refs: Vec<&[Fr]> = instance.iter().map(|v| &v[..]).collect(); // Bench setup generation @@ -96,7 +96,7 @@ mod tests { Challenge255, ChaChaRng, Blake2bWrite, G1Affine, Challenge255>, - SuperCircuit, + SuperCircuit, >( &general_params, &pk, diff --git a/eth-types/src/geth_types.rs b/eth-types/src/geth_types.rs index 0e1c6a08c..b7550b737 100644 --- a/eth-types/src/geth_types.rs +++ b/eth-types/src/geth_types.rs @@ -5,7 +5,7 @@ use crate::{ AccessList, Address, Block, Bytes, Error, GethExecTrace, Hash, ToBigEndian, ToLittleEndian, Word, U64, }; -use ethers_core::types::TransactionRequest; +use ethers_core::types::{TransactionRequest, H256}; use ethers_signers::{LocalWallet, Signer}; use halo2_proofs::halo2curves::{group::ff::PrimeField, secp256k1}; use num::Integer; @@ -129,6 +129,9 @@ pub struct Transaction { pub r: Word, /// "s" value of the transaction signature pub s: Word, + + /// Transaction hash + pub hash: H256, } impl From<&Transaction> for crate::Transaction { @@ -147,6 +150,7 @@ impl From<&Transaction> for crate::Transaction { v: tx.v.into(), r: tx.r, s: tx.s, + hash: tx.hash, ..Default::default() } } @@ -168,6 +172,7 @@ impl From<&crate::Transaction> for Transaction { v: tx.v.as_u64(), r: tx.r, s: tx.s, + hash: tx.hash, } } } @@ -198,7 +203,7 @@ impl Transaction { secp256k1::Fq::from_repr(sig_s_le), Error::Signature(libsecp256k1::Error::InvalidSignature), )?; - // msg = rlp([nonce, gasPrice, gas, to, value, data, sig_v, r, s]) + // msg = rlp([nonce, gasPrice, gas, to, value, data, chain_id, 0, 0]) let req: TransactionRequest = self.into(); let msg = req.chain_id(chain_id).rlp(); let msg_hash: [u8; 32] = Keccak256::digest(&msg) @@ -219,6 +224,7 @@ impl Transaction { Ok(SignData { signature: (sig_r, sig_s), pk, + msg, msg_hash, }) } diff --git a/eth-types/src/sign_types.rs b/eth-types/src/sign_types.rs index f6bee6980..9862d11f1 100644 --- a/eth-types/src/sign_types.rs +++ b/eth-types/src/sign_types.rs @@ -1,6 +1,7 @@ //! secp256k1 signature types and helper functions. use crate::{ToBigEndian, Word}; +use ethers_core::types::Bytes; use halo2_proofs::{ arithmetic::{CurveAffine, FieldExt}, halo2curves::{ @@ -14,6 +15,7 @@ use halo2_proofs::{ }; use lazy_static::lazy_static; use num_bigint::BigUint; +use sha3::{Digest, Keccak256}; use subtle::CtOption; /// Do a secp256k1 signature with a given randomness value. @@ -49,6 +51,8 @@ pub struct SignData { pub signature: (secp256k1::Fq, secp256k1::Fq), /// Secp256k1 public key pub pk: Secp256k1Affine, + /// Message being hashed before signing. + pub msg: Bytes, /// Hash of the message that is being signed pub msg_hash: secp256k1::Fq, } @@ -59,13 +63,20 @@ lazy_static! { let sk = secp256k1::Fq::one(); let pk = generator * sk; let pk = pk.to_affine(); - let msg_hash = secp256k1::Fq::one(); + let msg = b"1"; + let msg_hash: [u8; 32] = Keccak256::digest(&msg) + .as_slice() + .to_vec() + .try_into() + .expect("hash length isn't 32 bytes"); + let msg_hash = secp256k1::Fq::from_bytes(&msg_hash).unwrap(); let randomness = secp256k1::Fq::one(); let (sig_r, sig_s) = sign(randomness, sk, msg_hash); SignData { signature: (sig_r, sig_s), pk, + msg: msg.into(), msg_hash, } }; diff --git a/integration-tests/src/integration_test_circuits.rs b/integration-tests/src/integration_test_circuits.rs index 5d209050c..b95600200 100644 --- a/integration-tests/src/integration_test_circuits.rs +++ b/integration-tests/src/integration_test_circuits.rs @@ -37,6 +37,7 @@ const CIRCUITS_PARAMS: CircuitsParams = CircuitsParams { max_rws: 16384, max_txs: 4, max_calldata: 4000, + max_inner_blocks: 64, max_bytecode: 4000, keccak_padding: None, }; @@ -345,6 +346,7 @@ pub async fn test_super_circuit_block(block_num: u64) { const MAX_CALLDATA: usize = 512; const MAX_RWS: usize = 5888; const MAX_BYTECODE: usize = 5000; + const MAX_INNER_BLOCKS: usize = 64; log::info!("test super circuit, block number: {}", block_num); let cli = get_client(); @@ -353,6 +355,7 @@ pub async fn test_super_circuit_block(block_num: u64) { CircuitsParams { max_rws: MAX_RWS, max_txs: MAX_TXS, + max_inner_blocks: MAX_INNER_BLOCKS, max_calldata: MAX_CALLDATA, max_bytecode: MAX_BYTECODE, keccak_padding: None, @@ -362,7 +365,7 @@ pub async fn test_super_circuit_block(block_num: u64) { .unwrap(); let (builder, _) = cli.gen_inputs(block_num).await.unwrap(); let (k, circuit, instance) = - SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, MAX_RWS>::build_from_circuit_input_builder( + SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, MAX_INNER_BLOCKS, MAX_RWS>::build_from_circuit_input_builder( &builder, ) .unwrap(); diff --git a/integration-tests/tests/circuit_input_builder.rs b/integration-tests/tests/circuit_input_builder.rs index c05f903eb..0adfa0169 100644 --- a/integration-tests/tests/circuit_input_builder.rs +++ b/integration-tests/tests/circuit_input_builder.rs @@ -17,6 +17,7 @@ async fn test_circuit_input_builder_block(block_num: u64) { max_rws: 16384, max_txs: 1, max_calldata: 4000, + max_inner_blocks: 64, max_bytecode: 4000, keccak_padding: None, }, diff --git a/integration-tests/tests/circuits.rs b/integration-tests/tests/circuits.rs index 2e87446f5..d3ac55312 100644 --- a/integration-tests/tests/circuits.rs +++ b/integration-tests/tests/circuits.rs @@ -20,6 +20,7 @@ const CIRCUITS_PARAMS: CircuitsParams = CircuitsParams { max_rws: 30000, max_txs: 20, max_calldata: 30000, + max_inner_blocks: 64, max_bytecode: 30000, keccak_padding: None, }; @@ -37,6 +38,7 @@ async fn test_mock_prove_tx() { max_rws: 100000, max_txs: 10, max_calldata: 40000, + max_inner_blocks: 64, max_bytecode: 40000, keccak_padding: None, }; @@ -93,6 +95,7 @@ async fn test_super_circuit_all_block() { max_rws: 4_000_000, max_txs: 500, max_calldata: 2_000_000, + max_inner_blocks: 64, max_bytecode: 2_000_000, keccak_padding: None, }; @@ -105,7 +108,7 @@ async fn test_super_circuit_all_block() { } let (k, circuit, instance) = - SuperCircuit::::build_from_circuit_input_builder( + SuperCircuit::::build_from_circuit_input_builder( &builder, ) .unwrap(); @@ -136,6 +139,7 @@ async fn test_tx_circuit_all_block() { max_rws: 200_000, max_txs: 14, // so max_txs * num_rows_per_tx < 2**21 max_calldata: 200_000, + max_inner_blocks: 64, max_bytecode: 200_000, keccak_padding: None, }; @@ -173,6 +177,7 @@ async fn test_evm_circuit_all_block() { max_rws: 5_000_000, max_txs: 500, max_calldata: 400000, + max_inner_blocks: 64, max_bytecode: 400000, keccak_padding: None, }; diff --git a/testool/src/statetest/executor.rs b/testool/src/statetest/executor.rs index a59e1791f..7aff83eeb 100644 --- a/testool/src/statetest/executor.rs +++ b/testool/src/statetest/executor.rs @@ -4,7 +4,9 @@ use bus_mapping::circuit_input_builder::{CircuitInputBuilder, CircuitsParams}; use bus_mapping::mock::BlockData; use eth_types::{geth_types, Address, Bytes, GethExecTrace, U256, U64}; use ethers_core::k256::ecdsa::SigningKey; +use ethers_core::types::transaction::eip2718::TypedTransaction; use ethers_core::types::TransactionRequest; +use ethers_core::utils::keccak256; use ethers_signers::{LocalWallet, Signer}; use external_tracer::TraceConfig; use halo2_proofs::dev::MockProver; @@ -124,8 +126,10 @@ fn into_traceconfig(st: StateTest) -> (String, TraceConfig, StateTestResult) { if let Some(to) = st.to { tx = tx.to(to); } + let tx: TypedTransaction = tx.into(); - let sig = wallet.sign_transaction_sync(&tx.into()); + let sig = wallet.sign_transaction_sync(&tx); + let tx_hash = keccak256(tx.rlp_signed(&sig)); ( st.id, @@ -140,7 +144,6 @@ fn into_traceconfig(st: StateTest) -> (String, TraceConfig, StateTestResult) { gas_limit: U256::from(st.env.current_gas_limit), base_fee: U256::one(), }, - transactions: vec![geth_types::Transaction { from: st.from, to: st.to, @@ -155,6 +158,7 @@ fn into_traceconfig(st: StateTest) -> (String, TraceConfig, StateTestResult) { v: sig.v, r: sig.r, s: sig.s, + hash: tx_hash.into(), }], accounts: st.pre, ..Default::default() diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index 3929b893a..9b2b3ef6f 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -36,6 +36,7 @@ num-bigint = { version = "0.4" } rlp = "0.5.1" subtle = "2.4" rand_chacha = "0.3" +hex = "0.4.3" [dev-dependencies] bus-mapping = { path = "../bus-mapping", features = ["test"] } diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index ec5f86218..a19baa16d 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -350,7 +350,7 @@ pub mod test { .load(&mut layouter, block.bytecodes.values(), &challenges)?; config .block_table - .load(&mut layouter, &block.context, &block.txs, block.randomness)?; + .load(&mut layouter, &block.context, &block.txs, 1, &challenges)?; config.copy_table.load(&mut layouter, block, &challenges)?; config .keccak_table diff --git a/zkevm-circuits/src/pi_circuit.rs b/zkevm-circuits/src/pi_circuit.rs index a1a0c57c6..5da3d8af7 100644 --- a/zkevm-circuits/src/pi_circuit.rs +++ b/zkevm-circuits/src/pi_circuit.rs @@ -1,162 +1,127 @@ //! Public Input Circuit implementation +use std::iter; use std::marker::PhantomData; -use eth_types::geth_types::BlockConstants; -use eth_types::sign_types::SignData; -use eth_types::H256; -use eth_types::{ - geth_types::Transaction, Address, Field, ToBigEndian, ToLittleEndian, ToScalar, Word, -}; -use ethers_core::abi::ethereum_types::BigEndianHash; -use halo2_proofs::plonk::Instance; - -use crate::table::BlockTable; -use crate::table::TxFieldTag; use crate::table::TxTable; -use crate::util::{random_linear_combine_word as rlc, Challenges, SubCircuit, SubCircuitConfig}; -use crate::witness; -use gadgets::is_zero::IsZeroChip; -use gadgets::util::{not, or, Expr}; +use crate::table::{BlockTable, KeccakTable}; +use bus_mapping::circuit_input_builder::get_dummy_tx_hash; +use eth_types::H256; +use eth_types::{Field, ToBigEndian, Word}; +use ethers_core::utils::keccak256; +use halo2_proofs::plonk::{Expression, Fixed, Instance, SecondPhase}; + +use crate::evm_circuit::util::constraint_builder::BaseConstraintBuilder; +use crate::util::{Challenges, SubCircuit, SubCircuitConfig}; +use crate::witness::{Block, BlockContext, BlockContexts, Transaction}; +use gadgets::util::{not, select, Expr}; +use halo2_proofs::circuit::{Cell, RegionIndex}; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Region, SimpleFloorPlanner, Value}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Selector}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Selector}, poly::Rotation, }; /// Fixed by the spec -const TX_LEN: usize = 10; const BLOCK_LEN: usize = 7 + 256; const EXTRA_LEN: usize = 2; +const BYTE_POW_BASE: u64 = 1 << 8; +const BLOCK_HEADER_BYTES_NUM: usize = 148; +const KECCAK_DIGEST_SIZE: usize = 32; +const RPI_CELL_IDX: usize = 0; +const RPI_RLC_ACC_CELL_IDX: usize = 1; const ZERO_BYTE_GAS_COST: u64 = 4; const NONZERO_BYTE_GAS_COST: u64 = 16; -/// Values of the block table (as in the spec) -#[derive(Clone, Default, Debug)] -pub struct BlockValues { - coinbase: Address, - gas_limit: u64, - number: u64, - timestamp: u64, - difficulty: Word, - base_fee: Word, // NOTE: BaseFee was added by EIP-1559 and is ignored in legacy headers. - chain_id: u64, - history_hashes: Vec, -} - -/// Values of the tx table (as in the spec) -#[derive(Default, Debug, Clone)] -pub struct TxValues { - nonce: Word, - gas: Word, //gas limit - gas_price: Word, - from_addr: Address, - to_addr: Address, - is_create: u64, - value: Word, - call_data_len: u64, - call_data_gas_cost: u64, - tx_sign_hash: [u8; 32], -} - -/// Extra values (not contained in block or tx tables) -#[derive(Default, Debug, Clone)] -pub struct ExtraValues { - // block_hash: H256, - state_root: H256, - prev_state_root: H256, -} - /// PublicData contains all the values that the PiCircuit recieves as input #[derive(Debug, Clone, Default)] pub struct PublicData { /// chain id pub chain_id: Word, - /// History hashes contains the most recent 256 block hashes in history, - /// where the latest one is at history_hashes[history_hashes.len() - 1]. - pub history_hashes: Vec, /// Block Transactions - pub transactions: Vec, - /// Block State Root - pub state_root: H256, - /// Previous block root - pub prev_state_root: H256, - /// Constants related to Ethereum block - pub block_constants: BlockConstants, + pub transactions: Vec, + /// Block contexts + pub block_ctxs: BlockContexts, } impl PublicData { - /// Returns struct with values for the block table - pub fn get_block_table_values(&self) -> BlockValues { - let history_hashes = [ - vec![H256::zero(); 256 - self.history_hashes.len()], - self.history_hashes - .iter() - .map(|&hash| H256::from(hash.to_be_bytes())) - .collect(), - ] - .concat(); - BlockValues { - coinbase: self.block_constants.coinbase, - gas_limit: self.block_constants.gas_limit.as_u64(), - number: self.block_constants.number.as_u64(), - timestamp: self.block_constants.timestamp.as_u64(), - difficulty: self.block_constants.difficulty, - base_fee: self.block_constants.base_fee, - chain_id: self.chain_id.as_u64(), - history_hashes, - } - } + /// Compute the raw_public_inputs bytes from the verifier's perspective. + fn raw_public_input_bytes(&self, max_txs: usize) -> Vec { + // TODO: add history hashes and state roots + let dummy_tx_hash = get_dummy_tx_hash(self.chain_id.as_u64()); - /// Returns struct with values for the tx table - pub fn get_tx_table_values(&self) -> Vec { - let chain_id: u64 = self - .chain_id - .try_into() - .expect("Error converting chain_id to u64"); - let mut tx_vals = vec![]; - for tx in &self.txs() { - let sign_data: SignData = tx - .sign_data(chain_id) - .expect("Error computing tx_sign_hash"); - let mut msg_hash_le = [0u8; 32]; - msg_hash_le.copy_from_slice(sign_data.msg_hash.to_bytes().as_slice()); - tx_vals.push(TxValues { - nonce: tx.nonce, - gas_price: tx.gas_price, - gas: tx.gas_limit, - from_addr: tx.from, - to_addr: tx.to.unwrap_or_else(Address::zero), - is_create: (tx.to.is_none() as u64), - value: tx.value, - call_data_len: tx.call_data.0.len() as u64, - call_data_gas_cost: tx.call_data.0.iter().fold(0, |acc, byte| { - acc + if *byte == 0 { - ZERO_BYTE_GAS_COST - } else { - NONZERO_BYTE_GAS_COST - } - }), - tx_sign_hash: msg_hash_le, - }); - } - tx_vals - } + let result = self + .block_ctxs + .ctxs + .iter() + .flat_map(|(block_num, block)| { + let num_txs = self + .transactions + .iter() + .filter(|tx| tx.block_number == *block_num) + .count() as u64; + + iter::empty() + // Block Values + .chain(block.coinbase.to_fixed_bytes()) + .chain(block.timestamp.as_u64().to_be_bytes()) + .chain(block.number.as_u64().to_be_bytes()) + .chain(block.difficulty.to_be_bytes()) + .chain(block.gas_limit.to_be_bytes()) + .chain(block.base_fee.to_be_bytes()) + .chain(block.chain_id.to_be_bytes()) + .chain(num_txs.to_be_bytes()) + }) + // history_hashes + // .chain( + // block + // .history_hashes + // .iter() + // .flat_map(|tx_hash| tx_hash.to_fixed_bytes()), + // ) + // state roots + // .chain( + // extra.state_root.to_fixed_bytes() + // ) + // .chain( + // extra.prev_state_root.to_fixed_bytes() + // ) + // Tx Hashes + .chain( + self.transactions + .iter() + .flat_map(|tx| tx.hash.to_fixed_bytes()), + ) + .chain( + (0..(max_txs - self.transactions.len())) + .into_iter() + .flat_map(|_| dummy_tx_hash.to_fixed_bytes()), + ) + .collect::>(); - /// Returns struct with the extra values - pub fn get_extra_values(&self) -> ExtraValues { - ExtraValues { - // block_hash: self.hash.unwrap_or_else(H256::zero), - state_root: self.state_root, - prev_state_root: self.prev_state_root, - } + assert_eq!( + result.len(), + BLOCK_HEADER_BYTES_NUM * self.block_ctxs.ctxs.len() + KECCAK_DIGEST_SIZE * max_txs + ); + result } - fn txs(&self) -> Vec { - self.transactions.iter().map(Transaction::from).collect() + fn get_pi(&self, max_txs: usize) -> H256 { + let rpi_bytes = self.raw_public_input_bytes(max_txs); + let rpi_keccak = keccak256(&rpi_bytes); + H256(rpi_keccak) } } +// fn rlc_be_bytes(bytes: [u8; N], rand: Value) -> +// Value { bytes +// .into_iter() +// .fold(Value::known(F::zero()), |acc, byte| { +// acc.zip(rand) +// .and_then(|(acc, rand)| Value::known(acc * rand + +// F::from(byte as u64))) }) +// } + /// Config for PiCircuit #[derive(Clone, Debug)] pub struct PiCircuitConfig { @@ -164,47 +129,57 @@ pub struct PiCircuitConfig { max_txs: usize, /// Max number of supported calldata bytes max_calldata: usize, + /// Max number of supported inner blocks in a batch + max_inner_blocks: usize, + + raw_public_inputs: Column, // block, history_hashes, states, tx hashes + rpi_field_bytes: Column, // rpi in bytes + rpi_field_bytes_acc: Column, + rpi_rlc_acc: Column, // RLC(rpi) as the input to Keccak table + rpi_length_acc: Column, - q_block_table: Selector, - q_tx_table: Selector, - q_tx_calldata: Selector, - q_calldata_start: Selector, - - tx_id_inv: Column, - tx_value_inv: Column, - tx_id_diff_inv: Column, - fixed_u16: Column, - calldata_gas_cost: Column, - is_final: Column, - - raw_public_inputs: Column, - rpi_rlc_acc: Column, - rand_rpi: Column, + is_rpi_padding: Column, + real_rpi: Column, + + q_field_start: Selector, + q_field_step: Selector, + is_field_rlc: Column, + q_field_end: Selector, + + q_start: Selector, q_not_end: Selector, - q_end: Selector, + q_keccak: Selector, - pi: Column, // rpi_rand, rpi_rlc, chain_ID, state_root, prev_state_root + pi: Column, // hi(keccak(rpi)), lo(keccak(rpi)) - _marker: PhantomData, // External tables block_table: BlockTable, tx_table: TxTable, + keccak_table: KeccakTable, + + _marker: PhantomData, } /// Circuit configuration arguments -pub struct PiCircuitConfigArgs { +pub struct PiCircuitConfigArgs { /// Max number of supported transactions pub max_txs: usize, /// Max number of supported calldata bytes pub max_calldata: usize, + /// Max number of supported blocks in a batch + pub max_inner_blocks: usize, /// TxTable pub tx_table: TxTable, /// BlockTable pub block_table: BlockTable, + /// Keccak Table + pub keccak_table: KeccakTable, + /// Challenges + pub challenges: Challenges>, } impl SubCircuitConfig for PiCircuitConfig { - type ConfigArgs = PiCircuitConfigArgs; + type ConfigArgs = PiCircuitConfigArgs; /// Return a new PiCircuitConfig fn new( @@ -212,333 +187,197 @@ impl SubCircuitConfig for PiCircuitConfig { Self::ConfigArgs { max_txs, max_calldata, + max_inner_blocks, block_table, tx_table, + keccak_table, + challenges, }: Self::ConfigArgs, ) -> Self { - let q_block_table = meta.selector(); - - let q_tx_table = meta.complex_selector(); - let q_tx_calldata = meta.complex_selector(); - let q_calldata_start = meta.complex_selector(); - // Tx Table - let tx_id = tx_table.tx_id; - let tx_value = tx_table.value; - let tag = tx_table.tag; - let index = tx_table.index; - let tx_id_inv = meta.advice_column(); - let tx_value_inv = meta.advice_column(); - let tx_id_diff_inv = meta.advice_column(); - // The difference of tx_id of adjacent rows in calldata part of tx table - // lies in the interval [0, 2^16] if their tx_id both do not equal to zero. - // We do not use 2^8 for the reason that a large block may have more than - // 2^8 transfer transactions which have 21000*2^8 (~ 5.376M) gas. - let fixed_u16 = meta.fixed_column(); - let calldata_gas_cost = meta.advice_column(); - let is_final = meta.advice_column(); - - let raw_public_inputs = meta.advice_column(); - let rpi_rlc_acc = meta.advice_column(); - let rand_rpi = meta.advice_column(); - let q_not_end = meta.selector(); - let q_end = meta.selector(); + let rpi = meta.advice_column_in(SecondPhase); + let rpi_bytes = meta.advice_column(); + let rpi_bytes_acc = meta.advice_column_in(SecondPhase); + let rpi_rlc_acc = meta.advice_column_in(SecondPhase); + let rpi_length_acc = meta.advice_column(); + let is_rpi_padding = meta.advice_column(); + let real_rpi = meta.advice_column_in(SecondPhase); let pi = meta.instance_column(); - meta.enable_equality(raw_public_inputs); + let q_field_start = meta.complex_selector(); + let q_field_step = meta.complex_selector(); + let q_field_end = meta.complex_selector(); + let is_field_rlc = meta.fixed_column(); + + let q_start = meta.complex_selector(); + let q_not_end = meta.complex_selector(); + let q_keccak = meta.complex_selector(); + + meta.enable_equality(rpi); + meta.enable_equality(real_rpi); meta.enable_equality(rpi_rlc_acc); - meta.enable_equality(rand_rpi); + meta.enable_equality(block_table.value); // copy block to rpi + meta.enable_equality(tx_table.value); // copy tx hashes to rpi meta.enable_equality(pi); - // 0.0 rpi_rlc_acc[0] == RLC(raw_public_inputs, rand_rpi) + // field bytes meta.create_gate( - "rpi_rlc_acc[i] = rand_rpi * rpi_rlc_acc[i+1] + raw_public_inputs[i]", + "rpi_bytes_acc[i+1] = rpi_bytes_acc[i] * t + rpi_bytes[i+1]", |meta| { - // q_not_end * row.rpi_rlc_acc == - // (q_not_end * row_next.rpi_rlc_acc * row.rand_rpi + row.raw_public_inputs ) - let q_not_end = meta.query_selector(q_not_end); - let cur_rpi_rlc_acc = meta.query_advice(rpi_rlc_acc, Rotation::cur()); - let next_rpi_rlc_acc = meta.query_advice(rpi_rlc_acc, Rotation::next()); - let rand_rpi = meta.query_advice(rand_rpi, Rotation::cur()); - let raw_public_inputs = meta.query_advice(raw_public_inputs, Rotation::cur()); - - vec![ - q_not_end * (next_rpi_rlc_acc * rand_rpi + raw_public_inputs - cur_rpi_rlc_acc), - ] + let q_field_step = meta.query_selector(q_field_step); + let bytes_acc_next = meta.query_advice(rpi_bytes_acc, Rotation::next()); + let bytes_acc = meta.query_advice(rpi_bytes_acc, Rotation::cur()); + let bytes_next = meta.query_advice(rpi_bytes, Rotation::next()); + let is_field_rlc = meta.query_fixed(is_field_rlc, Rotation::next()); + let evm_rand = challenges.evm_word(); + let t = select::expr(is_field_rlc, evm_rand, BYTE_POW_BASE.expr()); + + vec![q_field_step * (bytes_acc_next - (bytes_acc * t + bytes_next))] }, ); - meta.create_gate("rpi_rlc_acc[last] = raw_public_inputs[last]", |meta| { - let q_end = meta.query_selector(q_end); - let raw_public_inputs = meta.query_advice(raw_public_inputs, Rotation::cur()); - let rpi_rlc_acc = meta.query_advice(rpi_rlc_acc, Rotation::cur()); - vec![q_end * (raw_public_inputs - rpi_rlc_acc)] - }); + meta.create_gate("rpi_bytes_acc = rpi_bytes for field start", |meta| { + let q_field_start = meta.query_selector(q_field_start); + let rpi_field_bytes_acc = meta.query_advice(rpi_bytes_acc, Rotation::cur()); + let rpi_field_bytes = meta.query_advice(rpi_bytes, Rotation::cur()); - // 0.1 rand_rpi[i] == rand_rpi[j] - meta.create_gate("rand_pi = rand_rpi.next", |meta| { - // q_not_end * row.rand_rpi == q_not_end * row_next.rand_rpi - let q_not_end = meta.query_selector(q_not_end); - let cur_rand_rpi = meta.query_advice(rand_rpi, Rotation::cur()); - let next_rand_rpi = meta.query_advice(rand_rpi, Rotation::next()); - - vec![q_not_end * (cur_rand_rpi - next_rand_rpi)] + vec![q_field_start * (rpi_field_bytes_acc - rpi_field_bytes)] }); + meta.create_gate("rpi_bytes_acc = rpi for field end", |meta| { + let q_field_end = meta.query_selector(q_field_end); + let rpi_bytes_acc = meta.query_advice(rpi_bytes_acc, Rotation::cur()); + let rpi = meta.query_advice(rpi, Rotation::cur()); - // 0.2 Block table -> value column match with raw_public_inputs at expected - // offset - meta.create_gate("block_table[i] = raw_public_inputs[offset + i]", |meta| { - let q_block_table = meta.query_selector(q_block_table); - let block_value = meta.query_advice(block_table.value, Rotation::cur()); - let rpi_block_value = meta.query_advice(raw_public_inputs, Rotation::cur()); - vec![q_block_table * (block_value - rpi_block_value)] + vec![q_field_end * (rpi - rpi_bytes_acc)] }); + meta.create_gate("rpi_next = rpi", |meta| { + let q_field_step = meta.query_selector(q_field_step); + let rpi_next = meta.query_advice(rpi, Rotation::next()); + let rpi = meta.query_advice(rpi, Rotation::cur()); - let offset = BLOCK_LEN + 1 + EXTRA_LEN; - let tx_table_len = max_txs * TX_LEN + 1; - - // 0.3 Tx table -> {tx_id, index, value} column match with raw_public_inputs - // at expected offset - meta.create_gate( - "tx_table.tx_id[i] == raw_public_inputs[offset + i]", - |meta| { - // row.q_tx_table * row.tx_table.tx_id - // == row.q_tx_table * row_offset_tx_table_tx_id.raw_public_inputs - let q_tx_table = meta.query_selector(q_tx_table); - let tx_id = meta.query_advice(tx_table.tx_id, Rotation::cur()); - let rpi_tx_id = meta.query_advice(raw_public_inputs, Rotation(offset as i32)); - - vec![q_tx_table * (tx_id - rpi_tx_id)] - }, - ); - - meta.create_gate( - "tx_table.index[i] == raw_public_inputs[offset + tx_table_len + i]", - |meta| { - // row.q_tx_table * row.tx_table.tx_index - // == row.q_tx_table * row_offset_tx_table_tx_index.raw_public_inputs - let q_tx_table = meta.query_selector(q_tx_table); - let tx_index = meta.query_advice(tx_table.index, Rotation::cur()); - let rpi_tx_index = - meta.query_advice(raw_public_inputs, Rotation((offset + tx_table_len) as i32)); - - vec![q_tx_table * (tx_index - rpi_tx_index)] - }, - ); + vec![q_field_step * (rpi_next - rpi)] + }); + // rpi_rlc meta.create_gate( - "tx_table.tx_value[i] == raw_public_inputs[offset + 2* tx_table_len + i]", + "rpi_rlc_acc[i+1] = keccak_rand * rpi_rlc_acc[i] + rpi_bytes[i+1]", |meta| { - // (row.q_tx_calldata | row.q_tx_table) * row.tx_table.tx_value - // == (row.q_tx_calldata | row.q_tx_table) * - // row_offset_tx_table_tx_value.raw_public_inputs - let q_tx_table = meta.query_selector(q_tx_table); - let tx_value = meta.query_advice(tx_value, Rotation::cur()); - let q_tx_calldata = meta.query_selector(q_tx_calldata); - let rpi_tx_value = meta.query_advice( - raw_public_inputs, - Rotation((offset + 2 * tx_table_len) as i32), + // if is_rpi_padding is true, then + // q_not_end * row_next.rpi_rlc_acc == + // (q_not_end * row.rpi_rlc_acc * keccak_rand + row_next.rpi_bytes) + // else, + // q_not_end * row_next.rpi_rlc_acc == q_not_end * row.rpi_rlc_acc + let mut cb = BaseConstraintBuilder::default(); + let is_rpi_padding = meta.query_advice(is_rpi_padding, Rotation::next()); + let rpi_rlc_acc_cur = meta.query_advice(rpi_rlc_acc, Rotation::cur()); + let rpi_bytes_next = meta.query_advice(rpi_bytes, Rotation::next()); + let keccak_rand = challenges.keccak_input(); + + cb.require_equal( + "rpi_rlc_acc' = is_rpi_padding ? rpi_rlc_acc : rpi_rlc_acc * r + rpi_bytes'", + meta.query_advice(rpi_rlc_acc, Rotation::next()), + select::expr( + is_rpi_padding.expr(), + rpi_rlc_acc_cur.expr(), + rpi_rlc_acc_cur * keccak_rand + rpi_bytes_next, + ), ); - vec![or::expr([q_tx_table, q_tx_calldata]) * (tx_value - rpi_tx_value)] - }, - ); + cb.require_equal( + "rpi_length_acc' = rpi_length_acc + (is_rpi_padding ? 0 : 1)", + meta.query_advice(rpi_length_acc, Rotation::next()), + meta.query_advice(rpi_length_acc, Rotation::cur()) + + select::expr(is_rpi_padding, 0.expr(), 1.expr()), + ); - let tx_id_is_zero_config = IsZeroChip::configure( - meta, - |meta| meta.query_selector(q_tx_calldata), - |meta| meta.query_advice(tx_table.tx_id, Rotation::cur()), - tx_id_inv, - ); - let tx_value_is_zero_config = IsZeroChip::configure( - meta, - |meta| { - or::expr([ - meta.query_selector(q_tx_table), - meta.query_selector(q_tx_calldata), - ]) - }, - |meta| meta.query_advice(tx_value, Rotation::cur()), - tx_value_inv, - ); - let _tx_id_diff_is_zero_config = IsZeroChip::configure( - meta, - |meta| meta.query_selector(q_tx_calldata), - |meta| { - meta.query_advice(tx_table.tx_id, Rotation::next()) - - meta.query_advice(tx_table.tx_id, Rotation::cur()) + cb.gate(meta.query_selector(q_not_end)) }, - tx_id_diff_inv, ); + meta.create_gate("rpi_rlc_acc[0] = rpi_bytes[0]", |meta| { + let q_start = meta.query_selector(q_start); + let rpi_rlc_acc = meta.query_advice(rpi_rlc_acc, Rotation::cur()); + let rpi_bytes = meta.query_advice(rpi_bytes, Rotation::cur()); - meta.lookup_any("tx_id_diff", |meta| { - let tx_id_next = meta.query_advice(tx_id, Rotation::next()); - let tx_id = meta.query_advice(tx_id, Rotation::cur()); - let tx_id_inv_next = meta.query_advice(tx_id_inv, Rotation::next()); - let tx_id_diff_inv = meta.query_advice(tx_id_diff_inv, Rotation::cur()); - let fixed_u16_table = meta.query_fixed(fixed_u16, Rotation::cur()); - - let tx_id_next_nonzero = tx_id_next.expr() * tx_id_inv_next; - let tx_id_not_equal_to_next = (tx_id_next.expr() - tx_id.expr()) * tx_id_diff_inv; - let tx_id_diff_minus_one = tx_id_next - tx_id - 1.expr(); - - vec![( - tx_id_diff_minus_one * tx_id_next_nonzero * tx_id_not_equal_to_next, - fixed_u16_table, - )] + vec![q_start * (rpi_rlc_acc - rpi_bytes)] }); - - meta.create_gate("calldata constraints", |meta| { - let q_is_calldata = meta.query_selector(q_tx_calldata); - let q_calldata_start = meta.query_selector(q_calldata_start); - let tx_idx = meta.query_advice(tx_id, Rotation::cur()); - let tx_idx_next = meta.query_advice(tx_id, Rotation::next()); - let tx_idx_inv_next = meta.query_advice(tx_id_inv, Rotation::next()); - let tx_idx_diff_inv = meta.query_advice(tx_id_diff_inv, Rotation::cur()); - let idx = meta.query_advice(index, Rotation::cur()); - let idx_next = meta.query_advice(index, Rotation::next()); - let value_next = meta.query_advice(tx_value, Rotation::next()); - let value_next_inv = meta.query_advice(tx_value_inv, Rotation::next()); - - let gas_cost = meta.query_advice(calldata_gas_cost, Rotation::cur()); - let gas_cost_next = meta.query_advice(calldata_gas_cost, Rotation::next()); - let is_final = meta.query_advice(is_final, Rotation::cur()); - - let is_tx_id_nonzero = not::expr(tx_id_is_zero_config.expr()); - let is_tx_id_next_nonzero = tx_idx_next.expr() * tx_idx_inv_next.expr(); - - let is_value_zero = tx_value_is_zero_config.expr(); - let is_value_nonzero = not::expr(tx_value_is_zero_config.expr()); - - let is_value_next_nonzero = value_next.expr() * value_next_inv.expr(); - let is_value_next_zero = not::expr(is_value_next_nonzero.expr()); - - // gas = value == 0 ? 4 : 16 - let gas = ZERO_BYTE_GAS_COST.expr() * is_value_zero.expr() - + NONZERO_BYTE_GAS_COST.expr() * is_value_nonzero.expr(); - let gas_next = ZERO_BYTE_GAS_COST.expr() * is_value_next_zero - + NONZERO_BYTE_GAS_COST.expr() * is_value_next_nonzero; - - // if tx_id == 0 then idx == 0, tx_id_next == 0 - let default_calldata_row_constraint1 = tx_id_is_zero_config.expr() * idx.expr(); - let default_calldata_row_constraint2 = tx_id_is_zero_config.expr() * tx_idx_next.expr(); - let default_calldata_row_constraint3 = tx_id_is_zero_config.expr() * is_final.expr(); - let default_calldata_row_constraint4 = tx_id_is_zero_config.expr() * gas_cost.expr(); - - // if tx_id != 0 then - // 1. tx_id_next == tx_id: idx_next == idx + 1, gas_cost_next == gas_cost + - // gas_next, is_final == false; - // 2. tx_id_next == tx_id + 1 + x (where x is in [0, 2^16)): idx_next == 0, - // gas_cost_next == gas_next, is_final == true; - // 3. tx_id_next == 0: is_final == true, idx_next == 0, gas_cost_next == 0; - // either case 1, case 2 or case 3 holds. - - let tx_id_equal_to_next = - 1.expr() - (tx_idx_next.expr() - tx_idx.expr()) * tx_idx_diff_inv.expr(); - let idx_of_same_tx_constraint = - tx_id_equal_to_next.clone() * (idx_next.expr() - idx.expr() - 1.expr()); - let idx_of_next_tx_constraint = (tx_idx_next.expr() - tx_idx.expr()) * idx_next.expr(); - - let gas_cost_of_same_tx_constraint = tx_id_equal_to_next.clone() - * (gas_cost_next.expr() - gas_cost.expr() - gas_next.expr()); - let gas_cost_of_next_tx_constraint = is_tx_id_next_nonzero.expr() - * (tx_idx_next.expr() - tx_idx.expr()) - * (gas_cost_next.expr() - gas_next.expr()); - - let is_final_of_same_tx_constraint = tx_id_equal_to_next * is_final.expr(); - let is_final_of_next_tx_constraint = - (tx_idx_next.expr() - tx_idx.expr()) * (is_final.expr() - 1.expr()); - - // if tx_id != 0 then - // 1. q_calldata_start * (index - 0) == 0 and - // 2. q_calldata_start * (gas_cost - gas) == 0. - - vec![ - q_is_calldata.expr() * default_calldata_row_constraint1, - q_is_calldata.expr() * default_calldata_row_constraint2, - q_is_calldata.expr() * default_calldata_row_constraint3, - q_is_calldata.expr() * default_calldata_row_constraint4, - q_is_calldata.expr() * is_tx_id_nonzero.expr() * idx_of_same_tx_constraint, - q_is_calldata.expr() * is_tx_id_nonzero.expr() * idx_of_next_tx_constraint, - q_is_calldata.expr() * is_tx_id_nonzero.expr() * gas_cost_of_same_tx_constraint, - q_is_calldata.expr() * is_tx_id_nonzero.expr() * gas_cost_of_next_tx_constraint, - q_is_calldata.expr() * is_tx_id_nonzero.expr() * is_final_of_same_tx_constraint, - q_is_calldata.expr() * is_tx_id_nonzero.expr() * is_final_of_next_tx_constraint, - q_calldata_start.expr() * is_tx_id_nonzero.expr() * (idx - 0.expr()), - q_calldata_start.expr() * is_tx_id_nonzero.expr() * (gas_cost - gas), - ] + meta.create_gate("real rpi", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_boolean( + "is_rpi_padding is boolean", + meta.query_advice(is_rpi_padding, Rotation::cur()), + ); + + cb.require_equal( + "real_rpi == not(is_rpi_padding) * rpi", + meta.query_advice(real_rpi, Rotation::cur()), + not::expr(meta.query_advice(is_rpi_padding, Rotation::cur())) + * meta.query_advice(rpi, Rotation::cur()), + ); + + cb.gate(meta.query_selector(q_not_end)) }); - // Test if tx tag equals to CallDataLength - let tx_tag_is_cdl_config = IsZeroChip::configure( - meta, - |meta| meta.query_selector(q_tx_table), - |meta| meta.query_advice(tag, Rotation::cur()) - TxFieldTag::CallDataLength.expr(), - tx_id_inv, - ); - - meta.create_gate( - "call_data_gas_cost should be zero if call_data_length is zero", - |meta| { - let q_tx_table = meta.query_selector(q_tx_table); - - let is_calldata_length_zero = tx_value_is_zero_config.expr(); - let is_calldata_length_row = tx_tag_is_cdl_config.expr(); - let calldata_cost = meta.query_advice(tx_value, Rotation::next()); - - vec![q_tx_table * is_calldata_length_row * is_calldata_length_zero * calldata_cost] - }, - ); - - meta.lookup_any("gas_cost in tx table", |meta| { - let q_tx_table = meta.query_selector(q_tx_table); - let is_final = meta.query_advice(is_final, Rotation::cur()); - - let tx_id = meta.query_advice(tx_id, Rotation::cur()); - - // calldata gas cost assigned in the tx table - // CallDataGasCost is on the next row of CallDataLength - let calldata_cost_assigned = meta.query_advice(tx_value, Rotation::next()); - // calldata gas cost calculated in call data - let calldata_cost_calc = meta.query_advice(calldata_gas_cost, Rotation::cur()); + meta.lookup_any("keccak(rpi)", |meta| { + let is_enabled = meta.query_advice(keccak_table.is_enabled, Rotation::cur()); + let input_rlc = meta.query_advice(keccak_table.input_rlc, Rotation::cur()); + let input_len = meta.query_advice(keccak_table.input_len, Rotation::cur()); + let output_rlc = meta.query_advice(keccak_table.output_rlc, Rotation::cur()); + let q_keccak = meta.query_selector(q_keccak); - let is_calldata_length_row = tx_tag_is_cdl_config.expr(); - let is_calldata_length_nonzero = not::expr(tx_value_is_zero_config.expr()); - - // lookup (tx_id, true, is_calldata_length_nonzero * is_calldata_cost * - // gas_cost) in the table (tx_id, is_final, gas_cost) - // if q_tx_table is true - let condition = q_tx_table * is_calldata_length_nonzero * is_calldata_length_row; + let rpi_rlc = meta.query_advice(rpi, Rotation::cur()); + let rpi_length = meta.query_advice(rpi_length_acc, Rotation::cur()); + let output = meta.query_advice(rpi_rlc_acc, Rotation::cur()); vec![ - (condition.expr() * tx_id.expr(), tx_id), - (condition.expr() * 1.expr(), is_final), + (q_keccak.expr() * 1.expr(), is_enabled), + (q_keccak.expr() * rpi_rlc, input_rlc), ( - condition.expr() * calldata_cost_assigned, - calldata_cost_calc, + q_keccak.expr() + // * (BLOCK_HEADER_BYTES_NUM + max_txs * KECCAK_DIGEST_SIZE).expr(), + * rpi_length, + input_len, ), + (q_keccak * output, output_rlc), ] }); + // The 32 bytes of keccak output are combined into (hi, lo) + // where r = challenges.evm_word(). + // And the layout will be like this. + // | rpi | rpi_bytes | rpi_bytes_acc | rpi_rlc_acc | + // | hi | b31 | b31 | b31 | + // | hi | b30 | b31*2^8 + b30 | b31*r + b30 | + // | hi | ... | ... | ... | + // | hi | b16 | b31*2^120+... | b31*r^15+...| + // | lo | b15 | b15 | b31*r^16+...| + // | lo | b14 | b15*2^8 + b14 | b31*r^17+...| + // | lo | ... | ... | ... | + // | lo | b0 | b15*2^120+... | b31*r^31+...| + + // TODO: add constraints on block_table.value for tag = 'CumNumTxs'. + // cur_block.cum_num_txs = prev_block.cum_num_txs + cur_block.num_txs + Self { max_txs, max_calldata, - q_block_table, + max_inner_blocks, block_table, - q_tx_table, - q_tx_calldata, - q_calldata_start, tx_table, - tx_id_inv, - tx_value_inv, - tx_id_diff_inv, - fixed_u16, - calldata_gas_cost, - is_final, - raw_public_inputs, + keccak_table, + raw_public_inputs: rpi, + rpi_field_bytes: rpi_bytes, + rpi_field_bytes_acc: rpi_bytes_acc, rpi_rlc_acc, - rand_rpi, + rpi_length_acc, + is_rpi_padding, + real_rpi, + q_field_start, + q_field_step, + is_field_rlc, + q_field_end, + q_start, q_not_end, - q_end, + q_keccak, pi, _marker: PhantomData, } @@ -546,540 +385,418 @@ impl SubCircuitConfig for PiCircuitConfig { } impl PiCircuitConfig { - /// Return the number of rows in the circuit - #[inline] - fn circuit_len(&self) -> usize { - // +1 empty row in block table, +1 empty row in tx_table - BLOCK_LEN + 1 + EXTRA_LEN + 3 * (TX_LEN * self.max_txs + 1) + self.max_calldata - } - - fn assign_tx_empty_row(&self, region: &mut Region<'_, F>, offset: usize) -> Result<(), Error> { - region.assign_advice( - || "tx_id", - self.tx_table.tx_id, - offset, - || Value::known(F::zero()), - )?; - region.assign_advice( - || "tx_id_inv", - self.tx_id_inv, - offset, - || Value::known(F::zero()), - )?; - region.assign_advice( - || "tag", - self.tx_table.tag, - offset, - || Value::known(F::from(TxFieldTag::Null as u64)), - )?; - region.assign_advice( - || "index", - self.tx_table.index, - offset, - || Value::known(F::zero()), - )?; - region.assign_advice( - || "tx_value", - self.tx_table.value, - offset, - || Value::known(F::zero()), - )?; - region.assign_advice( - || "tx_value_inv", - self.tx_value_inv, - offset, - || Value::known(F::zero()), - )?; - region.assign_advice( - || "is_final", - self.is_final, - offset, - || Value::known(F::zero()), - )?; - region.assign_advice( - || "gas_cost", - self.calldata_gas_cost, - offset, - || Value::known(F::zero()), - )?; - Ok(()) - } - /// Assigns a tx_table row and stores the values in a vec for the - /// raw_public_inputs column - #[allow(clippy::too_many_arguments)] - fn assign_tx_row( - &self, - region: &mut Region<'_, F>, - offset: usize, - tx_id: usize, - tag: TxFieldTag, - index: usize, - tx_value: F, - raw_pi_vals: &mut [F], - ) -> Result<(), Error> { - let tx_id = F::from(tx_id as u64); - // tx_id_inv = (tag - CallDataLength)^(-1) - let tx_id_inv = if tag != TxFieldTag::CallDataLength { - let x = F::from(tag as u64) - F::from(TxFieldTag::CallDataLength as u64); - x.invert().unwrap_or(F::zero()) - } else { - F::zero() - }; - let tag = F::from(tag as u64); - let index = F::from(index as u64); - let tx_value = tx_value; - let tx_value_inv = tx_value.invert().unwrap_or(F::zero()); - - self.q_tx_table.enable(region, offset)?; - - // Assign vals to Tx_table - region.assign_advice( - || "tx_id", - self.tx_table.tx_id, - offset, - || Value::known(tx_id), - )?; - region.assign_advice(|| "tag", self.tx_table.tag, offset, || Value::known(tag))?; - region.assign_advice( - || "index", - self.tx_table.index, - offset, - || Value::known(index), - )?; - region.assign_advice( - || "tx_value", - self.tx_table.value, - offset, - || Value::known(tx_value), - )?; - region.assign_advice( - || "tx_id_inv", - self.tx_id_inv, - offset, - || Value::known(tx_id_inv), - )?; - region.assign_advice( - || "tx_value_inverse", - self.tx_value_inv, - offset, - || Value::known(tx_value_inv), - )?; - - // Assign vals to raw_public_inputs column - let tx_table_len = TX_LEN * self.max_txs + 1; - - let id_offset = BLOCK_LEN + 1 + EXTRA_LEN; - let index_offset = id_offset + tx_table_len; - let value_offset = index_offset + tx_table_len; - - region.assign_advice( - || "raw_pi.tx_id", - self.raw_public_inputs, - offset + id_offset, - || Value::known(tx_id), - )?; - - region.assign_advice( - || "raw_pi.tx_index", - self.raw_public_inputs, - offset + index_offset, - || Value::known(index), - )?; - - region.assign_advice( - || "raw_pi.tx_value", - self.raw_public_inputs, - offset + value_offset, - || Value::known(tx_value), - )?; - - // Add copy to vec - raw_pi_vals[offset + id_offset] = tx_id; - raw_pi_vals[offset + index_offset] = index; - raw_pi_vals[offset + value_offset] = tx_value; - - Ok(()) - } - - /// Assigns one calldata row - #[allow(clippy::too_many_arguments)] - fn assign_tx_calldata_row( + #[allow(clippy::type_complexity)] + fn assign( &self, region: &mut Region<'_, F>, - offset: usize, - tx_id: usize, - tx_id_next: usize, - index: usize, - tx_value: F, - is_final: bool, - gas_cost: F, - raw_pi_vals: &mut [F], - ) -> Result<(), Error> { - let tx_id = F::from(tx_id as u64); - let tx_id_inv = tx_id.invert().unwrap_or(F::zero()); - let tx_id_diff = F::from(tx_id_next as u64) - tx_id; - let tx_id_diff_inv = tx_id_diff.invert().unwrap_or(F::zero()); - let tag = F::from(TxFieldTag::CallData as u64); - let index = F::from(index as u64); - let tx_value = tx_value; - let tx_value_inv = tx_value.invert().unwrap_or(F::zero()); - let is_final = if is_final { F::one() } else { F::zero() }; - - // Assign vals to raw_public_inputs column - let tx_table_len = TX_LEN * self.max_txs + 1; - let calldata_offset = tx_table_len + offset; - - self.q_tx_calldata.enable(region, calldata_offset)?; - - // Assign vals to Tx_table - region.assign_advice( - || "tx_id", - self.tx_table.tx_id, - calldata_offset, - || Value::known(tx_id), - )?; - region.assign_advice( - || "tx_id_inv", - self.tx_id_inv, - calldata_offset, - || Value::known(tx_id_inv), - )?; - region.assign_advice( - || "tag", - self.tx_table.tag, - calldata_offset, - || Value::known(tag), - )?; - region.assign_advice( - || "index", - self.tx_table.index, - calldata_offset, - || Value::known(index), - )?; - region.assign_advice( - || "tx_value", - self.tx_table.value, - calldata_offset, - || Value::known(tx_value), - )?; - region.assign_advice( - || "tx_value_inv", - self.tx_value_inv, - calldata_offset, - || Value::known(tx_value_inv), - )?; - region.assign_advice( - || "tx_id_diff_inv", - self.tx_id_diff_inv, - calldata_offset, - || Value::known(tx_id_diff_inv), - )?; - region.assign_advice( - || "is_final", - self.is_final, - calldata_offset, - || Value::known(is_final), - )?; - region.assign_advice( - || "gas_cost", - self.calldata_gas_cost, - calldata_offset, - || Value::known(gas_cost), - )?; - - let value_offset = BLOCK_LEN + 1 + EXTRA_LEN + 3 * tx_table_len; + public_data: &PublicData, + challenges: &Challenges>, + ) -> Result<(AssignedCell, AssignedCell), Error> { + let block_values = &public_data.block_ctxs; + let tx_hashes = public_data + .transactions + .iter() + .map(|tx| tx.hash) + .collect::>(); - region.assign_advice( - || "raw_pi.tx_value", - self.raw_public_inputs, - offset + value_offset, - || Value::known(tx_value), - )?; + let mut offset = 0; + let mut rpi_length_acc = 0u64; + let mut block_copy_cells = vec![]; + let mut block_copy_offsets = vec![]; + let mut tx_copy_cells = vec![]; + let mut block_table_offset = 1; // first row of block is all-zeros. + let mut rpi_rlc_acc = Value::known(F::zero()); + let dummy_tx_hash = get_dummy_tx_hash(public_data.chain_id.as_u64()); - // Add copy to vec - raw_pi_vals[offset + value_offset] = tx_value; + self.q_start.enable(region, offset)?; - Ok(()) - } + for (i, block) in block_values + .ctxs + .iter() + .map(|(_, block)| block.clone()) + .chain( + (block_values.ctxs.len()..self.max_inner_blocks) + .into_iter() + .map(|_| BlockContext::default()), + ) + .enumerate() + { + let is_rpi_padding = i >= block_values.ctxs.len(); + block_copy_offsets.push(block_table_offset); + let num_txs = public_data + .transactions + .iter() + .filter(|tx| tx.block_number == block.number.as_u64()) + .count() as u64; + // Assign fields in block table + // coinbase + let mut cells = self.assign_field_in_pi( + region, + &mut offset, + &block.coinbase.to_fixed_bytes(), + &mut rpi_rlc_acc, + &mut rpi_length_acc, + true, + is_rpi_padding, + challenges, + false, + )?; + debug_assert_eq!(cells.len(), 2); + block_copy_cells.push(cells[RPI_CELL_IDX].clone()); + + // timestamp + cells = self.assign_field_in_pi( + region, + &mut offset, + &block.timestamp.as_u64().to_be_bytes(), + &mut rpi_rlc_acc, + &mut rpi_length_acc, + true, + is_rpi_padding, + challenges, + false, + )?; + block_copy_cells.push(cells[RPI_CELL_IDX].clone()); + + // number + cells = self.assign_field_in_pi( + region, + &mut offset, + &block.number.as_u64().to_be_bytes(), + &mut rpi_rlc_acc, + &mut rpi_length_acc, + true, + is_rpi_padding, + challenges, + false, + )?; + block_copy_cells.push(cells[RPI_CELL_IDX].clone()); + + // difficulty + cells = self.assign_field_in_pi( + region, + &mut offset, + &block.difficulty.to_be_bytes(), + &mut rpi_rlc_acc, + &mut rpi_length_acc, + true, + is_rpi_padding, + challenges, + false, + )?; + block_copy_cells.push(cells[RPI_CELL_IDX].clone()); + + // gas_limit + cells = self.assign_field_in_pi( + region, + &mut offset, + &block.gas_limit.to_be_bytes(), + &mut rpi_rlc_acc, + &mut rpi_length_acc, + true, + is_rpi_padding, + challenges, + false, + )?; + block_copy_cells.push(cells[RPI_CELL_IDX].clone()); + + // base_fee + cells = self.assign_field_in_pi( + region, + &mut offset, + &block.base_fee.to_be_bytes(), + &mut rpi_rlc_acc, + &mut rpi_length_acc, + true, + is_rpi_padding, + challenges, + false, + )?; + block_copy_cells.push(cells[RPI_CELL_IDX].clone()); + + // chain_id + cells = self.assign_field_in_pi( + region, + &mut offset, + &block.chain_id.to_be_bytes(), + &mut rpi_rlc_acc, + &mut rpi_length_acc, + true, + is_rpi_padding, + challenges, + false, + )?; + block_copy_cells.push(cells[RPI_CELL_IDX].clone()); + + // num_txs + cells = self.assign_field_in_pi( + region, + &mut offset, + &num_txs.to_be_bytes(), + &mut rpi_rlc_acc, + &mut rpi_length_acc, + true, + is_rpi_padding, + challenges, + false, + )?; + block_copy_cells.push(cells[RPI_CELL_IDX].clone()); - /// Assigns the values for block table in the block_table column - /// and in the raw_public_inputs column. A copy is also stored in - /// a vector for computing RLC(raw_public_inputs) - fn assign_block_table( - &self, - region: &mut Region<'_, F>, - block_values: BlockValues, - randomness: F, - raw_pi_vals: &mut [F], - ) -> Result, Error> { - let mut offset = 0; - for i in 0..BLOCK_LEN + 1 { - self.q_block_table.enable(region, offset + i)?; + block_table_offset += 9 + block.history_hashes.len(); + } + debug_assert_eq!(offset, BLOCK_HEADER_BYTES_NUM * self.max_inner_blocks); + + // assign tx hashes + let num_txs = tx_hashes.len(); + let mut rpi_rlc_cell = None; + for tx_hash in tx_hashes.into_iter().chain( + (0..self.max_txs - num_txs) + .into_iter() + .map(|_| dummy_tx_hash), + ) { + let cells = self.assign_field_in_pi( + region, + &mut offset, + &tx_hash.to_fixed_bytes(), + &mut rpi_rlc_acc, + &mut rpi_length_acc, + false, + false, + challenges, + false, + )?; + tx_copy_cells.push(cells[RPI_CELL_IDX].clone()); + rpi_rlc_cell = Some(cells[RPI_RLC_ACC_CELL_IDX].clone()); } - // zero row - region.assign_advice( - || "zero", - self.block_table.value, - offset, - || Value::known(F::zero()), - )?; - region.assign_advice( - || "zero", - self.raw_public_inputs, - offset, - || Value::known(F::zero()), - )?; - raw_pi_vals[offset] = F::zero(); - offset += 1; - - // coinbase - let coinbase = block_values.coinbase.to_scalar().unwrap(); - region.assign_advice( - || "coinbase", - self.block_table.value, - offset, - || Value::known(coinbase), - )?; - region.assign_advice( - || "coinbase", - self.raw_public_inputs, - offset, - || Value::known(coinbase), - )?; - raw_pi_vals[offset] = coinbase; - offset += 1; - - // gas_limit - let gas_limit = F::from(block_values.gas_limit); - region.assign_advice( - || "gas_limit", - self.block_table.value, + debug_assert_eq!( offset, - || Value::known(gas_limit), - )?; - region.assign_advice( - || "gas_limit", - self.raw_public_inputs, - offset, - || Value::known(gas_limit), - )?; - raw_pi_vals[offset] = gas_limit; - offset += 1; + BLOCK_HEADER_BYTES_NUM * self.max_inner_blocks + KECCAK_DIGEST_SIZE * self.max_txs + ); - // number - let number = F::from(block_values.number); - region.assign_advice( - || "number", - self.block_table.value, - offset, - || Value::known(number), - )?; - region.assign_advice( - || "number", - self.raw_public_inputs, - offset, - || Value::known(number), - )?; - raw_pi_vals[offset] = number; - offset += 1; + for i in 0..(offset - 1) { + self.q_not_end.enable(region, i)?; + } - // timestamp - let timestamp = F::from(block_values.timestamp); - region.assign_advice( - || "timestamp", - self.block_table.value, - offset, - || Value::known(timestamp), - )?; - region.assign_advice( - || "timestamp", + for (block_cells, offset) in block_copy_cells + .chunks(8) + .into_iter() + .zip(block_copy_offsets.into_iter()) + { + for (i, block_cell) in block_cells.iter().enumerate() { + let row_offset = offset + i; + region.constrain_equal( + block_cell.cell(), + Cell { + region_index: RegionIndex(0), // FIXME: this is not safe + row_offset, + column: self.block_table.value.into(), + }, + )?; + } + } + for (i, tx_hash_cell) in tx_copy_cells.into_iter().enumerate() { + region.constrain_equal( + tx_hash_cell.cell(), + Cell { + region_index: RegionIndex(1), // FIXME: this is not safe + row_offset: i * 11 + 10, + column: self.tx_table.value.into(), + }, + )?; + } + // assign rpi_acc, keccak_rpi + let keccak_row = offset; + let rpi_rlc_cell = rpi_rlc_cell.unwrap(); + rpi_rlc_cell.copy_advice( + || "keccak(rpi)_input", + region, self.raw_public_inputs, - offset, - || Value::known(timestamp), + keccak_row, )?; - raw_pi_vals[offset] = timestamp; - offset += 1; - - // difficulty - let difficulty = rlc(block_values.difficulty.to_le_bytes(), randomness); + let keccak = public_data.get_pi(self.max_txs); + let keccak_rlc = + keccak + .to_fixed_bytes() + .iter() + .fold(Value::known(F::zero()), |acc, byte| { + acc.zip(challenges.evm_word()) + .and_then(|(acc, rand)| Value::known(acc * rand + F::from(*byte as u64))) + }); region.assign_advice( - || "difficulty", - self.block_table.value, - offset, - || Value::known(difficulty), + || "rpi_length_acc", + self.rpi_length_acc, + keccak_row, + || Value::known(F::from(rpi_length_acc)), )?; - region.assign_advice( - || "difficulty", - self.raw_public_inputs, - offset, - || Value::known(difficulty), + let keccak_output_cell = region.assign_advice( + || "keccak(rpi)_output", + self.rpi_rlc_acc, + keccak_row, + || keccak_rlc, )?; - raw_pi_vals[offset] = difficulty; - offset += 1; + self.q_keccak.enable(region, keccak_row)?; - // base_fee - let base_fee = rlc(block_values.base_fee.to_le_bytes(), randomness); - region.assign_advice( - || "base_fee", - self.block_table.value, - offset, - || Value::known(base_fee), - )?; - region.assign_advice( - || "base_fee", - self.raw_public_inputs, - offset, - || Value::known(base_fee), - )?; - raw_pi_vals[offset] = base_fee; + // start over to accumulate big-endian bytes of keccak output + rpi_rlc_acc = Value::known(F::zero()); offset += 1; - - // chain_id - let chain_id = F::from(block_values.chain_id); - region.assign_advice( - || "chain_id", - self.block_table.value, - offset, - || Value::known(chain_id), + // the high 16 bytes of keccak output + let mut cells = self.assign_field_in_pi( + region, + &mut offset, + &keccak.to_fixed_bytes()[..16], + &mut rpi_rlc_acc, + &mut rpi_length_acc, + false, + false, + challenges, + true, )?; - let chain_id_cell = region.assign_advice( - || "chain_id", - self.raw_public_inputs, - offset, - || Value::known(chain_id), + let keccak_hi_cell = cells[RPI_CELL_IDX].clone(); + + // the low 16 bytes of keccak output + cells = self.assign_field_in_pi( + region, + &mut offset, + &keccak.to_fixed_bytes()[16..], + &mut rpi_rlc_acc, + &mut rpi_length_acc, + false, + false, + challenges, + true, )?; - raw_pi_vals[offset] = chain_id; - offset += 1; - - for prev_hash in block_values.history_hashes { - let prev_hash = rlc(prev_hash.to_fixed_bytes(), randomness); - region.assign_advice( - || "prev_hash", - self.block_table.value, - offset, - || Value::known(prev_hash), - )?; - region.assign_advice( - || "prev_hash", - self.raw_public_inputs, - offset, - || Value::known(prev_hash), - )?; - raw_pi_vals[offset] = prev_hash; - offset += 1; - } + let keccak_lo_cell = cells[RPI_CELL_IDX].clone(); - Ok(chain_id_cell) - } - - /// Assigns the extra fields (not in block or tx tables): - /// - state root - /// - previous block state root - /// to the raw_public_inputs column and stores a copy in a - /// vector for computing RLC(raw_public_inputs). - fn assign_extra_fields( - &self, - region: &mut Region<'_, F>, - extra: ExtraValues, - randomness: F, - raw_pi_vals: &mut [F], - ) -> Result<[AssignedCell; 2], Error> { - let mut offset = BLOCK_LEN + 1; - // block hash - // let block_hash = rlc(extra.block_hash.to_fixed_bytes(), randomness); - // region.assign_advice( - // || "block.hash", - // self.raw_public_inputs, - // offset, - // || Ok(block_hash), - // )?; - // raw_pi_vals[offset] = block_hash; - // offset += 1; - - // block state root - let state_root = rlc(extra.state_root.to_fixed_bytes(), randomness); - let state_root_cell = region.assign_advice( - || "state.root", - self.raw_public_inputs, - offset, - || Value::known(state_root), + region.constrain_equal( + keccak_output_cell.cell(), + cells[RPI_RLC_ACC_CELL_IDX].cell(), )?; - raw_pi_vals[offset] = state_root; - offset += 1; - // previous block state root - let prev_state_root = rlc(extra.prev_state_root.to_fixed_bytes(), randomness); - let prev_state_root_cell = region.assign_advice( - || "parent_block.hash", - self.raw_public_inputs, - offset, - || Value::known(prev_state_root), - )?; - raw_pi_vals[offset] = prev_state_root; - Ok([state_root_cell, prev_state_root_cell]) + Ok((keccak_hi_cell, keccak_lo_cell)) } - /// Assign `rpi_rlc_acc` and `rand_rpi` columns - #[allow(clippy::type_complexity)] - fn assign_rlc_pi( + #[allow(clippy::too_many_arguments)] + fn assign_field_in_pi( &self, region: &mut Region<'_, F>, - rand_rpi: F, - raw_pi_vals: Vec, - ) -> Result<(AssignedCell, AssignedCell), Error> { - let circuit_len = self.circuit_len(); - assert_eq!(circuit_len, raw_pi_vals.len()); - - // Last row - let offset = circuit_len - 1; - let mut rpi_rlc_acc = raw_pi_vals[offset]; - region.assign_advice( - || "rpi_rlc_acc", - self.rpi_rlc_acc, - offset, - || Value::known(rpi_rlc_acc), - )?; - region.assign_advice( - || "rand_rpi", - self.rand_rpi, - offset, - || Value::known(rand_rpi), - )?; - self.q_end.enable(region, offset)?; + offset: &mut usize, + value_bytes: &[u8], + rpi_rlc_acc: &mut Value, + rpi_length_acc: &mut u64, + is_block: bool, + is_padding: bool, + challenges: &Challenges>, + keccak_hi_lo: bool, + ) -> Result>, Error> { + let len = value_bytes.len(); + + let mut value_bytes_acc = Value::known(F::zero()); + let (use_rlc, t) = if len * 8 > F::CAPACITY as usize { + (F::one(), challenges.evm_word()) + } else { + (F::zero(), Value::known(F::from(BYTE_POW_BASE))) + }; + let r = if keccak_hi_lo { + challenges.evm_word() + } else { + challenges.keccak_input() + }; + let value = value_bytes + .iter() + .fold(Value::known(F::zero()), |acc, byte| { + acc.zip(t) + .and_then(|(acc, t)| Value::known(acc * t + F::from(*byte as u64))) + }); - // Next rows - for offset in (1..circuit_len - 1).rev() { - rpi_rlc_acc *= rand_rpi; - rpi_rlc_acc += raw_pi_vals[offset]; + let mut cells = vec![None, None]; + for (i, byte) in value_bytes.iter().enumerate() { + let row_offset = *offset + i; + + let real_value = if is_padding { + Value::known(F::zero()) + } else { + value + }; + *rpi_length_acc += if is_padding { 0 } else { 1 }; + // calculate acc + value_bytes_acc = value_bytes_acc + .zip(t) + .and_then(|(acc, t)| Value::known(acc * t + F::from(*byte as u64))); + + if !is_padding { + *rpi_rlc_acc = rpi_rlc_acc + .zip(r) + .and_then(|(acc, rand)| Value::known(acc * rand + F::from(*byte as u64))); + } + + // set field-related selectors + if i == 0 { + self.q_field_start.enable(region, row_offset)?; + } + if i == len - 1 { + self.q_field_end.enable(region, row_offset)?; + } else { + self.q_field_step.enable(region, row_offset)?; + } + + region.assign_fixed( + || "is_field_rlc", + self.is_field_rlc, + row_offset, + || Value::known(use_rlc), + )?; + region.assign_advice( + || "field byte", + self.rpi_field_bytes, + row_offset, + || Value::known(F::from(*byte as u64)), + )?; region.assign_advice( + || "field byte acc", + self.rpi_field_bytes_acc, + row_offset, + || value_bytes_acc, + )?; + let rpi_cell = region.assign_advice( + || "field value", + self.raw_public_inputs, + row_offset, + || value, + )?; + let rpi_rlc_cell = region.assign_advice( || "rpi_rlc_acc", self.rpi_rlc_acc, - offset, - || Value::known(rpi_rlc_acc), + row_offset, + || *rpi_rlc_acc, + )?; + region.assign_advice( + || "is_rpi_padding", + self.is_rpi_padding, + row_offset, + || Value::known(F::from(is_padding as u64)), )?; + let real_rpi_cell = + region.assign_advice(|| "real_rpi", self.real_rpi, row_offset, || real_value)?; region.assign_advice( - || "rand_rpi", - self.rand_rpi, - offset, - || Value::known(rand_rpi), + || "rpi_length_acc", + self.rpi_length_acc, + row_offset, + || Value::known(F::from(*rpi_length_acc)), )?; - self.q_not_end.enable(region, offset)?; + + if i == len - 1 { + cells[RPI_CELL_IDX] = if is_block { + Some(real_rpi_cell) + } else { + Some(rpi_cell) + }; + cells[RPI_RLC_ACC_CELL_IDX] = Some(rpi_rlc_cell); + } } + *offset += len; - // First row - rpi_rlc_acc *= rand_rpi; - rpi_rlc_acc += raw_pi_vals[0]; - let rpi_rlc = region.assign_advice( - || "rpi_rlc_acc", - self.rpi_rlc_acc, - 0, - || Value::known(rpi_rlc_acc), - )?; - let rpi_rand = - region.assign_advice(|| "rand_rpi", self.rand_rpi, 0, || Value::known(rand_rpi))?; - self.q_not_end.enable(region, 0)?; - Ok((rpi_rand, rpi_rlc)) + Ok(cells.into_iter().map(|cell| cell.unwrap()).collect()) } } @@ -1088,12 +805,11 @@ impl PiCircuitConfig { pub struct PiCircuit { max_txs: usize, max_calldata: usize, - /// Randomness for RLC encdoing - pub randomness: F, - /// Randomness for PI encoding - pub rand_rpi: F, + max_inner_blocks: usize, /// PublicInputs data known by the verifier pub public_data: PublicData, + + _marker: PhantomData, } impl PiCircuit { @@ -1101,24 +817,9 @@ impl PiCircuit { pub fn new( max_txs: usize, max_calldata: usize, - randomness: impl Into, - rand_rpi: impl Into, - public_data: PublicData, + max_inner_blocks: usize, + block: &Block, ) -> Self { - Self { - max_txs, - max_calldata, - randomness: randomness.into(), - rand_rpi: rand_rpi.into(), - public_data, - } - } -} - -impl SubCircuit for PiCircuit { - type Config = PiCircuitConfig; - - fn new_from_block(block: &witness::Block) -> Self { let context = block .context .ctxs @@ -1128,66 +829,57 @@ impl SubCircuit for PiCircuit { .unwrap_or_default(); let public_data = PublicData { chain_id: context.chain_id, - history_hashes: context.history_hashes.clone(), - transactions: context.eth_block.transactions.clone(), - state_root: context.eth_block.state_root, - prev_state_root: H256::from_uint(&block.prev_state_root), - block_constants: BlockConstants { - coinbase: context.coinbase, - timestamp: context.timestamp, - number: context.number.as_u64().into(), - difficulty: context.difficulty, - gas_limit: context.gas_limit.into(), - base_fee: context.base_fee, - }, + // history_hashes: context.history_hashes.clone(), + transactions: block.txs.clone(), + block_ctxs: block.context.clone(), }; + Self { + public_data, + max_txs, + max_calldata, + max_inner_blocks, + _marker: PhantomData, + } + } + + /// Return txs + pub fn txs(&self) -> &[Transaction] { + &self.public_data.transactions + } +} + +impl SubCircuit for PiCircuit { + type Config = PiCircuitConfig; + + fn new_from_block(block: &Block) -> Self { PiCircuit::new( block.circuits_params.max_txs, block.circuits_params.max_calldata, - block.randomness, - block.randomness + F::from_u128(1), - public_data, + block.circuits_params.max_inner_blocks, + block, ) } /// Compute the public inputs for this circuit. fn instance(&self) -> Vec> { - let rlc_rpi_col = raw_public_inputs_col::( - self.max_txs, - self.max_calldata, - &self.public_data, - self.randomness, - ); - assert_eq!( - rlc_rpi_col.len(), - BLOCK_LEN + 1 + EXTRA_LEN + 3 * (TX_LEN * self.max_txs + 1) + self.max_calldata - ); + let keccak_rpi = self.public_data.get_pi(self.max_txs); + let keccak_hi = keccak_rpi + .to_fixed_bytes() + .iter() + .take(16) + .fold(F::zero(), |acc, byte| { + acc * F::from(BYTE_POW_BASE) + F::from(*byte as u64) + }); - // Computation of raw_pulic_inputs - let rlc_rpi = rlc_rpi_col + let keccak_lo = keccak_rpi + .to_fixed_bytes() .iter() - .rev() - .fold(F::zero(), |acc, val| acc * self.rand_rpi + val); - - // let block_hash = public_data - // .eth_block - // .hash - // .unwrap_or_else(H256::zero) - // .to_fixed_bytes(); - let public_inputs = vec![ - self.rand_rpi, - rlc_rpi, - F::from(self.public_data.chain_id.as_u64()), - rlc( - self.public_data.state_root.to_fixed_bytes(), - self.randomness, - ), - rlc( - self.public_data.prev_state_root.to_fixed_bytes(), - self.randomness, - ), - ]; + .skip(16) + .fold(F::zero(), |acc, byte| { + acc * F::from(BYTE_POW_BASE) + F::from(*byte as u64) + }); + let public_inputs = vec![keccak_hi, keccak_lo]; vec![public_inputs] } @@ -1195,189 +887,22 @@ impl SubCircuit for PiCircuit { fn synthesize_sub( &self, config: &Self::Config, - _challenges: &Challenges>, + challenges: &Challenges>, layouter: &mut impl Layouter, ) -> Result<(), Error> { - layouter.assign_region( - || "fixed u16 table", - |mut region| { - for i in 0..(1 << 16) { - region.assign_fixed( - || format!("row_{}", i), - config.fixed_u16, - i, - || Value::known(F::from(i as u64)), - )?; - } - - Ok(()) - }, - )?; let pi_cells = layouter.assign_region( - || "region 0", + || "pi region", |mut region| { - let circuit_len = config.circuit_len(); - let mut raw_pi_vals = vec![F::zero(); circuit_len]; - - // Assign block table - let block_values = self.public_data.get_block_table_values(); - let chain_id = config.assign_block_table( - &mut region, - block_values, - self.randomness, - &mut raw_pi_vals, - )?; + // assign + let (keccak_hi_cell, keccak_lo_cell) = + config.assign(&mut region, &self.public_data, challenges)?; - // Assign extra fields - let extra_vals = self.public_data.get_extra_values(); - let [state_root, prev_state_root] = config.assign_extra_fields( - &mut region, - extra_vals, - self.randomness, - &mut raw_pi_vals, - )?; - - let mut offset = 0; - // Assign Tx table - let txs = self.public_data.get_tx_table_values(); - assert!(txs.len() <= config.max_txs); - let tx_default = TxValues::default(); - - // Add empty row - config.assign_tx_row( - &mut region, - offset, - 0, - TxFieldTag::Null, - 0, - F::zero(), - &mut raw_pi_vals, - )?; - offset += 1; - - for i in 0..config.max_txs { - let tx = if i < txs.len() { &txs[i] } else { &tx_default }; - - for (tag, value) in &[ - ( - TxFieldTag::Nonce, - rlc(tx.nonce.to_le_bytes(), self.randomness), - ), - (TxFieldTag::Gas, rlc(tx.gas.to_le_bytes(), self.randomness)), - ( - TxFieldTag::GasPrice, - rlc(tx.gas_price.to_le_bytes(), self.randomness), - ), - ( - TxFieldTag::CallerAddress, - tx.from_addr.to_scalar().expect("tx.from too big"), - ), - ( - TxFieldTag::CalleeAddress, - tx.to_addr.to_scalar().expect("tx.to too big"), - ), - (TxFieldTag::IsCreate, F::from(tx.is_create)), - ( - TxFieldTag::Value, - rlc(tx.value.to_le_bytes(), self.randomness), - ), - (TxFieldTag::CallDataLength, F::from(tx.call_data_len)), - (TxFieldTag::CallDataGasCost, F::from(tx.call_data_gas_cost)), - ( - TxFieldTag::TxSignHash, - rlc(tx.tx_sign_hash, self.randomness), - ), - ] { - config.assign_tx_row( - &mut region, - offset, - i + 1, - *tag, - 0, - *value, - &mut raw_pi_vals, - )?; - offset += 1; - } - } - // Tx Table CallData - let mut calldata_count = 0; - config.q_calldata_start.enable(&mut region, offset)?; - // the call data bytes assignment starts at offset 0 - offset = 0; - let txs = self.public_data.txs(); - for (i, tx) in self.public_data.txs().iter().enumerate() { - let call_data_length = tx.call_data.0.len(); - let mut gas_cost = F::zero(); - for (index, byte) in tx.call_data.0.iter().enumerate() { - assert!(calldata_count < config.max_calldata); - let is_final = index == call_data_length - 1; - gas_cost += if *byte == 0 { - F::from(ZERO_BYTE_GAS_COST) - } else { - F::from(NONZERO_BYTE_GAS_COST) - }; - let tx_id_next = if is_final { - let mut j = i + 1; - while j < txs.len() && txs[j].call_data.0.is_empty() { - j += 1; - } - if j >= txs.len() { - 0 - } else { - j + 1 - } - } else { - i + 1 - }; - - config.assign_tx_calldata_row( - &mut region, - offset, - i + 1, - tx_id_next as usize, - index, - F::from(*byte as u64), - is_final, - gas_cost, - &mut raw_pi_vals, - )?; - offset += 1; - calldata_count += 1; - } - } - for _ in calldata_count..config.max_calldata { - config.assign_tx_calldata_row( - &mut region, - offset, - 0, // tx_id - 0, - 0, - F::zero(), - false, - F::zero(), - &mut raw_pi_vals, - )?; - offset += 1; - } - // NOTE: we add this empty row so as to pass mock prover's check - // otherwise it will emit CellNotAssigned Error - let tx_table_len = TX_LEN * self.max_txs + 1; - config.assign_tx_empty_row(&mut region, tx_table_len + offset)?; - - // rpi_rlc and rand_rpi cols - let (rpi_rand, rpi_rlc) = - config.assign_rlc_pi(&mut region, self.rand_rpi, raw_pi_vals)?; - - Ok(vec![ - rpi_rand, - rpi_rlc, - chain_id, - state_root, - prev_state_root, - ]) + Ok(vec![keccak_hi_cell, keccak_lo_cell]) }, )?; + // TODO: add copy constraints between block_table.index and + // block_table.value (tag = 'Number') for tags except + // history_hashes // Constrain raw_public_input cells to public inputs for (i, pi_cell) in pi_cells.iter().enumerate() { @@ -1397,13 +922,16 @@ impl SubCircuit for PiCircuit { /// Test Circuit for PiCircuit #[cfg(any(feature = "test", test))] #[derive(Default)] -pub struct PiTestCircuit( - pub PiCircuit, -); +pub struct PiTestCircuit< + F: Field, + const MAX_TXS: usize, + const MAX_CALLDATA: usize, + const MAX_INNER_BLOCKS: usize, +>(pub PiCircuit); #[cfg(any(feature = "test", test))] -impl Circuit - for PiTestCircuit +impl + Circuit for PiTestCircuit { type Config = (PiCircuitConfig, Challenges); type FloorPlanner = SimpleFloorPlanner; @@ -1415,17 +943,23 @@ impl Circuit fn configure(meta: &mut ConstraintSystem) -> Self::Config { let block_table = BlockTable::construct(meta); let tx_table = TxTable::construct(meta); + let keccak_table = KeccakTable::construct(meta); + let challenges = Challenges::construct(meta); + let challenge_exprs = challenges.exprs(meta); ( PiCircuitConfig::new( meta, PiCircuitConfigArgs { max_txs: MAX_TXS, max_calldata: MAX_CALLDATA, + max_inner_blocks: MAX_INNER_BLOCKS, block_table, + keccak_table, tx_table, + challenges: challenge_exprs, }, ), - Challenges::construct(meta), + challenges, ) } @@ -1435,147 +969,63 @@ impl Circuit mut layouter: impl Layouter, ) -> Result<(), Error> { let challenges = challenges.values(&mut layouter); - self.0.synthesize_sub(&config, &challenges, &mut layouter) - } -} -/// Compute the raw_public_inputs column from the verifier's perspective. -fn raw_public_inputs_col( - max_txs: usize, - max_calldata: usize, - public_data: &PublicData, - randomness: F, // For RLC encoding -) -> Vec { - let block = public_data.get_block_table_values(); - let extra = public_data.get_extra_values(); - let txs = public_data.get_tx_table_values(); - - let mut offset = 0; - let mut result = - vec![F::zero(); BLOCK_LEN + 1 + EXTRA_LEN + 3 * (TX_LEN * max_txs + 1) + max_calldata]; - - // Insert Block Values - // zero row - result[offset] = F::zero(); - offset += 1; - // coinbase - result[offset] = block.coinbase.to_scalar().unwrap(); - offset += 1; - // gas_limit - result[offset] = F::from(block.gas_limit); - offset += 1; - // number - result[offset] = F::from(block.number); - offset += 1; - // timestamp - result[offset] = F::from(block.timestamp); - offset += 1; - // difficulty - result[offset] = rlc(block.difficulty.to_le_bytes(), randomness); - offset += 1; - // base_fee - result[offset] = rlc(block.base_fee.to_le_bytes(), randomness); - offset += 1; - // chain_id - result[offset] = F::from(block.chain_id); - offset += 1; - // Previous block hashes - for prev_hash in block.history_hashes { - result[offset] = rlc(prev_hash.to_fixed_bytes(), randomness); - offset += 1; - } + // assign block table + config.block_table.load( + &mut layouter, + &self.0.public_data.block_ctxs, + &self.0.public_data.transactions, + self.0.max_inner_blocks, + &challenges, + )?; + // assign tx table + config.tx_table.load( + &mut layouter, + &self.0.public_data.transactions, + self.0.max_txs, + &challenges, + )?; + // assign keccak table + let rpi_bytes = self.0.public_data.raw_public_input_bytes(self.0.max_txs); + config + .keccak_table + .dev_load(&mut layouter, vec![&rpi_bytes], &challenges)?; - // Insert Extra Values - // block Root - result[BLOCK_LEN + 1] = rlc(extra.state_root.to_fixed_bytes(), randomness); - // parent block hash - result[BLOCK_LEN + 2] = rlc(extra.prev_state_root.to_fixed_bytes(), randomness); - - // Insert Tx table - offset = 0; - assert!(txs.len() <= max_txs); - let tx_default = TxValues::default(); - - let tx_table_len = TX_LEN * max_txs + 1; - - let id_offset = BLOCK_LEN + 1 + EXTRA_LEN; - let index_offset = id_offset + tx_table_len; - let value_offset = index_offset + tx_table_len; - - // Insert zero row - result[id_offset + offset] = F::zero(); - result[index_offset + offset] = F::zero(); - result[value_offset + offset] = F::zero(); - - offset += 1; - - for i in 0..max_txs { - let tx = if i < txs.len() { &txs[i] } else { &tx_default }; - - for val in &[ - rlc(tx.nonce.to_le_bytes(), randomness), - rlc(tx.gas.to_le_bytes(), randomness), - rlc(tx.gas_price.to_le_bytes(), randomness), - tx.from_addr.to_scalar().expect("tx.from too big"), - tx.to_addr.to_scalar().expect("tx.to too big"), - F::from(tx.is_create), - rlc(tx.value.to_le_bytes(), randomness), - F::from(tx.call_data_len), - F::from(tx.call_data_gas_cost), - rlc(tx.tx_sign_hash, randomness), - ] { - result[id_offset + offset] = F::from((i + 1) as u64); - result[index_offset + offset] = F::zero(); - result[value_offset + offset] = *val; - - offset += 1; - } - } - // Tx Table CallData - let mut calldata_count = 0; - for (_i, tx) in public_data.txs().iter().enumerate() { - for (_index, byte) in tx.call_data.0.iter().enumerate() { - assert!(calldata_count < max_calldata); - result[value_offset + offset] = F::from(*byte as u64); - offset += 1; - calldata_count += 1; - } - } - for _ in calldata_count..max_calldata { - result[value_offset + offset] = F::zero(); - offset += 1; - } + self.0.synthesize_sub(&config, &challenges, &mut layouter)?; - result + Ok(()) + } } #[cfg(test)] mod pi_circuit_test { use super::*; - use crate::test_util::rand_tx; + use crate::witness::block_convert; + use bus_mapping::mock::BlockData; + use eth_types::bytecode; + use eth_types::geth_types::GethData; use halo2_proofs::{ dev::{MockProver, VerifyFailure}, halo2curves::bn256::Fr, }; + use mock::TestContext; use pretty_assertions::assert_eq; - use rand::SeedableRng; - use rand_chacha::ChaCha20Rng; - fn run( + fn run< + F: Field, + const MAX_TXS: usize, + const MAX_CALLDATA: usize, + const MAX_INNER_BLOCKS: usize, + >( k: u32, - public_data: PublicData, + block: Block, ) -> Result<(), Vec> { - let mut rng = ChaCha20Rng::seed_from_u64(2); - let randomness = F::random(&mut rng); - let rand_rpi = F::random(&mut rng); - - let circuit = PiTestCircuit::(PiCircuit::new( + let circuit = PiTestCircuit::(PiCircuit::new( MAX_TXS, MAX_CALLDATA, - randomness, - rand_rpi, - public_data, + MAX_INNER_BLOCKS, + &block, )); let public_inputs = circuit.0.instance(); @@ -1586,34 +1036,37 @@ mod pi_circuit_test { prover.verify() } - #[test] - fn test_default_pi() { - const MAX_TXS: usize = 2; - const MAX_CALLDATA: usize = 8; - let public_data = PublicData::default(); - - let k = 17; - assert_eq!(run::(k, public_data), Ok(())); - } + // #[test] + // fn test_default_pi() { + // const MAX_TXS: usize = 2; + // const MAX_CALLDATA: usize = 8; + // let public_data = PublicData::default(); + // + // let k = 16; + // assert_eq!(run::(k, public_data), Ok(())); + // } #[test] fn test_simple_pi() { - const MAX_TXS: usize = 8; - const MAX_CALLDATA: usize = 200; - - let mut rng = ChaCha20Rng::seed_from_u64(2); - - let mut public_data = PublicData::default(); - let chain_id = 1337u64; - public_data.chain_id = Word::from(chain_id); - - let n_tx = 4; - for i in 0..n_tx { - let eth_tx = eth_types::Transaction::from(&rand_tx(&mut rng, chain_id, i & 2 == 0)); - public_data.transactions.push(eth_tx); - } - - let k = 17; - assert_eq!(run::(k, public_data), Ok(())); + const MAX_TXS: usize = 4; + const MAX_CALLDATA: usize = 20; + const MAX_INNER_BLOCKS: usize = 64; + + let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode! { + STOP + }) + .unwrap(); + let block: GethData = test_ctx.into(); + let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + let block = block_convert(&builder.block, &builder.code_db).unwrap(); + + let k = 16; + assert_eq!( + run::(k, block), + Ok(()) + ); } } diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs index cb63940b0..7e676ce75 100644 --- a/zkevm-circuits/src/super_circuit.rs +++ b/zkevm-circuits/src/super_circuit.rs @@ -81,6 +81,7 @@ use halo2_proofs::{ plonk::{Circuit, ConstraintSystem, Error, Expression}, }; +use crate::pi_circuit::{PiCircuit, PiCircuitConfig, PiCircuitConfigArgs}; use crate::rlp_circuit::{RlpCircuit, RlpCircuitConfig}; use std::array; use strum::IntoEnumIterator; @@ -96,6 +97,7 @@ pub struct SuperCircuitConfig< F: Field, const MAX_TXS: usize, const MAX_CALLDATA: usize, + const MAX_INNER_BLOCKS: usize, const MAX_RWS: usize, > { block_table: BlockTable, @@ -108,7 +110,7 @@ pub struct SuperCircuitConfig< bytecode_circuit: BytecodeCircuitConfig, copy_circuit: CopyCircuitConfig, keccak_circuit: KeccakCircuitConfig, - //pi_circuit: PiCircuitConfig, + pi_circuit: PiCircuitConfig, exp_circuit: ExpCircuitConfig, rlp_circuit: RlpCircuitConfig, } @@ -119,6 +121,7 @@ pub struct SuperCircuit< F: Field, const MAX_TXS: usize, const MAX_CALLDATA: usize, + const MAX_INNER_BLOCKS: usize, const MAX_RWS: usize, > { /// EVM Circuit @@ -128,7 +131,7 @@ pub struct SuperCircuit< /// The transaction circuit that will be used in the `synthesize` step. //pub tx_circuit: TxCircuit, /// Public Input Circuit - //pub pi_circuit: PiCircuit, + pub pi_circuit: PiCircuit, /// Bytecode Circuit pub bytecode_circuit: BytecodeCircuit, /// Copy Circuit @@ -141,8 +144,13 @@ pub struct SuperCircuit< pub rlp_circuit: RlpCircuit, } -impl - SuperCircuit +impl< + F: Field, + const MAX_TXS: usize, + const MAX_CALLDATA: usize, + const MAX_INNER_BLOCKS: usize, + const MAX_RWS: usize, + > SuperCircuit { /// Return the number of rows required to verify a given block pub fn get_num_rows_required(block: &Block) -> usize { @@ -163,10 +171,15 @@ impl Circuit - for SuperCircuit +impl< + F: Field, + const MAX_TXS: usize, + const MAX_CALLDATA: usize, + const MAX_INNER_BLOCKS: usize, + const MAX_RWS: usize, + > Circuit for SuperCircuit { - type Config = SuperCircuitConfig; + type Config = SuperCircuitConfig; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { @@ -229,19 +242,20 @@ impl - SuperCircuit +impl< + const MAX_TXS: usize, + const MAX_CALLDATA: usize, + const MAX_INNER_BLOCKS: usize, + const MAX_RWS: usize, + > SuperCircuit { /// From the witness data, generate a SuperCircuit instance with all of the /// sub-circuits filled with their corresponding witnesses. @@ -389,6 +417,7 @@ impl CircuitsParams { max_txs: MAX_TXS, max_calldata: MAX_CALLDATA, + max_inner_blocks: 64, max_rws: MAX_RWS, max_bytecode: 512, keccak_padding: None, @@ -428,7 +457,7 @@ impl let log2_ceil = |n| u32::BITS - (n as u32).leading_zeros() - (n & (n - 1) == 0) as u32; let num_rows_required = - SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, MAX_RWS>::get_num_rows_required(&block); + SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, MAX_INNER_BLOCKS, MAX_RWS>::get_num_rows_required(&block); let k = log2_ceil( 64 + fixed_table_tags @@ -454,18 +483,18 @@ impl let evm_circuit = EvmCircuit::new_from_block(&block); let state_circuit = StateCircuit::new_from_block(&block); //let tx_circuit = TxCircuit::new_from_block(&block); - //let pi_circuit = PiCircuit::new_from_block(&block); + let pi_circuit = PiCircuit::new_from_block(&block); let bytecode_circuit = BytecodeCircuit::new_from_block(&block); 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> { + let circuit = SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, MAX_INNER_BLOCKS, MAX_RWS> { evm_circuit, state_circuit, //tx_circuit, - //pi_circuit, + pi_circuit, bytecode_circuit, copy_circuit, exp_circuit, @@ -480,12 +509,10 @@ impl /// Returns suitable inputs for the SuperCircuit. pub fn instance(&self) -> Vec> { // SignVerifyChip -> ECDSAChip -> MainGate instance column - //let pi_instance = self.pi_circuit.instance(); // FIXME: why two columns?? //let instance = vec![pi_instance[0].clone()]; - //instance - vec![] + self.pi_circuit.instance() } } @@ -511,7 +538,7 @@ mod super_circuit_tests { #[test] fn super_circuit_degree() { let mut cs = ConstraintSystem::::default(); - SuperCircuit::<_, 1, 32, 256>::configure(&mut cs); + SuperCircuit::<_, 1, 32, 64, 256>::configure(&mut cs); log::info!("super circuit degree: {}", cs.degree()); log::info!("super circuit minimum_rows: {}", cs.minimum_rows()); assert!(cs.degree() <= 9); @@ -561,7 +588,7 @@ mod super_circuit_tests { block.sign(&wallets); - let (k, circuit, instance, _) = SuperCircuit::<_, 1, 32, 256>::build(block).unwrap(); + let (k, circuit, instance, _) = SuperCircuit::<_, 1, 32, 64, 256>::build(block).unwrap(); let prover = MockProver::run(k, &circuit, instance).unwrap(); let res = prover.verify_par(); if let Err(err) = res { diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index bc5d58667..cd6cfedaf 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -7,12 +7,16 @@ use crate::impl_expr; use crate::util::build_tx_log_address; use crate::util::Challenges; use crate::witness::{ - Block, BlockContexts, Bytecode, MptUpdateRow, MptUpdates, RlpWitnessGen, Rw, RwMap, RwRow, - SignedTransaction, Transaction, + Block, BlockContext, BlockContexts, Bytecode, MptUpdateRow, MptUpdates, RlpWitnessGen, Rw, + RwMap, RwRow, SignedTransaction, Transaction, +}; +use bus_mapping::circuit_input_builder::{ + get_dummy_tx, CopyDataType, CopyEvent, CopyStep, ExpEvent, }; -use bus_mapping::circuit_input_builder::{CopyDataType, CopyEvent, CopyStep, ExpEvent}; use core::iter::once; use eth_types::{Field, ToLittleEndian, ToScalar, Word, U256}; +use ethers_core::types::H256; +use ethers_core::utils::keccak256; use gadgets::binary_number::{BinaryNumberChip, BinaryNumberConfig}; use gadgets::util::{split_u256, split_u256_limb64}; use halo2_proofs::plonk::{Any, Expression, Fixed, VirtualCells}; @@ -72,10 +76,10 @@ pub enum TxFieldTag { Null = 0, /// Nonce Nonce, - /// Gas - Gas, /// GasPrice GasPrice, + /// Gas + Gas, /// CallerAddress CallerAddress, /// CalleeAddress @@ -175,14 +179,39 @@ impl TxTable { )?; offset += 1; + // FIXME: remove this hardcoded default chain_id + let chain_id = if !txs.is_empty() { txs[0].chain_id } else { 1 }; + let (dummy_tx, dummy_sig) = get_dummy_tx(chain_id); + let dummy_tx_hash = keccak256(dummy_tx.rlp_signed(&dummy_sig)); + let padding_txs: Vec = (txs.len()..max_txs) .map(|i| Transaction { id: i + 1, + hash: H256(dummy_tx_hash), ..Default::default() }) .collect(); for tx in txs.iter().chain(padding_txs.iter()) { - for row in tx.table_assignments(*challenges) { + for row in tx.table_assignments_fixed(*challenges) { + for (index, column) in advice_columns.iter().enumerate() { + region.assign_advice( + || format!("tx table row {}", offset), + *column, + offset, + || row[if index > 0 { index + 1 } else { index }], + )?; + } + region.assign_advice( + || format!("tx table row {}", offset), + self.tag, + offset, + || row[1], + )?; + offset += 1; + } + } + for tx in txs.iter().chain(padding_txs.iter()) { + for row in tx.table_assignments_dyn(*challenges) { for (index, column) in advice_columns.iter().enumerate() { region.assign_advice( || format!("tx table row {}", offset), @@ -717,7 +746,8 @@ impl BlockTable { layouter: &mut impl Layouter, block_ctxs: &BlockContexts, txs: &[Transaction], - randomness: F, + max_inner_blocks: usize, + challenges: &Challenges>, ) -> Result<(), Error> { layouter.assign_region( || "block table", @@ -735,19 +765,23 @@ impl BlockTable { offset += 1; let mut cum_num_txs = 0usize; - for block_ctx in block_ctxs.ctxs.values() { + let padding_blocks = (block_ctxs.ctxs.len()..max_inner_blocks) + .into_iter() + .map(|_| BlockContext::default()) + .collect::>(); + for block_ctx in block_ctxs.ctxs.values().chain(padding_blocks.iter()) { let num_txs = txs .iter() .filter(|tx| tx.block_number == block_ctx.number.as_u64()) .count(); cum_num_txs += num_txs; - for row in block_ctx.table_assignments(num_txs, cum_num_txs, randomness) { + for row in block_ctx.table_assignments(num_txs, cum_num_txs, challenges) { for (column, value) in block_table_columns.iter().zip_eq(row) { region.assign_advice( || format!("block table row {}", offset), *column, offset, - || Value::known(value), + || value, )?; } offset += 1; diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index 13c62d025..be46952fe 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -6,22 +6,27 @@ pub mod sign_verify; -use crate::table::RlpTable; -use crate::table::{KeccakTable, TxFieldTag, TxTable}; +use crate::evm_circuit::util::constraint_builder::BaseConstraintBuilder; +use crate::table::{KeccakTable, LookupTable, RlpTable, 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 crate::witness::{signed_tx_from_geth_tx, RlpDataType, RlpTxTag}; use bus_mapping::circuit_input_builder::keccak_inputs_tx_circuit; use eth_types::{ sign_types::SignData, {geth_types::Transaction, Address, Field, ToLittleEndian, ToScalar}, }; +use gadgets::binary_number::{BinaryNumberChip, BinaryNumberConfig}; +use gadgets::is_equal::{IsEqualChip, IsEqualConfig, IsEqualInstruction}; +use gadgets::util::{and, not, or, Expr}; +use halo2_proofs::poly::Rotation; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Region, SimpleFloorPlanner, Value}, plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Expression}, }; use itertools::Itertools; use log::error; +use num::Zero; use sign_verify::{AssignedSignatureVerify, SignVerifyChip, SignVerifyConfig}; use std::marker::PhantomData; @@ -33,15 +38,40 @@ pub use halo2_proofs::halo2curves::{ }, secp256k1::{self, Secp256k1Affine, Secp256k1Compressed}, }; +use halo2_proofs::plonk::Fixed; /// Config for TxCircuit #[derive(Clone, Debug)] pub struct TxCircuitConfig { - tx_id: Column, - tag: Column, - index: Column, - value: Column, + q_enable: Column, + is_usable: Column, + + /// TxFieldTag assigned to the row. + tag: BinaryNumberConfig, + /// Primarily used to verify if the `CallDataLength` is zero or non-zero. + value_is_zero: IsEqualConfig, + /// We use an equality gadget to know whether the tx id changes between + /// subsequent rows or not. + tx_id_unchanged: IsEqualConfig, + /// A boolean advice column, which is turned on only for the last byte in + /// call data. + is_final: Column, + /// A dedicated column that holds the calldata's length. We use this column + /// only for the TxFieldTag::CallData tag. + calldata_length: Column, + /// An accumulator value used to correctly calculate the calldata gas cost + /// for a tx. + calldata_gas_cost_acc: Column, + /// Chain ID. + chain_id: Column, + + /// Length of the RLP-encoded unsigned tx. + tx_sign_data_len: Column, + /// RLC-encoded RLP-encoding of unsigned tx. + tx_sign_data_rlc: Column, + sign_verify: SignVerifyConfig, + tx_table: TxTable, rlp_table: RlpTable, _marker: PhantomData, // External tables @@ -73,20 +103,225 @@ impl SubCircuitConfig for TxCircuitConfig { challenges, }: Self::ConfigArgs, ) -> Self { - let tx_id = tx_table.tx_id; - let tag = tx_table.tag; - let index = tx_table.index; - let value = tx_table.value; - meta.enable_equality(value); + let q_enable = meta.fixed_column(); + let is_usable = meta.advice_column(); + let tag = BinaryNumberChip::configure(meta, q_enable, None); + meta.enable_equality(tx_table.value); + + let value_is_zero = IsEqualChip::configure( + meta, + |meta| { + and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + or::expr(vec![ + tag.value_equals(TxFieldTag::CalleeAddress, Rotation::cur())(meta), + tag.value_equals(TxFieldTag::CallDataLength, Rotation::cur())(meta), + tag.value_equals(TxFieldTag::CallData, Rotation::cur())(meta), + ]), + ]) + }, + |meta| meta.query_advice(tx_table.value, Rotation::cur()), + |_| 0.expr(), + ); + let tx_id_unchanged = IsEqualChip::configure( + meta, + |meta| { + and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + ]) + }, + |meta| meta.query_advice(tx_table.tx_id, Rotation::cur()), + |meta| meta.query_advice(tx_table.tx_id, Rotation::next()), + ); + + let is_final = meta.advice_column(); + let calldata_length = meta.advice_column(); + let calldata_gas_cost_acc = meta.advice_column(); + let chain_id = meta.advice_column(); + + let tx_sign_data_len = meta.advice_column(); + let tx_sign_data_rlc = meta.advice_column(); + + Self::configure_lookups( + meta, + q_enable, + is_usable, + tag, + is_final, + calldata_length, + calldata_gas_cost_acc, + chain_id, + tx_sign_data_len, + tx_sign_data_rlc, + &value_is_zero, + tx_table.clone(), + keccak_table.clone(), + rlp_table, + ); let sign_verify = SignVerifyConfig::new(meta, keccak_table.clone(), challenges); + meta.create_gate("tx call data bytes", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + let is_final_cur = meta.query_advice(is_final, Rotation::cur()); + cb.require_boolean("is_final is boolean", is_final_cur.clone()); + + // checks for any row, except the final call data byte. + cb.condition(not::expr(is_final_cur.clone()), |cb| { + cb.require_equal( + "index::next == index::cur + 1", + meta.query_advice(tx_table.index, Rotation::next()), + meta.query_advice(tx_table.index, Rotation::cur()) + 1.expr(), + ); + cb.require_equal( + "tx_id::next == tx_id::cur", + tx_id_unchanged.is_equal_expression.clone(), + 1.expr(), + ); + cb.require_equal( + "calldata_length::cur == calldata_length::next", + meta.query_advice(calldata_length, Rotation::cur()), + meta.query_advice(calldata_length, Rotation::next()), + ); + }); + + // call data gas cost accumulator check. + cb.condition( + and::expr(vec![ + not::expr(is_final_cur.clone()), + value_is_zero.is_equal_expression.clone(), + ]), + |cb| { + cb.require_equal( + "calldata_gas_cost_acc::next == calldata_gas_cost::cur + 4", + meta.query_advice(calldata_gas_cost_acc, Rotation::next()), + meta.query_advice(calldata_gas_cost_acc, Rotation::cur()) + 4.expr(), + ); + }, + ); + cb.condition( + not::expr(or::expr(vec![ + is_final_cur.clone(), + value_is_zero.is_equal_expression.clone(), + ])), + |cb| { + cb.require_equal( + "calldata_gas_cost_acc::next == calldata_gas_cost::cur + 16", + meta.query_advice(calldata_gas_cost_acc, Rotation::next()), + meta.query_advice(calldata_gas_cost_acc, Rotation::cur()) + 16.expr(), + ); + }, + ); + + // on the final call data byte, tx_id must change. + cb.condition(is_final_cur, |cb| { + cb.require_zero( + "tx_id changes at is_final == 1", + tx_id_unchanged.is_equal_expression.clone(), + ); + cb.require_equal( + "calldata_length == index::cur + 1", + meta.query_advice(calldata_length, Rotation::cur()), + meta.query_advice(tx_table.index, Rotation::cur()) + 1.expr(), + ); + }); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::CallData, Rotation::cur())(meta), + ])) + }); + + meta.create_gate("tx id change at nonce row", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "tx_id::cur == tx_id::prev + 1", + meta.query_advice(tx_table.tx_id, Rotation::cur()), + meta.query_advice(tx_table.tx_id, Rotation::prev()) + 1.expr(), + ); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::Nonce, Rotation::cur())(meta), + ])) + }); + + meta.create_gate("tx is_create", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.condition(value_is_zero.is_equal_expression.clone(), |cb| { + cb.require_equal( + "if callee_address == 0 then is_create == 1", + meta.query_advice(tx_table.value, Rotation::next()), + 1.expr(), + ); + }); + cb.condition(not::expr(value_is_zero.is_equal_expression.clone()), |cb| { + cb.require_zero( + "if callee_address != 0 then is_create == 0", + meta.query_advice(tx_table.value, Rotation::next()), + ); + }); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::CalleeAddress, Rotation::cur())(meta), + ])) + }); + + meta.create_gate("tx signature v", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + let chain_id_expr = meta.query_advice(chain_id, Rotation::cur()); + cb.require_boolean( + "V - (chain_id * 2 + 35) Є {0, 1}", + meta.query_advice(tx_table.value, Rotation::cur()) + - (chain_id_expr.clone() + chain_id_expr + 35.expr()), + ); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::SigV, Rotation::cur())(meta), + ])) + }); + + meta.create_gate("tag equality", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "tag equality (fixed tag == binary number config's tag", + meta.query_advice(tx_table.tag, Rotation::cur()), + tag.value(Rotation::cur())(meta), + ); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + ])) + }); + Self { - tx_id, + q_enable, + is_usable, tag, - index, - value, + value_is_zero, + tx_id_unchanged, + is_final, + calldata_length, + calldata_gas_cost_acc, + chain_id, + tx_sign_data_len, + tx_sign_data_rlc, sign_verify, + tx_table, keccak_table, rlp_table, _marker: PhantomData, @@ -102,34 +337,105 @@ impl TxCircuitConfig { /// Assigns a tx circuit row and returns the assigned cell of the value in /// the row. + #[allow(clippy::too_many_arguments)] fn assign_row( &self, region: &mut Region<'_, F>, offset: usize, + usable: bool, tx_id: usize, + tx_id_next: usize, tag: TxFieldTag, index: usize, value: Value, + is_final: bool, + calldata_length: Option, + calldata_gas_cost_acc: Option, ) -> Result, Error> { + region.assign_fixed( + || "q_enable", + self.q_enable, + offset, + || Value::known(F::one()), + )?; + region.assign_advice( + || "is_usable", + self.is_usable, + offset, + || Value::known(F::from(usable as u64)), + )?; region.assign_advice( || "tx_id", - self.tx_id, + self.tx_table.tx_id, offset, || Value::known(F::from(tx_id as u64)), )?; region.assign_advice( || "tag", - self.tag, + self.tx_table.tag, offset, || Value::known(F::from(tag as u64)), )?; + + let tag_chip = BinaryNumberChip::construct(self.tag); + tag_chip.assign(region, offset, &tag)?; + region.assign_advice( || "index", - self.index, + self.tx_table.index, offset, || Value::known(F::from(index as u64)), )?; - region.assign_advice(|| "value", self.value, offset, || value) + + let is_zero_chip = IsEqualChip::construct(self.value_is_zero.clone()); + is_zero_chip.assign(region, offset, value, Value::known(F::zero()))?; + + let tx_id_unchanged_chip = IsEqualChip::construct(self.tx_id_unchanged.clone()); + tx_id_unchanged_chip.assign( + region, + offset, + Value::known(F::from(tx_id as u64)), + Value::known(F::from(tx_id_next as u64)), + )?; + + region.assign_advice( + || "is_final", + self.is_final, + offset, + || Value::known(F::from(is_final as u64)), + )?; + region.assign_advice( + || "calldata_length", + self.calldata_length, + offset, + || Value::known(F::from(calldata_length.unwrap_or_default())), + )?; + region.assign_advice( + || "calldata_gas_cost_acc", + self.calldata_gas_cost_acc, + offset, + || Value::known(F::from(calldata_gas_cost_acc.unwrap_or_default())), + )?; + region.assign_advice( + || "tx_sign_data_len", + self.tx_sign_data_len, + offset, + || Value::known(F::zero()), + )?; + region.assign_advice( + || "tx_sign_data_rlc", + self.tx_sign_data_rlc, + offset, + || Value::known(F::zero()), + )?; + region.assign_advice( + || "chain_id", + self.chain_id, + offset, + || Value::known(F::zero()), + )?; + + region.assign_advice(|| "value", self.tx_table.value, offset, || value) } /// Get number of rows required. @@ -139,6 +445,559 @@ impl TxCircuitConfig { let num_rows_per_tx = 140436; (num_tx * num_rows_per_tx).max(num_rows_range_table) } + + #[allow(clippy::too_many_arguments)] + fn configure_lookups( + meta: &mut ConstraintSystem, + q_enable: Column, + is_usable: Column, + tag: BinaryNumberConfig, + is_final: Column, + calldata_length: Column, + calldata_gas_cost_acc: Column, + chain_id: Column, + tx_sign_data_len: Column, + tx_sign_data_rlc: Column, + value_is_zero: &IsEqualConfig, + tx_table: TxTable, + keccak_table: KeccakTable, + rlp_table: RlpTable, + ) { + // lookup tx nonce. + meta.lookup_any("tx nonce in RLPTable::TxSign", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + tag.value_equals(TxFieldTag::Nonce, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::Nonce.expr(), + 1.expr(), // tag_index == 1 + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxSign.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + meta.lookup_any("tx nonce in RLPTable::TxHash", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + tag.value_equals(TxFieldTag::Nonce, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::Nonce.expr(), + 1.expr(), // tag_index == 1 + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxHash.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + + // lookup tx rlc(gasprice). + meta.lookup_any("tx rlc(gasprice) in RLPTable::TxSign", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + tag.value_equals(TxFieldTag::GasPrice, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::GasPrice.expr(), + 1.expr(), // tag_index == 1 + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxSign.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + meta.lookup_any("tx rlc(gasprice) in RLPTable::TxHash", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + tag.value_equals(TxFieldTag::GasPrice, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::GasPrice.expr(), + 1.expr(), // tag_index == 1 + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxHash.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + + // lookup tx gas. + meta.lookup_any("tx gas in RLPTable::TxSign", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + tag.value_equals(TxFieldTag::Gas, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::Gas.expr(), + 1.expr(), // tag_index == 1 + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxSign.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + meta.lookup_any("tx gas in RLPTable::TxHash", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + tag.value_equals(TxFieldTag::Gas, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::Gas.expr(), + 1.expr(), // tag_index == 1 + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxHash.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + + // lookup tx callee address. + meta.lookup_any("tx callee address in RLPTable::TxSign", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + tag.value_equals(TxFieldTag::CalleeAddress, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::To.expr(), + 1.expr(), // tag_index == 1 + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxSign.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + meta.lookup_any("tx callee address in RLPTable::TxHash", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + tag.value_equals(TxFieldTag::CalleeAddress, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::To.expr(), + 1.expr(), // tag_index == 1 + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxHash.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + + // lookup tx rlc(value). + meta.lookup_any("tx rlc(value) in RLPTable::TxSign", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + tag.value_equals(TxFieldTag::Value, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::Value.expr(), + 1.expr(), // tag_index == 1 + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxSign.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + meta.lookup_any("tx rlc(value) in RLPTable::TxHash", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + tag.value_equals(TxFieldTag::Value, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::Value.expr(), + 1.expr(), // tag_index == 1 + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxHash.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + + // lookup to check CallDataLength of the tx's call data. + meta.lookup_any("tx calldatalength in TxTable", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::CallData, Rotation::cur())(meta), + meta.query_advice(is_final, Rotation::cur()), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + TxFieldTag::CallDataLength.expr(), + 0.expr(), + meta.query_advice(tx_table.index, Rotation::cur()) + 1.expr(), + ] + .into_iter() + .zip(tx_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + + // lookup to check CallDataGasCost of the tx's call data. + meta.lookup_any("tx calldatagascost in TxTable", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::CallData, Rotation::cur())(meta), + meta.query_advice(is_final, Rotation::cur()), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + TxFieldTag::CallDataGasCost.expr(), + 0.expr(), + meta.query_advice(calldata_gas_cost_acc, Rotation::cur()), + ] + .into_iter() + .zip(tx_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + + // lookup RLP table to check SigV and Chain ID. + meta.lookup_any("rlp table Chain ID", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::SigV, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::ChainId.expr(), // tag + 1.expr(), // tag_index == 1 + meta.query_advice(chain_id, Rotation::cur()), + RlpDataType::TxSign.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + meta.lookup_any("rlp table SigV", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::SigV, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::SigV.expr(), // tag + 1.expr(), // tag_index == 1 + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxHash.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + + // lookup RLP table for SigR and SigS. + meta.lookup_any("rlp table SigR", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::SigR, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::SigR.expr(), + 1.expr(), + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxHash.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + meta.lookup_any("rlp table SigS", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::SigS, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::SigS.expr(), + 1.expr(), + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxHash.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + + // lookup tx calldata bytes in RLP table. + meta.lookup_any( + "tx calldata::index in RLPTable::TxSign where len(calldata) > 0", + |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::CallData, Rotation::cur())(meta), + not::expr(value_is_zero.is_equal_expression.clone()), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::Data.expr(), + meta.query_advice(calldata_length, Rotation::cur()) + - meta.query_advice(tx_table.index, Rotation::cur()), + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxSign.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }, + ); + meta.lookup_any( + "tx calldata::index in RLPTable::TxHash where len(calldata) > 0", + |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::CallData, Rotation::cur())(meta), + not::expr(value_is_zero.is_equal_expression.clone()), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::Data.expr(), + meta.query_advice(calldata_length, Rotation::cur()) + - meta.query_advice(tx_table.index, Rotation::cur()), + meta.query_advice(tx_table.value, Rotation::cur()), + RlpDataType::TxHash.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }, + ); + + // lookup tx's DataPrefix if call_data_length == 0. + meta.lookup_any( + "tx DataPrefix in RLPTable::TxSign where len(calldata) == 0", + |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + tag.value_equals(TxFieldTag::CallDataLength, Rotation::cur())(meta), + value_is_zero.is_equal_expression.clone(), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::DataPrefix.expr(), + 1.expr(), // tag_index == 1 + 128.expr(), // len == 0 => RLP == 128 + RlpDataType::TxSign.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }, + ); + meta.lookup_any( + "tx DataPrefix in RLPTable::TxHash where len(calldata) == 0", + |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + tag.value_equals(TxFieldTag::CallDataLength, Rotation::cur())(meta), + value_is_zero.is_equal_expression.clone(), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::DataPrefix.expr(), + 1.expr(), // tag_index == 1 + 128.expr(), // len == 0 => RLP == 128 + RlpDataType::TxHash.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }, + ); + + // lookup tx table to ensure call data bytes are populated if call_data_length > + // 0. + meta.lookup_any("is_final call data byte should be present", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::CallDataLength, Rotation::cur())(meta), + not::expr(value_is_zero.is_equal_expression.clone()), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + TxFieldTag::CallData.expr(), + meta.query_advice(tx_table.value, Rotation::cur()) - 1.expr(), // index + 1.expr(), // is_final + ] + .into_iter() + .zip( + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + meta.query_advice(tx_table.tag, Rotation::cur()), + meta.query_advice(tx_table.index, Rotation::cur()), + meta.query_advice(is_final, Rotation::cur()), + ] + .into_iter(), + ) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + + // lookup RLP table for length of RLP-encoding of unsigned tx. + meta.lookup_any("Length of RLP-encoding for RLPTable::TxSign", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::TxSignHash, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::RlpLength.expr(), + 1.expr(), // tag_index + meta.query_advice(tx_sign_data_len, Rotation::cur()), + RlpDataType::TxSign.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + + // lookup RLP table for RLC of RLP-encoding of unsigned tx. + meta.lookup_any("RLC of RLP-encoding for RLPTable::TxSign", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::TxSignHash, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::Rlp.expr(), + 1.expr(), // tag_index + meta.query_advice(tx_sign_data_rlc, Rotation::cur()), + RlpDataType::TxSign.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + + // lookup Keccak table for tx sign data hash, i.e. the sighash that has to be + // signed. + meta.lookup_any("Keccak table lookup for TxSignHash", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::TxSignHash, Rotation::cur())(meta), + ]); + vec![ + 1.expr(), // is_enabled + meta.query_advice(tx_sign_data_rlc, Rotation::cur()), // input_rlc + meta.query_advice(tx_sign_data_len, Rotation::cur()), // input_len + meta.query_advice(tx_table.value, Rotation::cur()), // output_rlc + ] + .into_iter() + .zip(keccak_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + + // lookup RLP table for length of RLP-encoding of signed tx. + meta.lookup_any("Length of RLP-encoding for RLPTable::TxHash", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::TxHash, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::RlpLength.expr(), + 1.expr(), // tag_index + meta.query_advice(tx_sign_data_len, Rotation::cur()), + RlpDataType::TxHash.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + // lookup RLP table for RLC of RLP-encoding of signed tx. + meta.lookup_any("RLC of RLP-encoding for RLPTable::TxHash", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::TxHash, Rotation::cur())(meta), + ]); + vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + RlpTxTag::Rlp.expr(), + 1.expr(), // tag_index + meta.query_advice(tx_sign_data_rlc, Rotation::cur()), + RlpDataType::TxHash.expr(), + ] + .into_iter() + .zip(rlp_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + + // lookup Keccak table for tx hash + meta.lookup_any("Keccak table lookup for TxHash", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_usable, Rotation::cur()), + tag.value_equals(TxFieldTag::TxHash, Rotation::cur())(meta), + ]); + vec![ + 1.expr(), // is_enabled + meta.query_advice(tx_sign_data_rlc, Rotation::cur()), // input_rlc + meta.query_advice(tx_sign_data_len, Rotation::cur()), // input_len + meta.query_advice(tx_table.value, Rotation::cur()), // output_rlc + ] + .into_iter() + .zip(keccak_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }); + } } /// Tx Circuit for verifying transaction signatures @@ -183,10 +1042,15 @@ impl TxCircuit { config.assign_row( &mut region, offset, - 0, + true, + 0, // tx_id + !assigned_sig_verifs.is_empty() as usize, // tx_id_next TxFieldTag::Null, 0, Value::known(F::zero()), + false, + None, + None, )?; offset += 1; // Assign al Tx fields except for call data @@ -197,24 +1061,21 @@ impl TxCircuit { } else { &tx_default }; + let signed_tx: ethers_core::types::Transaction = tx.into(); + let rlp_signed_tx_be_bytes = signed_tx.rlp().to_vec(); for (tag, value) in [ + (TxFieldTag::Nonce, Value::known(F::from(tx.nonce.as_u64()))), ( - TxFieldTag::Nonce, + TxFieldTag::GasPrice, challenges .evm_word() - .map(|challenge| rlc(tx.nonce.to_le_bytes(), challenge)), + .map(|challenge| rlc(tx.gas_price.to_le_bytes(), challenge)), ), ( TxFieldTag::Gas, Value::known(F::from(tx.gas_limit.as_u64())), ), - ( - TxFieldTag::GasPrice, - challenges - .evm_word() - .map(|challenge| rlc(tx.gas_price.to_le_bytes(), challenge)), - ), ( TxFieldTag::CallerAddress, Value::known(tx.from.to_scalar().expect("tx.from too big")), @@ -251,15 +1112,63 @@ impl TxCircuit { .fold(0, |acc, byte| acc + if *byte == 0 { 4 } else { 16 }), )), ), + (TxFieldTag::SigV, Value::known(F::from(tx.v))), + ( + TxFieldTag::SigR, + challenges + .evm_word() + .map(|challenge| rlc(tx.r.to_le_bytes(), challenge)), + ), + ( + TxFieldTag::SigS, + challenges + .evm_word() + .map(|challenge| rlc(tx.s.to_le_bytes(), challenge)), + ), ( TxFieldTag::TxSignHash, assigned_sig_verif.msg_hash_rlc.value().copied(), ), + ( + TxFieldTag::TxHash, + challenges.evm_word().map(|challenge| { + tx.hash + .to_fixed_bytes() + .into_iter() + .fold(F::zero(), |acc, byte| { + acc * challenge + F::from(byte as u64) + }) + }), + ), ] { - let assigned_cell = - config.assign_row(&mut region, offset, i + 1, tag, 0, value)?; - offset += 1; - + let tx_id_next = match tag { + TxFieldTag::TxHash => { + if i == assigned_sig_verifs.len() - 1 { + self.txs + .iter() + .enumerate() + .find(|(_i, tx)| tx.call_data.len() > 0) + .map(|(i, _tx)| i + 1) + .unwrap_or_else(|| 0) + } else { + i + 2 + } + } + _ => i + 1, + }; + let assigned_cell = config.assign_row( + &mut region, + offset, + true, + i + 1, // tx_id + tx_id_next, // tx_id_next + tag, + 0, + value, + false, + None, + None, + )?; // Ref. spec 0. Copy constraints using fixed offsets between the tx rows and // the SignVerifyChip match tag { @@ -267,27 +1176,99 @@ impl TxCircuit { assigned_cell.cell(), assigned_sig_verif.address.cell(), )?, - TxFieldTag::TxSignHash => region.constrain_equal( - assigned_cell.cell(), - assigned_sig_verif.msg_hash_rlc.cell(), - )?, + TxFieldTag::TxSignHash => { + region.constrain_equal( + assigned_cell.cell(), + assigned_sig_verif.msg_hash_rlc.cell(), + )?; + region.assign_advice( + || "tx_sign_data_len", + config.tx_sign_data_len, + offset, + || Value::known(F::from(assigned_sig_verif.msg_len as u64)), + )?; + region.assign_advice( + || "tx_sign_data_rlc", + config.tx_sign_data_rlc, + offset, + || assigned_sig_verif.msg_rlc, + )?; + } + TxFieldTag::TxHash => { + region.assign_advice( + || "tx_hash_data_len", + config.tx_sign_data_len, + offset, + || Value::known(F::from(rlp_signed_tx_be_bytes.len() as u64)), + )?; + region.assign_advice( + || "tx_hash_data_rlc", + config.tx_sign_data_rlc, + offset, + || { + challenges.keccak_input().map(|challenge| { + rlp_signed_tx_be_bytes + .iter() + .fold(F::zero(), |acc, byte| { + acc * challenge + F::from(*byte as u64) + }) + }) + }, + )?; + } + TxFieldTag::SigV => { + region.assign_advice( + || "chain id", + config.chain_id, + offset, + || Value::known(F::from(self.chain_id)), + )?; + } _ => (), } + + offset += 1; } } // Assign call data let mut calldata_count = 0; for (i, tx) in self.txs.iter().enumerate() { + let mut calldata_gas_cost = 0; + let calldata_length = tx.call_data.len(); for (index, byte) in tx.call_data.0.iter().enumerate() { assert!(calldata_count < self.max_calldata); + let (tx_id_next, is_final) = if index == calldata_length - 1 { + if i == self.txs.len() - 1 { + (0, true) + } else { + ( + self.txs + .iter() + .skip(i + 1) + .enumerate() + .find(|(_, tx)| tx.call_data.len() > 0) + .map(|(j, _)| j + 1) + .unwrap_or_else(|| 0), + true, + ) + } + } else { + (i + 1, false) + }; + calldata_gas_cost += if byte.is_zero() { 4 } else { 16 }; config.assign_row( &mut region, offset, - i + 1, // tx_id + true, + i + 1, // tx_id + tx_id_next, // tx_id_next TxFieldTag::CallData, index, Value::known(F::from(*byte as u64)), + is_final, + Some(calldata_length as u64), + Some(calldata_gas_cost), )?; offset += 1; calldata_count += 1; @@ -297,10 +1278,15 @@ impl TxCircuit { config.assign_row( &mut region, offset, + false, 0, // tx_id + 0, // tx_id_next TxFieldTag::CallData, 0, Value::known(F::zero()), + false, + None, + None, )?; offset += 1; } @@ -450,10 +1436,13 @@ mod tx_circuit_tests { assert_eq!( run::( k, - mock::CORRECT_MOCK_TXS[..NUM_TXS] - .iter() - .map(|tx| Transaction::from(tx.clone())) - .collect_vec(), + [ + mock::CORRECT_MOCK_TXS[1].clone(), + mock::CORRECT_MOCK_TXS[3].clone() + ] + .iter() + .map(|tx| Transaction::from(tx.clone())) + .collect_vec(), mock::MOCK_CHAIN_ID.as_u64(), MAX_TXS, MAX_CALLDATA diff --git a/zkevm-circuits/src/tx_circuit/sign_verify.rs b/zkevm-circuits/src/tx_circuit/sign_verify.rs index b072edf82..7a698bef9 100644 --- a/zkevm-circuits/src/tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/tx_circuit/sign_verify.rs @@ -288,6 +288,8 @@ pub(crate) struct AssignedECDSA { #[derive(Debug)] pub(crate) struct AssignedSignatureVerify { pub(crate) address: AssignedValue, + pub(crate) msg_len: usize, + pub(crate) msg_rlc: Value, pub(crate) msg_hash_rlc: AssignedValue, } @@ -345,6 +347,7 @@ impl SignVerifyChip { let SignData { signature, pk, + msg: _, msg_hash, } = sign_data; let (sig_r, sig_s) = signature; @@ -586,6 +589,10 @@ impl SignVerifyChip { Ok(AssignedSignatureVerify { address, + msg_len: sign_data.msg.len(), + msg_rlc: challenges + .keccak_input() + .map(|r| rlc::value(sign_data.msg.iter().rev(), r)), msg_hash_rlc, }) } @@ -694,8 +701,9 @@ mod sign_verify_tests { plonk::Circuit, }; use pretty_assertions::assert_eq; - use rand::{RngCore, SeedableRng}; + use rand::{Rng, RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; + use sha3::{Digest, Keccak256}; #[derive(Clone, Debug)] struct TestCircuitSignVerifyConfig { @@ -800,6 +808,14 @@ mod sign_verify_tests { secp256k1::Fq::random(rng) } + // Generate a test message. + fn gen_msg(mut rng: impl RngCore) -> Vec { + let msg_len: usize = rng.gen_range(0..128); + let mut msg = vec![0; msg_len]; + rng.fill_bytes(&mut msg); + msg + } + // Returns (r, s) fn sign_with_rng( rng: impl RngCore, @@ -826,11 +842,18 @@ mod sign_verify_tests { let mut signatures = Vec::new(); for _ in 0..NUM_SIGS { let (sk, pk) = gen_key_pair(&mut rng); - let msg_hash = gen_msg_hash(&mut rng); + let msg = gen_msg(&mut rng); + let msg_hash: [u8; 32] = Keccak256::digest(&msg) + .as_slice() + .to_vec() + .try_into() + .expect("hash length isn't 32 bytes"); + let msg_hash = secp256k1::Fq::from_bytes(&msg_hash).unwrap(); let sig = sign_with_rng(&mut rng, sk, msg_hash); signatures.push(SignData { signature: sig, pk, + msg: msg.into(), msg_hash, }); } diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index be9cdb6ac..b5223b126 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -10,12 +10,13 @@ use bus_mapping::{ use eth_types::{Address, Field, ToLittleEndian, ToScalar, Word}; use halo2_proofs::arithmetic::FieldExt; +use halo2_proofs::circuit::Value; use halo2_proofs::halo2curves::bn256::Fr; use itertools::Itertools; use super::{step::step_convert, tx::tx_convert, Bytecode, ExecStep, RwMap, Transaction}; -use crate::util::DEFAULT_RAND; +use crate::util::{Challenges, DEFAULT_RAND}; // TODO: Remove fields that are duplicated in`eth_block` /// Block is the struct used by all circuits, which contains all the needed @@ -107,64 +108,71 @@ impl BlockContext { &self, num_txs: usize, cum_num_txs: usize, - randomness: F, - ) -> Vec<[F; 3]> { + challenges: &Challenges>, + ) -> Vec<[Value; 3]> { let current_block_number = self.number.to_scalar().unwrap(); + let evm_word_rand = challenges.evm_word(); [ vec![ [ - F::from(BlockContextFieldTag::Coinbase as u64), - current_block_number, - self.coinbase.to_scalar().unwrap(), + Value::known(F::from(BlockContextFieldTag::Coinbase as u64)), + Value::known(current_block_number), + Value::known(self.coinbase.to_scalar().unwrap()), ], [ - F::from(BlockContextFieldTag::Timestamp as u64), - current_block_number, - self.timestamp.to_scalar().unwrap(), + Value::known(F::from(BlockContextFieldTag::Timestamp as u64)), + Value::known(current_block_number), + Value::known(self.timestamp.to_scalar().unwrap()), ], [ - F::from(BlockContextFieldTag::Number as u64), - current_block_number, - current_block_number, + Value::known(F::from(BlockContextFieldTag::Number as u64)), + Value::known(current_block_number), + Value::known(current_block_number), ], [ - F::from(BlockContextFieldTag::Difficulty as u64), - current_block_number, - RandomLinearCombination::random_linear_combine( - self.difficulty.to_le_bytes(), - randomness, - ), + Value::known(F::from(BlockContextFieldTag::Difficulty as u64)), + Value::known(current_block_number), + evm_word_rand.map(|rand| { + RandomLinearCombination::random_linear_combine( + self.difficulty.to_le_bytes(), + rand, + ) + }), ], [ - F::from(BlockContextFieldTag::GasLimit as u64), - current_block_number, - F::from(self.gas_limit), + Value::known(F::from(BlockContextFieldTag::GasLimit as u64)), + Value::known(current_block_number), + Value::known(F::from(self.gas_limit)), ], [ - F::from(BlockContextFieldTag::BaseFee as u64), - current_block_number, - RandomLinearCombination::random_linear_combine( - self.base_fee.to_le_bytes(), - randomness, - ), + Value::known(F::from(BlockContextFieldTag::BaseFee as u64)), + Value::known(current_block_number), + evm_word_rand.map(|rand| { + RandomLinearCombination::random_linear_combine( + self.base_fee.to_le_bytes(), + rand, + ) + }), ], [ - F::from(BlockContextFieldTag::ChainId as u64), - current_block_number, - RandomLinearCombination::random_linear_combine( - self.chain_id.to_le_bytes(), - randomness, - ), + Value::known(F::from(BlockContextFieldTag::ChainId as u64)), + Value::known(current_block_number), + evm_word_rand.map(|rand| { + RandomLinearCombination::random_linear_combine( + self.chain_id.to_le_bytes(), + rand, + ) + }), ], [ - F::from(BlockContextFieldTag::NumTxs as u64), - current_block_number, - F::from(num_txs as u64), + Value::known(F::from(BlockContextFieldTag::NumTxs as u64)), + Value::known(current_block_number), + Value::known(F::from(num_txs as u64)), ], [ - F::from(BlockContextFieldTag::CumNumTxs as u64), - current_block_number, - F::from(cum_num_txs as u64), + Value::known(F::from(BlockContextFieldTag::CumNumTxs as u64)), + Value::known(current_block_number), + Value::known(F::from(cum_num_txs as u64)), ], ], { @@ -174,12 +182,14 @@ impl BlockContext { .enumerate() .map(|(idx, hash)| { [ - F::from(BlockContextFieldTag::BlockHash as u64), - (self.number - len_history + idx).to_scalar().unwrap(), - RandomLinearCombination::random_linear_combine( - hash.to_le_bytes(), - randomness, - ), + Value::known(F::from(BlockContextFieldTag::BlockHash as u64)), + Value::known((self.number - len_history + idx).to_scalar().unwrap()), + evm_word_rand.map(|rand| { + RandomLinearCombination::random_linear_combine( + hash.to_le_bytes(), + rand, + ) + }), ] }) .collect() diff --git a/zkevm-circuits/src/witness/tx.rs b/zkevm-circuits/src/witness/tx.rs index a14e15440..9a75a6e25 100644 --- a/zkevm-circuits/src/witness/tx.rs +++ b/zkevm-circuits/src/witness/tx.rs @@ -48,97 +48,112 @@ pub struct Transaction { impl Transaction { /// Assignments for tx table - pub fn table_assignments( + pub fn table_assignments_fixed( &self, challenges: Challenges>, ) -> Vec<[Value; 4]> { - [ - vec![ - [ - Value::known(F::from(self.id as u64)), - Value::known(F::from(TxContextFieldTag::Nonce as u64)), - Value::known(F::zero()), - Value::known(F::from(self.nonce)), - ], - [ - Value::known(F::from(self.id as u64)), - Value::known(F::from(TxContextFieldTag::Gas as u64)), - Value::known(F::zero()), - Value::known(F::from(self.gas)), - ], - [ - Value::known(F::from(self.id as u64)), - Value::known(F::from(TxContextFieldTag::GasPrice as u64)), - Value::known(F::zero()), - challenges.evm_word().map(|evm_word| { - RandomLinearCombination::random_linear_combine( - self.gas_price.to_le_bytes(), - evm_word, - ) - }), - ], - [ - Value::known(F::from(self.id as u64)), - Value::known(F::from(TxContextFieldTag::CallerAddress as u64)), - Value::known(F::zero()), - Value::known(self.caller_address.to_scalar().unwrap()), - ], - [ - Value::known(F::from(self.id as u64)), - Value::known(F::from(TxContextFieldTag::CalleeAddress as u64)), - Value::known(F::zero()), - Value::known(self.callee_address.to_scalar().unwrap()), - ], - [ - Value::known(F::from(self.id as u64)), - Value::known(F::from(TxContextFieldTag::IsCreate as u64)), - Value::known(F::zero()), - Value::known(F::from(self.is_create as u64)), - ], - [ - Value::known(F::from(self.id as u64)), - Value::known(F::from(TxContextFieldTag::Value as u64)), - Value::known(F::zero()), - challenges.evm_word().map(|evm_word| { - RandomLinearCombination::random_linear_combine( - self.value.to_le_bytes(), - evm_word, - ) - }), - ], - [ - Value::known(F::from(self.id as u64)), - Value::known(F::from(TxContextFieldTag::CallDataLength as u64)), - Value::known(F::zero()), - Value::known(F::from(self.call_data_length as u64)), - ], - [ - Value::known(F::from(self.id as u64)), - Value::known(F::from(TxContextFieldTag::CallDataGasCost as u64)), - Value::known(F::zero()), - Value::known(F::from(self.call_data_gas_cost)), - ], - [ - Value::known(F::from(self.id as u64)), - Value::known(F::from(TxContextFieldTag::BlockNumber as u64)), - Value::known(F::zero()), - Value::known(F::from(self.block_number)), - ], + let mut tx_hash_le_bytes = self.hash.to_fixed_bytes(); + tx_hash_le_bytes.reverse(); + + vec![ + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::Nonce as u64)), + Value::known(F::zero()), + Value::known(F::from(self.nonce)), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::Gas as u64)), + Value::known(F::zero()), + Value::known(F::from(self.gas)), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::GasPrice as u64)), + Value::known(F::zero()), + challenges.evm_word().map(|evm_word| { + RandomLinearCombination::random_linear_combine( + self.gas_price.to_le_bytes(), + evm_word, + ) + }), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::CallerAddress as u64)), + Value::known(F::zero()), + Value::known(self.caller_address.to_scalar().unwrap()), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::CalleeAddress as u64)), + Value::known(F::zero()), + Value::known(self.callee_address.to_scalar().unwrap()), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::IsCreate as u64)), + Value::known(F::zero()), + Value::known(F::from(self.is_create as u64)), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::Value as u64)), + Value::known(F::zero()), + challenges.evm_word().map(|evm_word| { + RandomLinearCombination::random_linear_combine( + self.value.to_le_bytes(), + evm_word, + ) + }), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::CallDataLength as u64)), + Value::known(F::zero()), + Value::known(F::from(self.call_data_length as u64)), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::CallDataGasCost as u64)), + Value::known(F::zero()), + Value::known(F::from(self.call_data_gas_cost)), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::TxHash as u64)), + Value::known(F::zero()), + challenges.evm_word().map(|evm_word| { + RandomLinearCombination::random_linear_combine(tx_hash_le_bytes, evm_word) + }), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::BlockNumber as u64)), + Value::known(F::zero()), + Value::known(F::from(self.block_number)), ], - self.call_data - .iter() - .enumerate() - .map(|(idx, byte)| { - [ - Value::known(F::from(self.id as u64)), - Value::known(F::from(TxContextFieldTag::CallData as u64)), - Value::known(F::from(idx as u64)), - Value::known(F::from(*byte as u64)), - ] - }) - .collect(), ] - .concat() + } + + /// Assignments for tx table + pub fn table_assignments_dyn( + &self, + _challenges: Challenges>, + ) -> Vec<[Value; 4]> { + self.call_data + .iter() + .enumerate() + .map(|(idx, byte)| { + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::CallData as u64)), + Value::known(F::from(idx as u64)), + Value::known(F::from(*byte as u64)), + ] + }) + .collect() } }