Skip to content

Commit

Permalink
Public Inputs Circuit (scroll-tech#238)
Browse files Browse the repository at this point in the history
* feat: RLP encoding verification circuit

* fix: compilation and tx rlp encode tests

* fix: refactor redundant columns, is_first and is_last are advices

* fix: assign dummy rows front and back

* feat: assign multiple inputs to rlp circuit

* feat: add tx table lookup from rlp circuit

* fix: lookup for all fields of tx

* fix: calldata rlc lookup

* hash of rlp encoding

* refactor: remove receipt related verification

* fix: remove lookups from rlp circuit

* refactor: separate out rlp table and embed in circuit

* feat: add eip-155 support for unsigned tx

* chore: refactor tag_index into RLP table

* chore: refactor constraints into TxSign and TxHash

* feat: signed tx support

* feat: verify sig_r and sig_s fields

* fix: add missing check for only one tag

* chore: remove unused gadget

* fix: randomness | add rlp table to tx circuit

* feat: tx circuit lookup to rlp table

* feat: configure lookups from tx circuit

* feat: add calldata length and gas cost to rlp table

* fix: lookups from tx circuit and tests

* fix: handle calldata length == 0 case in lookups

* fix: account for chainid and 0, 0

* fix: clippy

* fix: constraints for tx_id

* fix: additional constraints around last row

* chore: rename rlp circuit based on #650

* fix: calldatalength and gas cost in tx circuit

* feat: RLP encoding verification circuit

* fix: compilation and tx rlp encode tests

* fix: refactor redundant columns, is_first and is_last are advices

* fix: assign dummy rows front and back

* feat: assign multiple inputs to rlp circuit

* feat: add tx table lookup from rlp circuit

* fix: lookup for all fields of tx

* fix: calldata rlc lookup

* hash of rlp encoding

* refactor: remove receipt related verification

* fix: remove lookups from rlp circuit

* refactor: separate out rlp table and embed in circuit

* feat: add eip-155 support for unsigned tx

* chore: refactor tag_index into RLP table

* chore: refactor constraints into TxSign and TxHash

* feat: signed tx support

* feat: verify sig_r and sig_s fields

* fix: add missing check for only one tag

* chore: remove unused gadget

* fix: randomness | add rlp table to tx circuit

* fix: next tx id assignment

* fix: lookup for call data bytes

* fix: make assignments to tx table

* feat: lookups for msg len/rlc and sig fields

* fix: add constraint for tx_id increment at nonce row

* feat: tag equality check

* fix: resolve issues after merging

* feat: lookup to check that call data bytes exist in tx table

* rewrite pi circuit to use rlp-based approach

* pi circuit assignment, clippy fixes

* lookup to rlp table for tx hash in tx circuit

* chore: clippy fix

* add copy constraints between pi and block/tx table

* add lookup to keccak for final public input in pi circuit

* chore: clippy fix

* fix: compilation after update against upstream

* feat: updates to RLP circuit/table

* fix: compilation

* chore: clippy fix

* refactor pi circuit to use challenge api and expose keccak (hi,lo)

* refactor pi circuit to use challenge api and change pi to keccak hi&lo

* refactor witness of rlp circuit to use value api

* refactor rlp_circuit to use single set of constraints to handle tags

* fix clippy errors

* fix

* add padding constraints

* reduce degree to 9

* fix

* add rlp_circuit to super_circuit

* disable lt/cmp chips for padding rows to reduce witness assign time

* fix: get chain_id from block header

* fix

* fix clippy error

* skip tx without sigs

* fmt

* skip tx/block table load in pi_circuit's synthesize_sub

* add max_inner_blocks

* fix clippy

* feat: padding blocks will not increase keccak input's size

* add TODO in pi circuit

* enable pi circuit in super_circuit

Co-authored-by: Rohit Narurkar <[email protected]>
Co-authored-by: kunxian-xia <[email protected]>
  • Loading branch information
3 people authored Jan 7, 2023
1 parent d0acd9d commit 7bb4a39
Show file tree
Hide file tree
Showing 20 changed files with 2,287 additions and 1,583 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bus-mapping/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
136 changes: 131 additions & 5 deletions bus-mapping/src/circuit_input_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
}
Expand Down Expand Up @@ -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),
)
Expand Down Expand Up @@ -425,6 +433,13 @@ pub fn keccak_inputs(block: &Block, code_db: &CodeDB) -> Result<Vec<Vec<u8>>, Er
"keccak total len after txs: {}",
keccak_inputs.iter().map(|i| i.len()).sum::<usize>()
);
// 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());
Expand Down Expand Up @@ -452,6 +467,7 @@ pub fn keccak_inputs_sign_verify(sigs: &[SignData]) -> Vec<Vec<u8>> {
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);
Expand All @@ -460,12 +476,122 @@ pub fn keccak_inputs_sign_verify(sigs: &[SignData]) -> Vec<Vec<u8>> {
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<u64, BlockHead>,
transactions: &[Transaction],
max_txs: usize,
) -> Vec<u8> {
// 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::<Vec<u8>>();

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<Vec<Vec<u8>>, 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::<Vec<Vec<u8>>>();
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<SignData> = txs
.iter()
.enumerate()
Expand Down
52 changes: 23 additions & 29 deletions circuit-benchmarks/src/pi_circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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]
Expand All @@ -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::<MAX_TXS, MAX_CALLDATA>();
let circuit = PiTestCircuit::<Fr, MAX_TXS, MAX_CALLDATA>(PiCircuit::<Fr>::new(
MAX_TXS,
MAX_CALLDATA,
randomness,
rand_rpi,
public_data,
));
let block = generate_block::<MAX_TXS, MAX_CALLDATA>();
let circuit = PiTestCircuit::<Fr, MAX_TXS, MAX_CALLDATA, MAX_INNER_BLOCKS>(
PiCircuit::<Fr>::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[..]][..];
Expand Down Expand Up @@ -76,7 +71,7 @@ mod tests {
Challenge255<G1Affine>,
XorShiftRng,
Blake2bWrite<Vec<u8>, G1Affine, Challenge255<G1Affine>>,
PiTestCircuit<Fr, MAX_TXS, MAX_CALLDATA>,
PiTestCircuit<Fr, MAX_TXS, MAX_CALLDATA, MAX_INNER_BLOCKS>,
>(
&general_params,
&pk,
Expand Down Expand Up @@ -111,17 +106,16 @@ mod tests {
end_timer!(start3);
}

fn generate_publicdata<const MAX_TXS: usize, const MAX_CALLDATA: usize>() -> 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<const MAX_TXS: usize, const MAX_CALLDATA: usize>() -> Block<Fr> {
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()
}
}
4 changes: 2 additions & 2 deletions circuit-benchmarks/src/super_circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -96,7 +96,7 @@ mod tests {
Challenge255<G1Affine>,
ChaChaRng,
Blake2bWrite<Vec<u8>, G1Affine, Challenge255<G1Affine>>,
SuperCircuit<Fr, 1, 32, 512>,
SuperCircuit<Fr, 1, 32, 64, 512>,
>(
&general_params,
&pk,
Expand Down
10 changes: 8 additions & 2 deletions eth-types/src/geth_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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()
}
}
Expand All @@ -168,6 +172,7 @@ impl From<&crate::Transaction> for Transaction {
v: tx.v.as_u64(),
r: tx.r,
s: tx.s,
hash: tx.hash,
}
}
}
Expand Down Expand Up @@ -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)
Expand All @@ -219,6 +224,7 @@ impl Transaction {
Ok(SignData {
signature: (sig_r, sig_s),
pk,
msg,
msg_hash,
})
}
Expand Down
Loading

0 comments on commit 7bb4a39

Please sign in to comment.