├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── rust-toolchain ├── src ├── cli.rs ├── constants.rs ├── env.rs ├── error.rs ├── lib.rs ├── main.rs ├── utils │ ├── anvil │ │ ├── client.rs │ │ ├── conversion.rs │ │ ├── mod.rs │ │ └── types.rs │ ├── etherscan.rs │ ├── geth.rs │ ├── halo2 │ │ ├── helpers.rs │ │ ├── mod.rs │ │ ├── proof.rs │ │ ├── real_prover.rs │ │ ├── real_verifier.rs │ │ └── srs.rs │ ├── helpers.rs │ ├── huff.rs │ ├── ipfs.rs │ ├── mod.rs │ ├── scaffold.rs │ └── solidity.rs ├── verification.rs ├── wasm.rs └── witness │ ├── inputs_builder.rs │ └── mod.rs └── wasm_build.sh /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: ["dev"] 6 | pull_request: 7 | branches: ["dev"] 8 | 9 | # env: 10 | # CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | nowasm-build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: nowasm-build 18 | run: cargo build 19 | wasm-build: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v3 23 | - uses: actions-rust-lang/setup-rust-toolchain@v1 24 | with: 25 | components: rust-src 26 | - uses: jetli/wasm-pack-action@v0.4.0 27 | with: 28 | version: 'latest' 29 | - name: wasm-build 30 | run: ./wasm_build.sh 31 | fmt: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v3 35 | - uses: actions-rust-lang/setup-rust-toolchain@v1 36 | with: 37 | components: rustfmt 38 | - name: fmt 39 | run: cargo fmt 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | rw.txt 3 | out/ 4 | out2/ 5 | temp 6 | pure-out/ 7 | pure-out2/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proof-of-exploit" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "exploit" 8 | path = "src/main.rs" 9 | 10 | [lib] 11 | crate-type = ["cdylib", "rlib"] 12 | 13 | [dependencies] 14 | # anvil 15 | anvil = { git = "https://github.com/foundry-rs/foundry.git", package = "anvil", rev = "dea5405", optional = true } 16 | anvil-core = { git = "https://github.com/foundry-rs/foundry.git", package = "anvil-core", rev = "dea5405", optional = true } 17 | ethers = { git = "https://github.com/gakonst/ethers-rs", optional = true } 18 | partial-mpt = { git = "https://github.com/zemse/partial-mpt", branch = "main", optional = true } 19 | 20 | # zkevm 21 | bus-mapping = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits", branch = "main", default-features = false, optional = true } 22 | eth-types = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits", branch = "main", optional = true } 23 | zkevm-circuits = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits", branch = "main", default-features = false, optional = true } 24 | halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2023_04_20" } 25 | snark-verifier = { git = "https://github.com/privacy-scaling-explorations/snark-verifier.git", rev = "a440ff91", optional = true } 26 | ethers-core = { version = "2.0.7", optional = true } 27 | itertools = { version = "0.10", optional = true } 28 | rand_chacha = { version = "0.3", optional = true } 29 | 30 | # misc 31 | tokio = { version = "1.13", features = ["macros"], optional = true } 32 | futures = { version = "0.3.*", optional = true } 33 | dotenv = { version = "0.15.0", optional = true } 34 | bytes = { version = "1.1.0", optional = true } 35 | clap = { version = "4.0", features = ["derive", "cargo"], optional = true } 36 | serde = { version = "1.0.188", optional = true } 37 | serde_json = { version = "1.0.107", optional = true } 38 | regex = { version = "1.9.5", optional = true } 39 | reqwest = { version = "0.11", optional = true } 40 | pinata-sdk = { version = "1.1.0", optional = true } 41 | svm-rs = { version = "0.3.3", optional = true } 42 | semver = { version = "1.0", features = ["serde"], optional = true } 43 | home = { version = "0.5.5", optional = true } 44 | 45 | # wasm 46 | js-sys = { version = "0.3", optional = true } 47 | wasm-bindgen = { version = "0.2.84", features = ["serde-serialize"], optional = true } 48 | rayon = { version = "1.5", optional = true } 49 | wasm-bindgen-rayon = { version = "1.0", optional = true } 50 | web-sys = { version = "0.3", features = ["Request", "Window", "Response"], optional = true } 51 | wasm-bindgen-futures = { version = "0.4", optional = true } 52 | console_error_panic_hook = { version = "0.1.7", optional = true } 53 | 54 | [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] 55 | getrandom = { version = "0.2", features = ["js"], optional = true } 56 | 57 | [patch."https://github.com/privacy-scaling-explorations/zkevm-circuits"] 58 | bus-mapping = { git = "https://github.com/proof-of-exploit/zkevm-circuits", rev = "16507249", default-features = false, optional = true } 59 | eth-types = { git = "https://github.com/proof-of-exploit/zkevm-circuits", rev = "16507249", optional = true } 60 | zkevm-circuits = { git = "https://github.com/proof-of-exploit/zkevm-circuits", rev = "16507249", default-features = false, optional = true } 61 | 62 | # for local development only 63 | # [patch."https://github.com/privacy-scaling-explorations/zkevm-circuits"] 64 | # bus-mapping = { path = "../zkevm-circuits/bus-mapping", default-features = false, optional = true } 65 | # eth-types = { path = "../zkevm-circuits/eth-types", optional = true } 66 | # zkevm-circuits = { path = "../zkevm-circuits/zkevm-circuits", default-features = false } 67 | 68 | [features] 69 | default = ["nowasm"] 70 | nowasm = [ 71 | "bus-mapping/nowasm", 72 | "zkevm-circuits/nowasm", 73 | "anvil", 74 | "anvil-core", 75 | "ethers", 76 | "partial-mpt", 77 | "eth-types", 78 | "snark-verifier", 79 | "ethers-core", 80 | "itertools", 81 | "rand_chacha", 82 | "tokio", 83 | "futures", 84 | "dotenv", 85 | "clap", 86 | "serde", 87 | "serde_json", 88 | "regex", 89 | "reqwest", 90 | "pinata-sdk", 91 | "svm-rs", 92 | "semver", 93 | "home", 94 | ] 95 | wasm = [ 96 | "zkevm-circuits/wasm", 97 | "bus-mapping/wasm", 98 | "getrandom", 99 | "wasm-bindgen", 100 | "console_error_panic_hook", 101 | "rayon", 102 | "wasm-bindgen-rayon", 103 | "web-sys", 104 | "wasm-bindgen-futures", 105 | "js-sys", 106 | ] 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > This repository is archived due to the halted development of a crucial dependency [PSE/zkevm-circuits](https://github.com/privacy-scaling-explorations/zkevm-circuits). 2 | 3 | # Proof of Exploit CLI 4 | 5 | Security researchers can prove that a smart contract can be exploited without revealing the bug. 6 | 7 | Bug bounty managers receiving lot of bug reports can easily screen bug reports. 8 | 9 | For example, this [repository](https://github.com/zemse/proof-of-exploit-huff-template) demonstrates exploiting a re-entrancy vulnerability and here is it's [proof of exploit link](https://proofofexplo.it/verify/Qmek2Mo43HgFn3B6kjMHXBLznqbxyiyxMbTV9sYbJ4oKwE). 10 | 11 | ## Technical details 12 | 13 | This project depends on: 14 | 15 | - [fork of PSE/zkevm-circuits](https://github.com/proof-of-exploit/zkevm-circuits) for the zk stuff. 16 | - [anvil](https://github.com/foundry-rs/foundry/tree/master/anvil) for spawning in-memory mainnet fork chain. 17 | 18 | A block is locally mined containing the transaction which solves challenge. This block is used as a witness to the [SuperCircuit](https://github.com/privacy-scaling-explorations/zkevm-circuits/blob/7e9603a28a818819c071c81fd2f4f6b58737dea6/zkevm-circuits/src/super_circuit.rs#L270). 19 | 20 | The transaction is expected to flip a slot in the `Challenge` contract. 21 | 22 | ```solidity 23 | contract Challenge { 24 | bool isSolved; 25 | 26 | function entryPoint() public returns (bool) { 27 | // arbitrary EVM code 28 | 29 | isSolved = true; 30 | } 31 | } 32 | ``` 33 | 34 | The challenge contract codehash is revealed in the public inputs of the zksnark. 35 | 36 | ## Installation 37 | 38 | To install the `exploit` binary you can clone this repository and run the following command: 39 | 40 | ``` 41 | cargo install --locked --path . 42 | ``` 43 | 44 | Note: the `--locked` is important 45 | 46 | Installation on a fresh Ubuntu 22 instance: 47 | 48 | ```shell 49 | # install libs 50 | sudo apt-get update 51 | sudo apt-get install gcc libssl-dev pkg-config 52 | # install rust and cargo 53 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 54 | source "$HOME/.cargo/env" 55 | # install proof-of-exploit cli 56 | git clone https://github.com/proof-of-exploit/cli proof-of-exploit-cli 57 | cd proof-of-exploit-cli 58 | cargo install --locked --path . 59 | ``` 60 | 61 | 62 | ## Usage 63 | 64 | This project creates a binary called `exploit`. 65 | 66 | ``` 67 | $ exploit --help 68 | 69 | Usage: exploit [COMMAND] 70 | 71 | Commands: 72 | test Test the exploit 73 | prove Generate proofs 74 | verify Verify proofs 75 | help Print this message or the help of the given subcommand(s) 76 | 77 | Options: 78 | -h, --help Print help 79 | -V, --version Print version 80 | ``` 81 | 82 | ### Proving 83 | 84 | For generating a zk proof, the `prove` subcommand can be used. 85 | 86 | ``` 87 | $ exploit prove 88 | ``` 89 | 90 | - `Challenge` contract will be public and included in the proof. 91 | - `Exploit` contract will not be revealed. 92 | - Generating proof requires lot of memory (200G+). 93 | 94 | ### Testing exploit 95 | 96 | During writing the exploit if needed to check if the exploit is working properly, the `test` subcommand can be used and it is exactly same as the `prove`. 97 | 98 | ``` 99 | $ exploit test --rpc https://eth-sepolia.g.alchemy.com/v2/ --block 4405541 \ 100 | --challenge src/Challenge.sol --exploit src/Exploit.huff 101 | 102 | anvil initialized - chain_id: 11155111, block_number: 4405541 103 | transaction gas: 97945 104 | test passed 105 | ``` 106 | 107 | ### Verification 108 | 109 | ``` 110 | $ exploit verify --proof Qmek2Mo43HgFn3B6kjMHXBLznqbxyiyxMbTV9sYbJ4oKwE 111 | Proof verification success! 112 | 113 | Public Inputs: 114 | Chain Id: 11155111 115 | Block: 4814850 https://sepolia.etherscan.io/block/4814850 116 | State Root: 0x17a4764598b67b7c6fb327e9ae56693b641606850b1a28758b6c28b2a3381ce3 117 | Challenge Codehash: 0x11864e842a04f15016579a7e3f747a18e7dc6eb8c817789bb02be4f94a19d18c 118 | 119 | To view challenge source code, use --unpack flag. 120 | ``` 121 | 122 | ### Verification on website 123 | 124 | For the ease of use for the bug bounty manager, a website can be used to verify the proofs. 125 | 126 | https://proofofexplo.it/verify/Qmek2Mo43HgFn3B6kjMHXBLznqbxyiyxMbTV9sYbJ4oKwE 127 | 128 | The project is compiled into WASM using the `wasm_build.sh` script. 129 | 130 | ## Credits 131 | 132 | Thanks to [Privacy and Scaling Explorations](http://github.com/privacy-scaling-explorations) for supporting this project. 133 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2023-04-24 -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | env::Env, 3 | utils::{ 4 | anvil::types::zkevm_types::Bytes, halo2::proof::Proof, huff::compile_huff, ipfs, solidity, 5 | }, 6 | }; 7 | use clap::{arg, command, ArgMatches, Command}; 8 | use eth_types::U256; 9 | use ethers::utils::parse_ether; 10 | use home::home_dir; 11 | use std::{fs::create_dir_all, path::PathBuf, str::FromStr}; 12 | 13 | pub const EXPLOIT: &str = "exploit"; 14 | pub const TEST: &str = "test"; 15 | pub const PROVE: &str = "prove"; 16 | pub const VERIFY: &str = "verify"; 17 | pub const PUBLISH: &str = "publish"; 18 | pub const SCAFFOLD: &str = "scaffold"; 19 | 20 | pub fn exploit_command() -> Command { 21 | command!(EXPLOIT) 22 | .name("Proof of Exploit") 23 | .about("Generate and verify zk proof of exploits for ethereum smart contracts") 24 | .version("v0.1.0") 25 | .after_help("Find more information at https://github.com/zemse/proof-of-exploit") 26 | .subcommands([ 27 | ProveArgs::apply(command!(TEST)).about("Test the exploit using MockProver (~15G RAM)"), 28 | ProveArgs::apply(command!(PROVE)).about("Generate proof using RealProver (200G+ RAM)"), 29 | VerifyArgs::apply(command!(VERIFY)).about("Verify zk proofs"), 30 | PublishArgs::apply(command!(PUBLISH)).about("Publish proof to IPFS"), 31 | ScaffoldArgs::apply(command!(SCAFFOLD)) 32 | .about("Scaffold new project for writing exploit"), 33 | ]) 34 | .subcommand_required(true) 35 | } 36 | 37 | pub struct ProveArgs { 38 | pub rpc: String, 39 | pub geth_rpc: Option, 40 | pub block: Option, 41 | pub challenge_artifact: solidity::Artifact, 42 | pub exploit_bytecode: Bytes, 43 | pub exploit_balance: U256, 44 | pub gas: Option, 45 | pub srs_path: PathBuf, 46 | pub proof_out_path: Option, 47 | pub ipfs: bool, 48 | pub max_rws: usize, 49 | pub max_copy_rows: usize, 50 | pub max_exp_steps: usize, 51 | pub max_bytecode: usize, 52 | pub max_evm_rows: usize, 53 | pub max_keccak_rows: usize, 54 | } 55 | 56 | impl ProveArgs { 57 | pub fn apply(c: clap::Command) -> clap::Command { 58 | c.arg(arg!(--rpc "Enter ethereum archive node RPC url" )) 59 | .arg(arg!(--"geth-rpc" "Use geth node for generating traces" )) 60 | .arg(arg!(--block "Enter the fork block number" )) 61 | .arg(arg!(--challenge "Enter hex bytecode or file path" )) 62 | .arg(arg!(--exploit "Enter hex bytecode or file path" )) 63 | .arg(arg!(--"exploit-balance" "Enter ether amount to fund 0xbada55 address" )) 64 | .arg(arg!(--gas "Enter amount of gas for exploit tx" )) 65 | .arg(arg!(--srs "Enter the dir for srs params" )) 66 | .arg(arg!(--out "Path for output proof.json file" )) 67 | .arg(arg!(--ipfs "Publish the proof to IPFS" )) 68 | .arg(arg!(--"max-rws" )) 69 | .arg(arg!(--"max-copy-rows" )) 70 | .arg(arg!(--"max-exp-steps" )) 71 | .arg(arg!(--"max-bytecode" )) 72 | .arg(arg!(--"max-evm-rows" )) 73 | .arg(arg!(--"max-keccak-rows" )) 74 | } 75 | 76 | pub fn from(arg_matches: Option<&ArgMatches>, env: &Env) -> Self { 77 | let arg_matches = arg_matches.unwrap(); 78 | let rpc = parse_optional(arg_matches, "rpc") 79 | .or(env.eth_rpc_url.clone()) 80 | .expect("please provide --rpc or ETH_RPC_URL"); 81 | let geth_rpc = parse_optional(arg_matches, "geth-rpc").or(env.geth_rpc_url.clone()); 82 | let block = parse_optional(arg_matches, "block").or(env.fork_block_number); 83 | let challenge_input = parse_optional(arg_matches, "challenge") 84 | .or(env.challenge_path.clone()) 85 | .unwrap_or("./src/Challenge.sol".to_string()); 86 | let challenge_artifact = solidity::Artifact::from_source(challenge_input); 87 | let exploit_input = parse_optional(arg_matches, "exploit") 88 | .or(env.exploit_path.clone()) 89 | .unwrap_or("./src/Exploit.huff".to_string()); 90 | let exploit_bytecode = compile_huff(exploit_input); 91 | let exploit_balance = parse_ether( 92 | parse_optional(arg_matches, "exploit-balance") 93 | .or(env.exploit_balance.clone()) 94 | .unwrap_or("0".to_string()), 95 | ) 96 | .expect("please provide ether amount correctly for --exploit-balance"); 97 | let gas = parse_optional(arg_matches, "gas"); 98 | let srs_path = parse_srs_path(arg_matches, env); 99 | let proof_out_path = parse_optional(arg_matches, "out"); 100 | let ipfs = arg_matches.get_flag("ipfs"); 101 | let max_rws = parse_optional(arg_matches, "max-rws").unwrap_or(env.max_rws.unwrap_or(1000)); 102 | let max_copy_rows = parse_optional(arg_matches, "max-copy-rows") 103 | .unwrap_or(env.max_copy_rows.unwrap_or(1000)); 104 | let max_exp_steps = parse_optional(arg_matches, "max-exp-steps") 105 | .unwrap_or(env.max_exp_steps.unwrap_or(1000)); 106 | let max_bytecode = 107 | parse_optional(arg_matches, "max-bytecode").unwrap_or(env.max_bytecode.unwrap_or(512)); 108 | let max_evm_rows = 109 | parse_optional(arg_matches, "max-evm-rows").unwrap_or(env.max_evm_rows.unwrap_or(1000)); 110 | let max_keccak_rows = parse_optional(arg_matches, "max-keccak-rows") 111 | .unwrap_or(env.max_keccak_rows.unwrap_or(1000)); 112 | 113 | Self { 114 | rpc, 115 | geth_rpc, 116 | block, 117 | challenge_artifact, 118 | exploit_bytecode, 119 | exploit_balance, 120 | gas, 121 | srs_path, 122 | proof_out_path, 123 | ipfs, 124 | max_rws, 125 | max_copy_rows, 126 | max_exp_steps, 127 | max_bytecode, 128 | max_evm_rows, 129 | max_keccak_rows, 130 | } 131 | } 132 | } 133 | 134 | pub struct VerifyArgs { 135 | pub srs_path: PathBuf, 136 | pub proof: Proof, 137 | pub unpack_dir: Option, 138 | } 139 | 140 | impl VerifyArgs { 141 | pub fn apply(c: clap::Command) -> clap::Command { 142 | c.arg(arg!(--srs "Enter the path for storing SRS parameters" )) 143 | .arg(arg!(--proof "Enter the proof path or IPFS hash" )) 144 | .arg(arg!(--unpack "Enter path to unpack challenge solidity code" )) 145 | } 146 | 147 | pub async fn from(arg_matches: Option<&ArgMatches>, env: &Env) -> Self { 148 | let arg_matches = arg_matches.unwrap(); 149 | let srs_path = parse_srs_path(arg_matches, env); 150 | 151 | let proof_input: String = parse_optional(arg_matches, "proof") 152 | .expect("please provide the path to proof json file using --proof"); 153 | let proof = if let Ok(proof) = 154 | Proof::read_from_file(&PathBuf::from_str(proof_input.as_str()).unwrap()) 155 | { 156 | proof 157 | } else { 158 | ipfs::get(proof_input).await.unwrap() 159 | }; 160 | 161 | let unpack_dir: Option = parse_optional(arg_matches, "unpack"); 162 | 163 | Self { 164 | srs_path, 165 | proof, 166 | unpack_dir, 167 | } 168 | } 169 | } 170 | 171 | pub struct PublishArgs { 172 | pub file_path: String, 173 | } 174 | 175 | impl PublishArgs { 176 | pub fn apply(c: clap::Command) -> clap::Command { 177 | c.arg(arg!(--file "Enter the file path" )) 178 | } 179 | 180 | pub fn from(arg_matches: Option<&ArgMatches>) -> Self { 181 | let arg_matches = arg_matches.unwrap(); 182 | let file_path: String = parse_optional(arg_matches, "file") 183 | .expect("please provide the path to file using --file"); 184 | Self { file_path } 185 | } 186 | } 187 | 188 | pub struct ScaffoldArgs { 189 | pub project_name: String, 190 | } 191 | 192 | impl ScaffoldArgs { 193 | pub fn apply(c: clap::Command) -> clap::Command { 194 | c.arg(arg!(--name "Enter project name")) 195 | .arg_required_else_help(true) 196 | } 197 | 198 | pub fn from(arg_matches: Option<&ArgMatches>) -> Self { 199 | let arg_matches = arg_matches.unwrap(); 200 | let project_name = 201 | parse_optional(arg_matches, "name").expect("please provide project name using --name"); 202 | Self { project_name } 203 | } 204 | } 205 | 206 | fn parse_srs_path(arg_matches: &ArgMatches, env: &Env) -> PathBuf { 207 | let srs_input = parse_optional(arg_matches, "srs").or(env.srs_path.clone()); 208 | let srs_path = if let Some(srs_input) = srs_input { 209 | PathBuf::from(srs_input) 210 | } else { 211 | home_dir().unwrap().join(".proof-of-exploit-srs") 212 | }; 213 | create_dir_all(srs_path.clone()).unwrap(); 214 | srs_path 215 | } 216 | 217 | pub fn parse_optional(am: &ArgMatches, id: &str) -> Option 218 | where 219 | ::Err: std::fmt::Debug, 220 | { 221 | am.get_one::(id).map(|val| val.parse().unwrap()) 222 | } 223 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const MAX_TXS: usize = 1; 2 | pub const MAX_CALLDATA: usize = 256; 3 | pub const RANDOMNESS: u64 = 0x100; 4 | pub use bus_mapping::{POX_CHALLENGE_ADDRESS, POX_EXPLOIT_ADDRESS}; 5 | -------------------------------------------------------------------------------- /src/env.rs: -------------------------------------------------------------------------------- 1 | use dotenv::dotenv; 2 | use ethers::types::U64; 3 | use std::env; 4 | 5 | #[allow(dead_code)] 6 | pub struct Env { 7 | pub eth_rpc_url: Option, 8 | pub fork_block_number: Option, 9 | pub geth_rpc_url: Option, 10 | pub challenge_path: Option, 11 | pub exploit_path: Option, 12 | pub exploit_balance: Option, 13 | pub srs_path: Option, 14 | pub max_rws: Option, 15 | pub max_copy_rows: Option, 16 | pub max_exp_steps: Option, 17 | pub max_bytecode: Option, 18 | pub max_evm_rows: Option, 19 | pub max_keccak_rows: Option, 20 | } 21 | 22 | #[allow(dead_code)] 23 | impl Env { 24 | pub fn load() -> Env { 25 | dotenv().ok(); 26 | // TODO refactor - remove duplicate code 27 | 28 | // anvil params 29 | let eth_rpc_url = match env::var("ETH_RPC_URL") { 30 | Ok(val) => Some(val), 31 | Err(_) => None, 32 | }; 33 | let fork_block_number = match env::var("FORK_BLOCK_NUMBER") { 34 | Ok(val) => Some(U64::from_str_radix(&val, 10).unwrap().as_usize()), 35 | Err(_) => None, 36 | }; 37 | 38 | // temp params 39 | let geth_rpc_url = match env::var("GETH_RPC_URL") { 40 | Ok(val) => Some(val), 41 | Err(_) => None, 42 | }; 43 | 44 | // PoX params 45 | let challenge_path = match env::var("CHALLENGE") { 46 | Ok(val) => Some(val), 47 | Err(_) => match env::var("CHALLENGE_PATH") { 48 | Ok(val) => Some(val), 49 | Err(_) => None, 50 | }, 51 | }; 52 | let exploit_path = match env::var("EXPLOIT") { 53 | Ok(val) => Some(val), 54 | Err(_) => match env::var("EXPLOIT_PATH") { 55 | Ok(val) => Some(val), 56 | Err(_) => None, 57 | }, 58 | }; 59 | let exploit_balance = match env::var("EXPLOIT_BALANCE") { 60 | Ok(val) => Some(val), 61 | Err(_) => None, 62 | }; 63 | 64 | // zkEVM params 65 | let srs_path = match env::var("SRS") { 66 | Ok(val) => Some(val), 67 | Err(_) => match env::var("SRS_PATH") { 68 | Ok(val) => Some(val), 69 | Err(_) => None, 70 | }, 71 | }; 72 | let max_rws = match env::var("MAX_ROWS") { 73 | Ok(val) => Some(U64::from_str_radix(&val, 10).unwrap().as_usize()), 74 | Err(_) => None, 75 | }; 76 | let max_copy_rows = match env::var("MAX_COPY_ROWS") { 77 | Ok(val) => Some(U64::from_str_radix(&val, 10).unwrap().as_usize()), 78 | Err(_) => None, 79 | }; 80 | let max_exp_steps = match env::var("MAX_EXP_ROWS") { 81 | Ok(val) => Some(U64::from_str_radix(&val, 10).unwrap().as_usize()), 82 | Err(_) => None, 83 | }; 84 | let max_bytecode = match env::var("MAX_BYTECODE") { 85 | Ok(val) => Some(U64::from_str_radix(&val, 10).unwrap().as_usize()), 86 | Err(_) => None, 87 | }; 88 | let max_evm_rows = match env::var("MAX_EVM_ROWS") { 89 | Ok(val) => Some(U64::from_str_radix(&val, 10).unwrap().as_usize()), 90 | Err(_) => None, 91 | }; 92 | let max_keccak_rows = match env::var("MAX_KECCAK_ROWS") { 93 | Ok(val) => Some(U64::from_str_radix(&val, 10).unwrap().as_usize()), 94 | Err(_) => None, 95 | }; 96 | 97 | Env { 98 | eth_rpc_url, 99 | fork_block_number, 100 | geth_rpc_url, 101 | challenge_path, 102 | exploit_path, 103 | exploit_balance, 104 | srs_path, 105 | max_rws, 106 | max_copy_rows, 107 | max_exp_steps, 108 | max_bytecode, 109 | max_evm_rows, 110 | max_keccak_rows, 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use anvil::eth::error::BlockchainError; 2 | use ethers_core::utils::rlp; 3 | use halo2_proofs::plonk; 4 | use partial_mpt; 5 | 6 | #[allow(dead_code)] 7 | #[derive(Debug)] 8 | pub enum Error { 9 | AnvilError(Box), 10 | RlpDecoderError(Box), 11 | BusMappingError(Box), 12 | Halo2Error(Box), 13 | StdError(Box), 14 | PartialMptError(Box), 15 | SerdeJsonError(Box), 16 | PinataError(Box), 17 | EthersProviderError(Box), 18 | InternalError(&'static str), 19 | } 20 | 21 | impl From for Error { 22 | fn from(err: BlockchainError) -> Self { 23 | Error::AnvilError(Box::new(err)) 24 | } 25 | } 26 | 27 | impl From for Error { 28 | fn from(err: bus_mapping::Error) -> Self { 29 | Error::BusMappingError(Box::new(err)) 30 | } 31 | } 32 | 33 | impl From for Error { 34 | fn from(err: rlp::DecoderError) -> Self { 35 | Error::RlpDecoderError(Box::new(err)) 36 | } 37 | } 38 | 39 | impl From for Error { 40 | fn from(err: plonk::Error) -> Self { 41 | Error::Halo2Error(Box::new(err)) 42 | } 43 | } 44 | 45 | impl From for Error { 46 | fn from(err: partial_mpt::Error) -> Self { 47 | Error::PartialMptError(Box::new(err)) 48 | } 49 | } 50 | 51 | impl From for Error { 52 | fn from(err: std::io::Error) -> Self { 53 | Error::StdError(Box::new(err)) 54 | } 55 | } 56 | 57 | impl From for Error { 58 | fn from(err: serde_json::Error) -> Self { 59 | Error::SerdeJsonError(Box::new(err)) 60 | } 61 | } 62 | 63 | impl From for Error { 64 | fn from(err: ethers::providers::ProviderError) -> Self { 65 | Error::EthersProviderError(Box::new(err)) 66 | } 67 | } 68 | 69 | impl From for Error { 70 | fn from(err: pinata_sdk::ApiError) -> Self { 71 | Error::PinataError(Box::new(err)) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(let_chains)] 2 | #![feature(slice_pattern)] 3 | 4 | #[cfg(all(feature = "wasm", feature = "nowasm"))] 5 | compile_error!( 6 | "proof-of-exploit: both wasm & nowasm are enabled, just one of them must be enabled" 7 | ); 8 | #[cfg(all(not(feature = "wasm"), not(feature = "nowasm")))] 9 | compile_error!("proof-of-exploit: none of wasm & nowasm are enabled, one of them must be enabled"); 10 | 11 | #[cfg(not(feature = "wasm"))] 12 | pub mod cli; 13 | #[cfg(not(feature = "wasm"))] 14 | pub mod constants; 15 | #[cfg(not(feature = "wasm"))] 16 | pub mod env; 17 | #[cfg(not(feature = "wasm"))] 18 | pub mod error; 19 | #[cfg(not(feature = "wasm"))] 20 | pub mod utils; 21 | #[cfg(not(feature = "wasm"))] 22 | pub mod verification; 23 | #[cfg(not(feature = "wasm"))] 24 | pub mod witness; 25 | 26 | #[cfg(feature = "wasm")] 27 | pub mod wasm; 28 | #[cfg(feature = "wasm")] 29 | pub use wasm::*; 30 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "dep_wasm"))] 2 | use proof_of_exploit::{ 3 | cli::{ 4 | exploit_command, ProveArgs, PublishArgs, ScaffoldArgs, VerifyArgs, PROVE, PUBLISH, 5 | SCAFFOLD, TEST, VERIFY, 6 | }, 7 | env::Env, 8 | utils::{ipfs, scaffold}, 9 | verification::handle_verify, 10 | witness::Witness, 11 | }; 12 | 13 | #[cfg(not(feature = "dep_wasm"))] 14 | #[tokio::main] 15 | async fn main() { 16 | let env = Env::load(); 17 | 18 | let matches = exploit_command().get_matches(); 19 | let subcommand_name = matches.subcommand_name(); 20 | let arg_matches = subcommand_name.and_then(|name| matches.subcommand_matches(name)); 21 | 22 | match subcommand_name { 23 | Some(TEST) => { 24 | let r = ProveArgs::from(arg_matches, &env); 25 | let w = Witness::gen(&r).await; 26 | w.assert(); 27 | } 28 | Some(PROVE) => { 29 | let r = ProveArgs::from(arg_matches, &env); 30 | let w = Witness::gen(&r).await; 31 | w.prove(r).await; 32 | } 33 | Some(VERIFY) => { 34 | let r = VerifyArgs::from(arg_matches, &env).await; 35 | handle_verify(r).await; 36 | } 37 | Some(PUBLISH) => { 38 | let r = PublishArgs::from(arg_matches); 39 | let hash = ipfs::publish_file(r.file_path).await.unwrap(); 40 | println!("Published file to ipfs: {}", hash); 41 | } 42 | Some(SCAFFOLD) => { 43 | let r = ScaffoldArgs::from(arg_matches); 44 | scaffold::huff_template(r.project_name.as_str()); 45 | println!("\nGet started:\ncd {}", r.project_name); 46 | } 47 | _ => unreachable!("command not found"), 48 | } 49 | } 50 | 51 | #[cfg(feature = "dep_wasm")] 52 | fn main() { 53 | unreachable!(); 54 | } 55 | -------------------------------------------------------------------------------- /src/utils/anvil/client.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | conversion::{convert_option, ConversionReverse}, 3 | types::{anvil_types, zkevm_types}, 4 | }; 5 | use crate::{env::Env, error::Error}; 6 | use anvil::{eth::EthApi, spawn, NodeConfig}; 7 | use ethers::utils::parse_ether; 8 | use std::{thread::sleep, time::Duration}; 9 | 10 | use super::conversion::Conversion; 11 | pub struct AnvilClient { 12 | eth_api: EthApi, 13 | } 14 | 15 | #[allow(dead_code)] 16 | impl AnvilClient { 17 | pub async fn default() -> Self { 18 | let env = Env::load(); 19 | Self::setup(env.eth_rpc_url, env.fork_block_number).await 20 | } 21 | 22 | pub async fn setup(eth_rpc_url: Option, fork_block_number: Option) -> Self { 23 | let node_config = NodeConfig::default() 24 | .with_eth_rpc_url(eth_rpc_url) 25 | .with_fork_block_number(fork_block_number.map(|v| v as u64)) 26 | .with_port(8548) 27 | .silent() 28 | .with_steps_tracing(true) 29 | .with_gas_price(Some(0)) 30 | .with_base_fee(Some(0)); 31 | 32 | let (eth_api, _) = spawn(node_config).await; 33 | Self { eth_api } 34 | } 35 | 36 | pub fn eth_chain_id(&self) -> Result, Error> { 37 | match self.eth_api.eth_chain_id()? { 38 | Some(chain_id) => Ok(Some(zkevm_types::Word::from(chain_id.as_usize()))), 39 | None => Ok(None), 40 | } 41 | } 42 | 43 | pub fn block_number(&self) -> Result { 44 | Ok(self.eth_api.block_number()?.as_usize()) 45 | } 46 | 47 | pub async fn block_by_number( 48 | &self, 49 | block_number: usize, 50 | ) -> Result, Error> { 51 | let b = anvil_types::BlockNumber::Number(anvil_types::U64::from(block_number)); 52 | match self.eth_api.block_by_number(b).await? { 53 | Some(block) => Ok(Some(block.to_zkevm_type())), 54 | None => Ok(None), 55 | } 56 | } 57 | 58 | pub async fn block_by_number_full( 59 | &self, 60 | block_number: usize, 61 | ) -> Result, Error> { 62 | match self 63 | .eth_api 64 | .block_by_number_full(anvil_types::BlockNumber::Number(anvil_types::U64::from( 65 | block_number, 66 | ))) 67 | .await? 68 | { 69 | Some(block) => { 70 | let mut _block = block.to_zkevm_type(); 71 | _block.transactions = _block 72 | .transactions 73 | .iter() 74 | .map(|tx| patch_transaction(tx.clone())) 75 | .collect(); 76 | Ok(Some(_block)) 77 | } 78 | None => Ok(None), 79 | } 80 | } 81 | 82 | pub async fn transaction_by_hash( 83 | &self, 84 | hash: zkevm_types::H256, 85 | ) -> Result, Error> { 86 | match self 87 | .eth_api 88 | .transaction_by_hash(hash.to_anvil_type()) 89 | .await? 90 | { 91 | Some(tx) => Ok(Some(patch_transaction(tx.to_zkevm_type()))), 92 | None => Ok(None), 93 | } 94 | } 95 | 96 | pub async fn transaction_receipt( 97 | &self, 98 | hash: zkevm_types::H256, 99 | ) -> Result, Error> { 100 | match self 101 | .eth_api 102 | .transaction_receipt(hash.to_anvil_type()) 103 | .await? 104 | { 105 | Some(rc) => Ok(Some(rc.to_zkevm_type())), 106 | None => Ok(None), 107 | } 108 | } 109 | 110 | pub async fn get_proof( 111 | &self, 112 | address: zkevm_types::Address, 113 | keys: Vec, 114 | block_number: Option, 115 | ) -> Result { 116 | Ok(self 117 | .eth_api 118 | .get_proof( 119 | address.to_anvil_type(), 120 | keys.iter().map(|key| key.to_anvil_type()).collect(), 121 | block_number.map(|_block_number| { 122 | anvil_types::BlockId::Number(anvil_types::BlockNumber::Number( 123 | anvil_types::U64::from(_block_number), 124 | )) 125 | }), 126 | ) 127 | .await? 128 | .to_zkevm_type()) 129 | } 130 | 131 | pub async fn block_by_hash( 132 | &self, 133 | hash: zkevm_types::Hash, 134 | ) -> Result, Error> { 135 | Ok(convert_option( 136 | self.eth_api.block_by_hash(hash.to_anvil_type()).await?, 137 | )) 138 | } 139 | 140 | pub async fn debug_trace_transaction( 141 | &self, 142 | hash: zkevm_types::Hash, 143 | options: anvil_types::GethDebugTracingOptions, 144 | ) -> Result { 145 | let trace = self 146 | .eth_api 147 | .debug_trace_transaction(hash.to_anvil_type(), options) 148 | .await? 149 | .to_zkevm_type(); 150 | Ok(patch_trace(trace)) 151 | } 152 | 153 | pub async fn get_code( 154 | &self, 155 | address: zkevm_types::Address, 156 | block_number: Option, 157 | ) -> Result { 158 | Ok(self 159 | .eth_api 160 | .get_code( 161 | address.to_anvil_type(), 162 | block_number.map(|_block_number| { 163 | anvil_types::BlockId::Number(anvil_types::BlockNumber::Number( 164 | anvil_types::U64::from(_block_number), 165 | )) 166 | }), 167 | ) 168 | .await? 169 | .to_zkevm_type()) 170 | } 171 | 172 | pub async fn set_balance( 173 | &self, 174 | address: zkevm_types::Address, 175 | balance: zkevm_types::U256, 176 | ) -> Result<(), Error> { 177 | Ok(self 178 | .eth_api 179 | .anvil_set_balance(address.to_anvil_type(), balance.to_anvil_type()) 180 | .await?) 181 | } 182 | 183 | pub async fn set_code( 184 | &self, 185 | address: zkevm_types::Address, 186 | code: zkevm_types::Bytes, 187 | ) -> Result<(), Error> { 188 | Ok(self 189 | .eth_api 190 | .anvil_set_code(address.to_anvil_type(), code.to_anvil_type()) 191 | .await?) 192 | } 193 | 194 | pub async fn get_balance( 195 | &self, 196 | address: zkevm_types::Address, 197 | block_number: Option, 198 | ) -> Result { 199 | Ok(self 200 | .eth_api 201 | .balance( 202 | address.to_anvil_type(), 203 | block_number.map(|_block_number| { 204 | anvil_types::BlockId::Number(anvil_types::BlockNumber::Number( 205 | anvil_types::U64::from(_block_number), 206 | )) 207 | }), 208 | ) 209 | .await? 210 | .to_zkevm_type()) 211 | } 212 | 213 | pub async fn get_nonce( 214 | &self, 215 | address: zkevm_types::Address, 216 | block_number: Option, 217 | ) -> Result { 218 | Ok(self 219 | .eth_api 220 | .transaction_count( 221 | address.to_anvil_type(), 222 | block_number.map(|_block_number| { 223 | anvil_types::BlockId::Number(anvil_types::BlockNumber::Number( 224 | anvil_types::U64::from(_block_number), 225 | )) 226 | }), 227 | ) 228 | .await? 229 | .to_zkevm_type()) 230 | } 231 | 232 | pub async fn get_storage_at( 233 | &self, 234 | address: zkevm_types::Address, 235 | index: zkevm_types::U256, 236 | block_number: Option, 237 | ) -> Result { 238 | Ok(self 239 | .eth_api 240 | .storage_at( 241 | address.to_anvil_type(), 242 | index.to_anvil_type(), 243 | block_number.map(|_block_number| { 244 | anvil_types::BlockId::Number(anvil_types::BlockNumber::Number( 245 | anvil_types::U64::from(_block_number), 246 | )) 247 | }), 248 | ) 249 | .await? 250 | .to_zkevm_type()) 251 | } 252 | 253 | pub async fn estimate_gas( 254 | &self, 255 | request: anvil_types::EthTransactionRequest, 256 | block_number: Option, 257 | ) -> Result { 258 | Ok(self 259 | .eth_api 260 | .estimate_gas( 261 | request, 262 | block_number.map(|n| { 263 | anvil_types::BlockId::Number(anvil_types::BlockNumber::Number( 264 | anvil_types::U64::from(n), 265 | )) 266 | }), 267 | ) 268 | .await 269 | .map(|v| v.to_zkevm_type())?) 270 | } 271 | 272 | pub async fn send_raw_transaction( 273 | &self, 274 | raw_tx: zkevm_types::Bytes, 275 | ) -> Result { 276 | Ok(self 277 | .eth_api 278 | .send_raw_transaction(raw_tx.to_anvil_type()) 279 | .await? 280 | .to_zkevm_type()) 281 | } 282 | 283 | pub async fn fund_wallet( 284 | &self, 285 | address: zkevm_types::Address, 286 | ) -> Result { 287 | let accounts = self.eth_api.accounts().unwrap(); 288 | Ok(self 289 | .eth_api 290 | .send_transaction(anvil_types::EthTransactionRequest { 291 | from: Some(accounts[0]), 292 | to: Some(address.to_anvil_type()), 293 | gas_price: None, 294 | max_fee_per_gas: None, 295 | max_priority_fee_per_gas: None, 296 | gas: None, 297 | value: Some(parse_ether("1").unwrap()), 298 | data: None, 299 | nonce: None, 300 | chain_id: None, 301 | access_list: None, 302 | transaction_type: None, 303 | }) 304 | .await? 305 | .to_zkevm_type()) 306 | } 307 | 308 | pub async fn mine_one(&self) { 309 | self.eth_api.mine_one().await; 310 | } 311 | 312 | pub async fn wait_for_transaction(&self, hash: zkevm_types::Hash) -> Result<(), Error> { 313 | loop { 314 | let rc = self.transaction_receipt(hash).await.unwrap(); 315 | if rc.is_some() { 316 | return Ok(()); 317 | } 318 | sleep(Duration::from_secs(1)) 319 | } 320 | } 321 | } 322 | 323 | pub fn patch_transaction(mut tx: zkevm_types::Transaction) -> zkevm_types::Transaction { 324 | if tx.transaction_type.is_none() { 325 | tx.transaction_type = Some(zkevm_types::U64::from(0)); 326 | tx.max_fee_per_gas = None; 327 | tx.max_priority_fee_per_gas = None; 328 | } 329 | tx 330 | } 331 | 332 | pub fn patch_trace(mut trace: zkevm_types::GethExecTrace) -> zkevm_types::GethExecTrace { 333 | if trace.struct_logs.len() == 1 && trace.struct_logs[0].op == zkevm_types::OpcodeId::STOP { 334 | trace.struct_logs = vec![] 335 | } 336 | trace 337 | } 338 | 339 | #[cfg(test)] 340 | mod tests { 341 | use super::AnvilClient; 342 | 343 | // ignored because cannot run anvil again in other test 344 | #[ignore] 345 | #[tokio::test] 346 | async fn test() { 347 | let cli = AnvilClient::setup(None, None).await; 348 | let bn = cli.block_number().unwrap(); 349 | assert_eq!(bn, 0); 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /src/utils/anvil/conversion.rs: -------------------------------------------------------------------------------- 1 | use super::types::{anvil_types, zkevm_types}; 2 | use ethers::{types::BigEndianHash, utils::hex}; 3 | use std::{collections::HashMap, str::FromStr}; 4 | 5 | // Conversion from anvil types to zkevm types 6 | pub trait Conversion { 7 | fn to_zkevm_type(&self) -> T; 8 | } 9 | 10 | pub fn convert_option, Z>(some_val: Option) -> Option { 11 | some_val.map(|val| val.to_zkevm_type()) 12 | } 13 | 14 | impl Conversion for anvil_types::U256 { 15 | fn to_zkevm_type(&self) -> zkevm_types::U256 { 16 | let mut new = zkevm_types::U256::zero(); 17 | new.0 = self.0; 18 | new 19 | } 20 | } 21 | 22 | impl Conversion for anvil_types::U64 { 23 | fn to_zkevm_type(&self) -> zkevm_types::U64 { 24 | let mut new = zkevm_types::U64::zero(); 25 | new.0 = self.0; 26 | new 27 | } 28 | } 29 | 30 | impl Conversion for anvil_types::H256 { 31 | fn to_zkevm_type(&self) -> zkevm_types::U256 { 32 | zkevm_types::U256::from_big_endian(self.as_bytes()) 33 | } 34 | } 35 | 36 | impl Conversion for anvil_types::H64 { 37 | fn to_zkevm_type(&self) -> zkevm_types::H64 { 38 | let mut new = zkevm_types::H64::zero(); 39 | new.0 = self.0; 40 | new 41 | } 42 | } 43 | 44 | impl Conversion for anvil_types::H256 { 45 | fn to_zkevm_type(&self) -> zkevm_types::H256 { 46 | let mut new = zkevm_types::H256::zero(); 47 | new.0 = self.0; 48 | new 49 | } 50 | } 51 | 52 | impl Conversion for anvil_types::H160 { 53 | fn to_zkevm_type(&self) -> zkevm_types::H160 { 54 | let mut new = zkevm_types::H160::zero(); 55 | new.0 = self.0; 56 | new 57 | } 58 | } 59 | 60 | impl Conversion for anvil_types::Bytes { 61 | fn to_zkevm_type(&self) -> zkevm_types::Bytes { 62 | zkevm_types::Bytes::from(self.to_vec()) 63 | } 64 | } 65 | 66 | impl Conversion for anvil_types::Bloom { 67 | fn to_zkevm_type(&self) -> zkevm_types::Bloom { 68 | zkevm_types::Bloom::from_slice(&self.0) 69 | } 70 | } 71 | 72 | impl Conversion for anvil_types::AccessList { 73 | fn to_zkevm_type(&self) -> zkevm_types::AccessList { 74 | zkevm_types::AccessList( 75 | self.0 76 | .iter() 77 | .map(|item| zkevm_types::AccessListItem { 78 | address: item.address.to_zkevm_type(), 79 | storage_keys: item 80 | .storage_keys 81 | .iter() 82 | .map(|key| key.to_zkevm_type()) 83 | .collect(), 84 | }) 85 | .collect(), 86 | ) 87 | } 88 | } 89 | 90 | impl Conversion for anvil_types::Transaction { 91 | fn to_zkevm_type(&self) -> zkevm_types::Transaction { 92 | zkevm_types::Transaction { 93 | hash: self.hash.to_zkevm_type(), 94 | nonce: self.nonce.to_zkevm_type(), 95 | block_hash: convert_option(self.block_hash), 96 | block_number: convert_option(self.block_number), 97 | transaction_index: convert_option(self.transaction_index), 98 | from: self.from.to_zkevm_type(), 99 | to: convert_option(self.to), 100 | value: self.value.to_zkevm_type(), 101 | gas_price: convert_option(self.gas_price), 102 | gas: self.gas.to_zkevm_type(), 103 | input: self.input.to_zkevm_type(), 104 | v: self.v.to_zkevm_type(), 105 | r: self.r.to_zkevm_type(), 106 | s: self.s.to_zkevm_type(), 107 | transaction_type: convert_option(self.transaction_type), 108 | access_list: self 109 | .access_list 110 | .as_ref() 111 | .map(|access_list| access_list.to_zkevm_type()), 112 | max_priority_fee_per_gas: convert_option(self.max_priority_fee_per_gas), 113 | max_fee_per_gas: convert_option(self.max_fee_per_gas), 114 | chain_id: convert_option(self.chain_id), 115 | other: zkevm_types::OtherFields::default(), 116 | } 117 | } 118 | } 119 | 120 | impl, Z> Conversion> for anvil_types::Block { 121 | fn to_zkevm_type(&self) -> zkevm_types::Block { 122 | zkevm_types::Block { 123 | hash: convert_option(self.hash), 124 | parent_hash: self.parent_hash.to_zkevm_type(), 125 | uncles_hash: self.uncles_hash.to_zkevm_type(), 126 | author: convert_option(self.author), 127 | state_root: self.state_root.to_zkevm_type(), 128 | transactions_root: self.transactions_root.to_zkevm_type(), 129 | receipts_root: self.receipts_root.to_zkevm_type(), 130 | number: convert_option(self.number), 131 | gas_used: self.gas_used.to_zkevm_type(), 132 | gas_limit: self.gas_limit.to_zkevm_type(), 133 | extra_data: self.extra_data.to_zkevm_type(), 134 | logs_bloom: convert_option(self.logs_bloom), 135 | timestamp: self.timestamp.to_zkevm_type(), 136 | difficulty: self.difficulty.to_zkevm_type(), 137 | total_difficulty: convert_option(self.total_difficulty), 138 | seal_fields: self.seal_fields.iter().map(|b| b.to_zkevm_type()).collect(), 139 | uncles: self.uncles.iter().map(|b| b.to_zkevm_type()).collect(), 140 | transactions: self 141 | .transactions 142 | .iter() 143 | .map(|b| b.to_zkevm_type()) 144 | .collect(), 145 | size: convert_option(self.size), 146 | mix_hash: convert_option(self.mix_hash), 147 | nonce: convert_option(self.nonce), 148 | base_fee_per_gas: convert_option(self.base_fee_per_gas), 149 | other: zkevm_types::OtherFields::default(), 150 | withdrawals_root: convert_option(self.withdrawals_root), 151 | withdrawals: self 152 | .withdrawals 153 | .as_ref() 154 | .map(|ws| ws.iter().map(|w| w.to_zkevm_type()).collect()), 155 | } 156 | } 157 | } 158 | 159 | impl Conversion for anvil_types::TransactionReceipt { 160 | fn to_zkevm_type(&self) -> zkevm_types::TransactionReceipt { 161 | zkevm_types::TransactionReceipt { 162 | transaction_hash: self.transaction_hash.to_zkevm_type(), 163 | transaction_index: self.transaction_index.to_zkevm_type(), 164 | block_hash: convert_option(self.block_hash), 165 | block_number: convert_option(self.block_number), 166 | from: self.from.to_zkevm_type(), 167 | to: convert_option(self.to), 168 | cumulative_gas_used: self.cumulative_gas_used.to_zkevm_type(), 169 | gas_used: convert_option(self.gas_used), 170 | contract_address: convert_option(self.contract_address), 171 | logs: self.logs.iter().map(|b| b.to_zkevm_type()).collect(), 172 | status: convert_option(self.status), 173 | root: convert_option(self.root), 174 | logs_bloom: self.logs_bloom.to_zkevm_type(), 175 | transaction_type: convert_option(self.transaction_type), 176 | effective_gas_price: convert_option(self.effective_gas_price), 177 | other: zkevm_types::OtherFields::default(), 178 | } 179 | } 180 | } 181 | 182 | impl Conversion for anvil_types::Log { 183 | fn to_zkevm_type(&self) -> zkevm_types::Log { 184 | zkevm_types::Log { 185 | address: self.address.to_zkevm_type(), 186 | topics: self.topics.iter().map(|b| b.to_zkevm_type()).collect(), 187 | data: self.data.to_zkevm_type(), 188 | block_hash: convert_option(self.block_hash), 189 | block_number: convert_option(self.block_number), 190 | transaction_hash: convert_option(self.transaction_hash), 191 | transaction_index: convert_option(self.transaction_index), 192 | log_index: convert_option(self.log_index), 193 | transaction_log_index: convert_option(self.transaction_log_index), 194 | log_type: self.log_type.clone(), 195 | removed: self.removed, 196 | } 197 | } 198 | } 199 | 200 | impl Conversion for anvil_types::GethTrace { 201 | fn to_zkevm_type(&self) -> zkevm_types::GethExecTrace { 202 | if let ethers::types::GethTrace::Known(ethers::types::GethTraceFrame::Default( 203 | anvil_trace, 204 | )) = self.to_owned() 205 | { 206 | zkevm_types::GethExecTrace { 207 | gas: anvil_trace.gas.as_u64(), 208 | failed: anvil_trace.failed, 209 | return_value: hex::encode(anvil_trace.return_value.as_ref()), // TODO see if 0x adjustment is needed 210 | struct_logs: anvil_trace 211 | .struct_logs 212 | .into_iter() 213 | .map(|step| { 214 | zkevm_types::GethExecStep { 215 | pc: step.pc, 216 | op: zkevm_types::OpcodeId::from_str(step.op.as_str()).unwrap(), 217 | gas: step.gas, 218 | gas_cost: step.gas_cost, 219 | refund: step.refund_counter.unwrap_or(0), 220 | depth: u16::try_from(step.depth).expect("error converting depth"), 221 | error: step.error, 222 | stack: zkevm_types::Stack( 223 | step.stack 224 | .unwrap_or(Vec::new()) 225 | .into_iter() 226 | .map(|w| w.to_zkevm_type()) 227 | .collect(), 228 | ), 229 | memory: zkevm_types::Memory::default(), // memory is not enabled 230 | storage: { 231 | let tree = step.storage.unwrap_or_default(); 232 | let mut hash_map = 233 | HashMap::::new(); 234 | for (key, value) in &tree { 235 | hash_map.insert(key.to_zkevm_type(), value.to_zkevm_type()); 236 | } 237 | zkevm_types::Storage(hash_map) 238 | }, 239 | } 240 | }) 241 | .collect(), 242 | } 243 | } else { 244 | panic!("unknown value in trace") 245 | } 246 | } 247 | } 248 | 249 | impl Conversion for anvil_types::EIP1186ProofResponse { 250 | fn to_zkevm_type(&self) -> zkevm_types::EIP1186ProofResponse { 251 | zkevm_types::EIP1186ProofResponse { 252 | address: self.address.to_zkevm_type(), 253 | balance: self.balance.to_zkevm_type(), 254 | code_hash: self.code_hash.to_zkevm_type(), 255 | nonce: zkevm_types::U64::from(self.nonce.as_u64()), 256 | storage_hash: self.storage_hash.to_zkevm_type(), 257 | account_proof: self 258 | .account_proof 259 | .iter() 260 | .map(|ap| ap.to_zkevm_type()) 261 | .collect(), 262 | storage_proof: self 263 | .storage_proof 264 | .iter() 265 | .map(|sp| zkevm_types::StorageProof { 266 | key: sp.key.to_zkevm_type(), 267 | value: sp.value.to_zkevm_type(), 268 | proof: sp.proof.iter().map(|p| p.to_zkevm_type()).collect(), 269 | }) 270 | .collect(), 271 | } 272 | } 273 | } 274 | 275 | impl ConversionReverse for zkevm_types::EIP1186ProofResponse { 276 | fn to_anvil_type(&self) -> anvil_types::EIP1186ProofResponse { 277 | anvil_types::EIP1186ProofResponse { 278 | address: self.address.to_anvil_type(), 279 | balance: self.balance.to_anvil_type(), 280 | code_hash: self.code_hash.to_anvil_type(), 281 | nonce: anvil_types::U64::from(self.nonce.as_u64()), 282 | storage_hash: self.storage_hash.to_anvil_type(), 283 | account_proof: self 284 | .account_proof 285 | .iter() 286 | .map(|ap| ap.to_anvil_type()) 287 | .collect(), 288 | storage_proof: self 289 | .storage_proof 290 | .iter() 291 | .map(|sp| anvil_types::StorageProof { 292 | key: sp.key.to_anvil_type(), 293 | value: sp.value.to_anvil_type(), 294 | proof: sp.proof.iter().map(|p| p.to_anvil_type()).collect(), 295 | }) 296 | .collect(), 297 | } 298 | } 299 | } 300 | 301 | impl Conversion for anvil_types::Withdrawal { 302 | fn to_zkevm_type(&self) -> zkevm_types::Withdrawal { 303 | zkevm_types::Withdrawal { 304 | index: self.index, 305 | validator_index: self.validator_index, 306 | address: self.address, 307 | amount: self.amount, 308 | } 309 | } 310 | } 311 | impl ConversionReverse for zkevm_types::Withdrawal { 312 | fn to_anvil_type(&self) -> anvil_types::Withdrawal { 313 | anvil_types::Withdrawal { 314 | index: self.index, 315 | validator_index: self.validator_index, 316 | address: self.address, 317 | amount: self.amount, 318 | } 319 | } 320 | } 321 | 322 | // Conversion from zkevm types to anvil types 323 | pub trait ConversionReverse { 324 | fn to_anvil_type(&self) -> T; 325 | } 326 | 327 | pub fn convert_option_reverse, Z>(some_val: Option) -> Option { 328 | some_val.map(|val| val.to_anvil_type()) 329 | } 330 | 331 | impl ConversionReverse for zkevm_types::H160 { 332 | fn to_anvil_type(&self) -> anvil_types::H160 { 333 | let mut new = anvil_types::H160::zero(); 334 | new.0 = self.0; 335 | new 336 | } 337 | } 338 | 339 | impl ConversionReverse for zkevm_types::H256 { 340 | fn to_anvil_type(&self) -> anvil_types::H256 { 341 | let mut new = anvil_types::H256::zero(); 342 | new.0 = self.0; 343 | new 344 | } 345 | } 346 | 347 | impl ConversionReverse for zkevm_types::U64 { 348 | fn to_anvil_type(&self) -> anvil_types::U64 { 349 | let mut new = anvil_types::U64::zero(); 350 | new.0 = self.0; 351 | new 352 | } 353 | } 354 | 355 | impl ConversionReverse for zkevm_types::U256 { 356 | fn to_anvil_type(&self) -> anvil_types::U256 { 357 | let mut new = anvil_types::U256::zero(); 358 | new.0 = self.0; 359 | new 360 | } 361 | } 362 | 363 | impl ConversionReverse for zkevm_types::U256 { 364 | fn to_anvil_type(&self) -> anvil_types::H256 { 365 | anvil_types::H256::from_uint(&self.to_anvil_type()) 366 | } 367 | } 368 | 369 | impl ConversionReverse for zkevm_types::Bytes { 370 | fn to_anvil_type(&self) -> anvil_types::Bytes { 371 | anvil_types::Bytes::from(self.to_vec()) 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /src/utils/anvil/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod conversion; 3 | pub mod types; 4 | pub use client::AnvilClient; 5 | -------------------------------------------------------------------------------- /src/utils/anvil/types.rs: -------------------------------------------------------------------------------- 1 | pub mod zkevm_types { 2 | use eth_types::BigEndianHash; 3 | pub use eth_types::{ 4 | evm_types::{GasCost, Memory, OpcodeId, Stack, Storage}, 5 | Address, Block, Bytes, EIP1186ProofResponse, GethExecStep, GethExecTrace, Hash, 6 | StorageProof, Transaction, Word, H160, H256, H64, U256, U64, 7 | }; 8 | pub use ethers::types::GethDebugTracingOptions; // intentionally 9 | pub use ethers_core::types::{ 10 | transaction::eip2930::{AccessList, AccessListItem}, 11 | BlockNumber, Bloom, Log, OtherFields, TransactionReceipt, Withdrawal, 12 | }; 13 | pub type EthBlockFull = Block; 14 | pub type EthBlockHeader = Block; 15 | 16 | pub fn h256_to_u256(input: H256) -> U256 { 17 | U256::from_big_endian(input.as_bytes()) 18 | } 19 | 20 | pub fn u256_to_h256(input: U256) -> H256 { 21 | H256::from_uint(&input) 22 | } 23 | } 24 | 25 | pub mod anvil_types { 26 | pub use anvil_core::eth::transaction::EthTransactionRequest; 27 | pub use ethers::types::{ 28 | transaction::eip2930::AccessList, Address, Block, BlockId, BlockNumber, Bloom, Bytes, 29 | EIP1186ProofResponse, GethDebugTracingOptions, GethTrace, Log, OtherFields, StorageProof, 30 | Transaction, TransactionReceipt, TxHash, Withdrawal, H160, H256, H64, U256, U64, 31 | }; 32 | pub type EthBlockFull = Block; 33 | pub type EthBlockHeader = Block; 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/etherscan.rs: -------------------------------------------------------------------------------- 1 | pub fn block_url(chain_id: u64, block_number: u64) -> String { 2 | if chain_id == 11155111 { 3 | format!("https://sepolia.etherscan.io/block/{}", block_number) 4 | } else { 5 | String::new() 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/geth.rs: -------------------------------------------------------------------------------- 1 | use super::{anvil::conversion::ConversionReverse, helpers::hashmap}; 2 | use crate::error::Error; 3 | use anvil_core::eth::transaction::EthTransactionRequest; 4 | use bus_mapping::{POX_CHALLENGE_ADDRESS, POX_EXPLOIT_ADDRESS}; 5 | use eth_types::{Bytes, GethExecTrace, Transaction, U256, U64}; 6 | use ethers::{ 7 | providers::{Http, Provider}, 8 | utils::hex, 9 | }; 10 | use serde::{Deserialize, Serialize}; 11 | use serde_json::Value; 12 | use std::collections::HashMap; 13 | 14 | #[derive(Clone)] 15 | pub struct GethClient { 16 | provider: Provider, 17 | } 18 | 19 | #[derive(Clone, Debug, Serialize, Deserialize, Default)] 20 | #[serde(rename_all = "camelCase")] 21 | pub struct GethDebugTracingOptions { 22 | #[serde(default, skip_serializing_if = "Option::is_none")] 23 | pub disable_storage: Option, 24 | #[serde(default, skip_serializing_if = "Option::is_none")] 25 | pub disable_stack: Option, 26 | #[serde(default, skip_serializing_if = "Option::is_none")] 27 | pub enable_memory: Option, 28 | #[serde(default, skip_serializing_if = "Option::is_none")] 29 | pub enable_return_data: Option, 30 | #[serde(default, skip_serializing_if = "Option::is_none")] 31 | pub timeout: Option, 32 | #[serde(default, skip_serializing_if = "Option::is_none")] 33 | state_overrides: Option>, 34 | } 35 | 36 | #[derive(Clone, Debug, Serialize, Deserialize)] 37 | struct StateOverrides { 38 | #[serde(default, skip_serializing_if = "Option::is_none")] 39 | code: Option, 40 | #[serde(default, skip_serializing_if = "Option::is_none")] 41 | balance: Option, 42 | } 43 | 44 | impl GethClient { 45 | pub fn new(url: String) -> Self { 46 | GethClient { 47 | provider: Provider::::try_from(&url).unwrap(), 48 | } 49 | } 50 | 51 | pub async fn simulate_exploit( 52 | &self, 53 | tx: &Transaction, 54 | challenge_bytecode: Bytes, 55 | exploit_bytecode: Bytes, 56 | exploit_balance: U256, 57 | ) -> Result { 58 | Ok(self 59 | .provider 60 | .request::<_, GethExecTrace>( 61 | "debug_traceCall", 62 | [ 63 | serde_json::to_value(EthTransactionRequest { 64 | from: Some(tx.from), 65 | to: Some(POX_CHALLENGE_ADDRESS), 66 | gas_price: tx.gas_price, 67 | max_fee_per_gas: tx.max_fee_per_gas, 68 | max_priority_fee_per_gas: tx.max_priority_fee_per_gas, 69 | gas: Some(tx.gas), 70 | value: Some(tx.value), 71 | data: Some(tx.input.to_anvil_type()), 72 | nonce: Some(tx.nonce), 73 | chain_id: tx.chain_id.map(|c| U64::from(c.as_u64())), 74 | access_list: None, // TODO tx.access_list, 75 | transaction_type: tx.transaction_type.map(|c| U256::from(c.as_u64())), 76 | }) 77 | .unwrap(), 78 | Value::String("latest".to_string()), // node not support archive trace - Value::String(format!("0x{block_number:x}")), 79 | serde_json::to_value(GethDebugTracingOptions { 80 | enable_memory: None, 81 | disable_stack: None, 82 | disable_storage: None, 83 | enable_return_data: None, 84 | timeout: None, 85 | state_overrides: Some(hashmap![ 86 | format!("{POX_CHALLENGE_ADDRESS:?}") => StateOverrides { 87 | code: Some(hex::encode_prefixed(challenge_bytecode)), 88 | balance: None, 89 | }, 90 | format!("{POX_EXPLOIT_ADDRESS:?}") => StateOverrides { 91 | code: Some(hex::encode_prefixed(exploit_bytecode)), 92 | balance: Some(format!("0x{exploit_balance:x}")), 93 | } 94 | ]), 95 | }) 96 | .unwrap(), 97 | ], 98 | ) 99 | .await?) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/utils/halo2/helpers.rs: -------------------------------------------------------------------------------- 1 | use eth_types::{Fr, H256}; 2 | use ethers::utils::hex; 3 | use serde::{ 4 | de::{self, Visitor}, 5 | Deserialize, Serialize, 6 | }; 7 | use std::{ 8 | fmt::{self, Debug, Formatter}, 9 | str::FromStr, 10 | }; 11 | use zkevm_circuits::super_circuit::SuperCircuitParams; 12 | 13 | pub fn derive_circuit_name(circuit: ConcreteCircuit) -> String 14 | where 15 | ConcreteCircuit: Debug, 16 | { 17 | let mut circuit_format = format!("{:?}", circuit); 18 | if let Some(index) = circuit_format.find(' ') { 19 | circuit_format.truncate(index); 20 | circuit_format 21 | } else { 22 | panic!("no space found in '{}'", circuit_format); 23 | } 24 | } 25 | 26 | #[derive(Clone, Debug)] 27 | pub struct FrWrapper(pub Fr); 28 | 29 | impl Serialize for FrWrapper { 30 | fn serialize(&self, serializer: S) -> Result 31 | where 32 | S: serde::Serializer, 33 | { 34 | let mut bytes = self.0.to_bytes(); 35 | bytes.reverse(); 36 | serializer.serialize_str(hex::encode_prefixed(bytes).as_str()) 37 | } 38 | } 39 | 40 | impl<'de> Deserialize<'de> for FrWrapper { 41 | fn deserialize(deserializer: D) -> Result 42 | where 43 | D: serde::Deserializer<'de>, 44 | { 45 | deserializer.deserialize_str(FrVisitor) 46 | } 47 | } 48 | 49 | pub struct FrVisitor; 50 | 51 | impl<'de> Visitor<'de> for FrVisitor { 52 | type Value = FrWrapper; 53 | 54 | fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { 55 | formatter.write_str("str") 56 | } 57 | 58 | fn visit_str(self, v: &str) -> Result 59 | where 60 | E: de::Error, 61 | { 62 | let bytes = H256::from_str(v).unwrap(); 63 | let mut bytes = bytes.as_fixed_bytes().to_owned(); 64 | bytes.reverse(); 65 | Ok(FrWrapper(Fr::from_bytes(&bytes).unwrap())) 66 | } 67 | } 68 | 69 | #[derive(Clone, Debug, Serialize, Deserialize)] 70 | pub struct SuperCircuitParamsWrapper { 71 | pub mock_randomness: FrWrapper, 72 | } 73 | 74 | impl SuperCircuitParamsWrapper { 75 | pub fn wrap(value: SuperCircuitParams) -> Self { 76 | Self { 77 | mock_randomness: FrWrapper(value.mock_randomness), 78 | } 79 | } 80 | pub fn unwrap(self) -> SuperCircuitParams { 81 | SuperCircuitParams { 82 | mock_randomness: self.mock_randomness.0, 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/utils/halo2/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod helpers; 2 | pub mod proof; 3 | pub mod real_prover; 4 | pub mod real_verifier; 5 | pub mod srs; 6 | -------------------------------------------------------------------------------- /src/utils/halo2/proof.rs: -------------------------------------------------------------------------------- 1 | use super::{super::solidity::Artifact, helpers::FrWrapper, helpers::SuperCircuitParamsWrapper}; 2 | use crate::error::Error; 3 | use bus_mapping::circuit_input_builder::FixedCParams; 4 | use ethers::types::Bytes; 5 | use halo2_proofs::halo2curves::bn256::Fr; 6 | use semver::Version; 7 | use serde::{Deserialize, Serialize}; 8 | use std::{ 9 | fmt::Debug, 10 | fs::File, 11 | io::{Read, Write}, 12 | path::PathBuf, 13 | str::FromStr, 14 | }; 15 | use zkevm_circuits::{instance::PublicData, super_circuit::SuperCircuitParams}; 16 | 17 | #[derive(Clone, Debug, Serialize, Deserialize)] 18 | pub struct Proof { 19 | pub version: Version, 20 | pub degree: u32, 21 | pub data: Bytes, 22 | instances: Vec>, 23 | circuit_params: SuperCircuitParamsWrapper, 24 | pub fixed_circuit_params: FixedCParams, 25 | pub public_data: PublicData, 26 | pub challenge_artifact: Option, 27 | #[serde(skip_serializing_if = "Option::is_none")] 28 | pub summary: Option, 29 | } 30 | 31 | impl Proof { 32 | #[allow(clippy::too_many_arguments)] 33 | pub fn from( 34 | degree: u32, 35 | proof: Vec, 36 | instances: Vec>, 37 | circuit_params: SuperCircuitParams, 38 | fixed_circuit_params: FixedCParams, 39 | public_data: PublicData, 40 | challenge_artifact: Option, 41 | summary: Option, 42 | ) -> Self { 43 | Self { 44 | version: Version::from_str(env!("CARGO_PKG_VERSION")).unwrap(), 45 | degree, 46 | data: Bytes::from(proof), 47 | instances: instances 48 | .iter() 49 | .map(|column| column.iter().map(|element| FrWrapper(*element)).collect()) 50 | .collect(), 51 | circuit_params: SuperCircuitParamsWrapper::wrap(circuit_params), 52 | fixed_circuit_params, 53 | public_data, 54 | challenge_artifact, 55 | summary, 56 | } 57 | } 58 | 59 | pub fn instances(&self) -> Vec> { 60 | self.instances 61 | .iter() 62 | .map(|column| column.iter().map(|element| element.0).collect()) 63 | .collect() 64 | } 65 | 66 | pub fn num_instances(&self) -> Vec { 67 | self.instances.iter().map(|column| column.len()).collect() 68 | } 69 | 70 | pub fn circuit_params(&self) -> SuperCircuitParams { 71 | self.circuit_params.clone().unwrap() 72 | } 73 | 74 | pub fn unpack(&self) -> (u32, Bytes, Vec>, PublicData, SuperCircuitParams) { 75 | let instances = self.instances(); 76 | let circuit_params = self.circuit_params(); 77 | ( 78 | self.degree, 79 | self.data.clone(), 80 | instances, 81 | self.public_data.clone(), 82 | circuit_params, 83 | ) 84 | } 85 | 86 | pub fn write_to_file(&self, path: &PathBuf) -> Result<(), Error> { 87 | // TODO ensure that parent dir exists 88 | let mut file = File::create(path)?; 89 | file.write_all(serde_json::to_string(self)?.as_bytes()) 90 | .unwrap(); 91 | Ok(()) 92 | } 93 | 94 | pub fn read_from_file(path: &PathBuf) -> Result { 95 | let mut file = File::open(path)?; 96 | let mut contents = String::new(); 97 | file.read_to_string(&mut contents)?; 98 | Ok(serde_json::from_str(&contents)?) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/utils/halo2/real_prover.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | proof::Proof, 3 | real_verifier::RealVerifier, 4 | srs::{VerifierSRS, SRS}, 5 | }; 6 | use crate::error::Error; 7 | use halo2_proofs::{ 8 | halo2curves::bn256::{Bn256, Fr, G1Affine}, 9 | plonk::{create_proof, Circuit}, 10 | poly::kzg::{commitment::KZGCommitmentScheme, multiopen::ProverSHPLONK}, 11 | transcript::{Blake2bWrite, Challenge255, TranscriptWriterBuffer}, 12 | }; 13 | use rand_chacha::{rand_core::SeedableRng, ChaChaRng}; 14 | use std::path::PathBuf; 15 | use zkevm_circuits::{ 16 | instance::public_data_convert, super_circuit::SuperCircuit, util::SubCircuit, 17 | }; 18 | 19 | #[derive(Clone)] 20 | pub struct RealProver { 21 | circuit: SuperCircuit, 22 | degree: u32, 23 | srs: SRS, 24 | } 25 | 26 | impl RealProver { 27 | pub fn from(circuit: SuperCircuit, degree: u32, srs_path: PathBuf) -> Self { 28 | let srs = SRS::load(&circuit, degree, srs_path); 29 | Self { 30 | circuit, 31 | degree, 32 | srs, 33 | } 34 | } 35 | 36 | pub fn prove(&mut self) -> Result { 37 | let public_data = public_data_convert(&self.circuit.evm_circuit.block.clone().unwrap()); 38 | let instances = self.circuit.instance(); 39 | let instances_refs_intermediate = instances.iter().map(|v| &v[..]).collect::>(); 40 | let mut transcript = Blake2bWrite::<_, G1Affine, Challenge255<_>>::init(vec![]); 41 | create_proof::< 42 | KZGCommitmentScheme, 43 | ProverSHPLONK<'_, Bn256>, 44 | Challenge255, 45 | ChaChaRng, 46 | Blake2bWrite, G1Affine, Challenge255>, 47 | _, 48 | >( 49 | &self.srs.general_params, 50 | &self.srs.circuit_proving_key, 51 | &[self.circuit.clone()], 52 | &[&instances_refs_intermediate], 53 | ChaChaRng::seed_from_u64(2), 54 | &mut transcript, 55 | ) 56 | .unwrap(); 57 | 58 | let proof = transcript.finalize(); 59 | Ok(Proof::from( 60 | self.degree, 61 | proof, 62 | instances, 63 | self.circuit.params(), 64 | self.circuit.circuits_params, 65 | public_data, 66 | None, 67 | None, 68 | )) 69 | } 70 | 71 | pub fn verifier(&mut self) -> RealVerifier { 72 | RealVerifier { 73 | srs: VerifierSRS { 74 | general_params: self.srs.general_params.clone(), 75 | verifier_params: self.srs.verifier_params.clone(), 76 | circuit_verifying_key: self.srs.circuit_verifying_key.clone(), 77 | }, 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/utils/halo2/real_verifier.rs: -------------------------------------------------------------------------------- 1 | use super::{proof::Proof, srs::VerifierSRS}; 2 | use crate::error::Error; 3 | use core::slice::SlicePattern; 4 | use eth_types::{keccak256, H256}; 5 | use halo2_proofs::{ 6 | halo2curves::bn256::{Bn256, Fr, G1Affine}, 7 | plonk::verify_proof, 8 | poly::kzg::{ 9 | commitment::KZGCommitmentScheme, multiopen::VerifierSHPLONK, strategy::SingleStrategy, 10 | }, 11 | transcript::{Blake2bRead, Challenge255, TranscriptReadBuffer}, 12 | }; 13 | use std::path::PathBuf; 14 | 15 | // type PlonkVerifier = verifier::plonk::PlonkVerifier>; 16 | 17 | pub struct RealVerifier { 18 | pub srs: VerifierSRS, 19 | } 20 | 21 | impl RealVerifier { 22 | pub async fn load_srs(srs_path: PathBuf, proof: &Proof) -> Self { 23 | Self { 24 | srs: VerifierSRS::load( 25 | srs_path, 26 | proof.degree, 27 | proof.circuit_params(), 28 | proof.fixed_circuit_params, 29 | ) 30 | .await, 31 | } 32 | } 33 | 34 | pub async fn verify(&self, proof: &Proof) -> Result<(), Error> { 35 | let (_, proof_data, instances, public_data, _) = proof.unpack(); 36 | let strategy = SingleStrategy::new(&self.srs.general_params); 37 | let instance_refs_intermediate = instances.iter().map(|v| &v[..]).collect::>(); 38 | let mut verifier_transcript = 39 | Blake2bRead::<_, G1Affine, Challenge255<_>>::init(&proof_data[..]); 40 | 41 | verify_proof::< 42 | KZGCommitmentScheme, 43 | VerifierSHPLONK<'_, Bn256>, 44 | Challenge255, 45 | Blake2bRead<&[u8], G1Affine, Challenge255>, 46 | SingleStrategy<'_, Bn256>, 47 | >( 48 | &self.srs.verifier_params, 49 | &self.srs.circuit_verifying_key, 50 | strategy, 51 | &[&instance_refs_intermediate], 52 | &mut verifier_transcript, 53 | )?; 54 | // println!("- ZK proof verifies"); 55 | 56 | // verify public data to be image of instance 57 | let digest = public_data.get_rpi_digest_word::(); 58 | if !(instances[0][0] == digest.lo() && instances[0][1] == digest.hi()) { 59 | return Err(Error::InternalError("digest mismatch")); 60 | } 61 | // println!("- Public inputs digest matches with instance"); 62 | 63 | if let Some(challenge_artifact) = proof.challenge_artifact.clone() { 64 | // verify compilation 65 | challenge_artifact.verify_compilation().await?; 66 | // println!("- Challenge contract compiles to POX codehash in public inputs"); 67 | 68 | // ensure that challenge codehash is same as the codehash in public inputs 69 | let bytecode = challenge_artifact 70 | .get_deployed_bytecode("Challenge".to_string()) 71 | .unwrap(); 72 | let compiled_codehash = H256::from(keccak256(bytecode.as_slice())); 73 | if compiled_codehash != proof.public_data.pox_challenge_codehash { 74 | return Err(Error::InternalError( 75 | "compiled codehash does not match public inputs", 76 | )); 77 | } 78 | // println!("- Compiled codehash verified with public inputs"); 79 | // println!("- Challenge codehash in public inputs"); 80 | } else { 81 | println!("Warning: Challenge artifact is not present in the proof"); 82 | } 83 | 84 | Ok(()) 85 | } 86 | 87 | // pub fn generate_yul(&self, write_to_file: bool) -> Result { 88 | // let protocol = compile( 89 | // &self.verifier_params, 90 | // &self.circuit_verifying_key, 91 | // Config::kzg().with_num_instance(self.num_instance.clone()), 92 | // ); 93 | // let vk: KzgDecidingKey = ( 94 | // self.verifier_params.get_g()[0], 95 | // self.verifier_params.g2(), 96 | // self.verifier_params.s_g2(), 97 | // ) 98 | // .into(); 99 | 100 | // let loader = EvmLoader::new::(); 101 | // let protocol = protocol.loaded(&loader); 102 | // let mut transcript = EvmTranscript::<_, Rc, _, _>::new(&loader); 103 | 104 | // let instances = transcript.load_instances(self.num_instance.clone()); 105 | // let proof = PlonkVerifier::read_proof(&vk, &protocol, &instances, &mut transcript).unwrap(); 106 | // PlonkVerifier::verify(&vk, &protocol, &instances, &proof).unwrap(); 107 | 108 | // let source = loader.solidity_code(); 109 | // if write_to_file { 110 | // let proof_path = self 111 | // .dir_path 112 | // .join(Path::new(&format!("{}_verifier.yul", self.circuit_name))); 113 | 114 | // let mut file = File::create(proof_path)?; 115 | // file.write_all(source.as_bytes())?; 116 | // } 117 | // Ok(source) 118 | // } 119 | } 120 | -------------------------------------------------------------------------------- /src/utils/halo2/srs.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::Error, utils::ipfs}; 2 | use bus_mapping::circuit_input_builder::FixedCParams; 3 | use halo2_proofs::{ 4 | halo2curves::bn256::{Bn256, Fr, G1Affine}, 5 | plonk::{keygen_pk, keygen_vk, Circuit, ProvingKey, VerifyingKey}, 6 | poly::{commitment::ParamsProver, kzg::commitment::ParamsKZG}, 7 | SerdeFormat, 8 | }; 9 | use rand_chacha::{rand_core::SeedableRng, ChaChaRng}; 10 | use std::{ 11 | fs::{remove_file, File}, 12 | path::PathBuf, 13 | }; 14 | use zkevm_circuits::super_circuit::{SuperCircuit, SuperCircuitParams}; 15 | 16 | const SERDE_FORMAT: SerdeFormat = SerdeFormat::RawBytes; 17 | 18 | #[derive(Clone)] 19 | pub struct SRS { 20 | pub general_params: ParamsKZG, 21 | pub verifier_params: ParamsKZG, 22 | pub circuit_verifying_key: VerifyingKey, 23 | pub circuit_proving_key: ProvingKey, 24 | } 25 | 26 | impl SRS { 27 | pub fn load(circuit: &SuperCircuit, degree: u32, srs_path: PathBuf) -> Self { 28 | let general_params = load_general_params(srs_path.clone(), degree); 29 | let verifier_params = general_params.verifier_params().clone(); 30 | let circuit_verifying_key = 31 | load_circuit_verifying_key(srs_path.clone(), degree, circuit, &general_params); 32 | let circuit_proving_key = load_circuit_proving_key( 33 | srs_path, 34 | degree, 35 | circuit, 36 | &general_params, 37 | &circuit_verifying_key, 38 | ); 39 | Self { 40 | general_params, 41 | verifier_params, 42 | circuit_verifying_key, 43 | circuit_proving_key, 44 | } 45 | } 46 | } 47 | 48 | pub struct VerifierSRS { 49 | pub general_params: ParamsKZG, 50 | pub verifier_params: ParamsKZG, 51 | pub circuit_verifying_key: VerifyingKey, 52 | } 53 | 54 | impl VerifierSRS { 55 | pub async fn load( 56 | srs_path: PathBuf, 57 | degree: u32, 58 | circuit_params: SuperCircuitParams, 59 | fcp: FixedCParams, 60 | ) -> Self { 61 | let general_params = read( 62 | srs_path.clone(), 63 | general_params_file_name(degree), 64 | |mut file| Ok(ParamsKZG::::read_custom(&mut file, SERDE_FORMAT)?), 65 | ) 66 | .await 67 | .unwrap(); 68 | let verifier_params = general_params.verifier_params().clone(); 69 | // let verifier_params = read( 70 | // srs_path.clone(), 71 | // verifier_params_file_name(degree), 72 | // |mut file| Ok(ParamsKZG::::read_custom(&mut file, SERDE_FORMAT)?), 73 | // ) 74 | // .await 75 | // .unwrap(); 76 | let circuit_verifying_key = read( 77 | srs_path, 78 | circuit_verifying_key_file_name(degree, fcp), 79 | |file| { 80 | Ok(VerifyingKey::::read::>( 81 | file, 82 | SERDE_FORMAT, 83 | circuit_params.clone(), 84 | )?) 85 | }, 86 | ) 87 | .await 88 | .unwrap(); 89 | Self { 90 | general_params, 91 | verifier_params, 92 | circuit_verifying_key, 93 | } 94 | } 95 | } 96 | 97 | fn general_params_file_name(degree: u32) -> String { 98 | format!("kzg_general_params_{}", degree) 99 | } 100 | 101 | // fn verifier_params_file_name(degree: u32) -> String { 102 | // format!("kzg_verifier_params_{}", degree) 103 | // } 104 | 105 | fn circuit_verifying_key_file_name(degree: u32, fcp: FixedCParams) -> String { 106 | format!("PoX_verifying_key_{}_{}", degree, circuit_params_str(fcp)) 107 | } 108 | 109 | fn circuit_proving_key_file_name(degree: u32, fcp: FixedCParams) -> String { 110 | format!("PoX_proving_key_{}_{}", degree, circuit_params_str(fcp),) 111 | } 112 | 113 | fn load_general_params(srs_path: PathBuf, degree: u32) -> ParamsKZG { 114 | read_or_gen( 115 | "general params", 116 | srs_path.join(general_params_file_name(degree)), 117 | |mut file| Ok(ParamsKZG::::read_custom(&mut file, SERDE_FORMAT)?), 118 | |mut file| { 119 | let rng = ChaChaRng::seed_from_u64(2); 120 | let general_params = ParamsKZG::::setup(degree, rng); 121 | general_params.write_custom(&mut file, SERDE_FORMAT)?; 122 | Ok(general_params) 123 | }, 124 | ) 125 | .expect("load_general_params should not fail") 126 | } 127 | 128 | // fn load_verifier_params( 129 | // srs_path: PathBuf, 130 | // degree: u32, 131 | // general_params: &ParamsKZG, 132 | // ) -> ParamsKZG { 133 | // read_or_gen( 134 | // "verifier params", 135 | // srs_path.join(verifier_params_file_name(degree)), 136 | // |mut file| Ok(ParamsKZG::::read_custom(&mut file, SERDE_FORMAT)?), 137 | // |mut file| { 138 | // let verifier_params = general_params.verifier_params().to_owned(); 139 | // verifier_params.write_custom(&mut file, SERDE_FORMAT)?; 140 | // Ok(verifier_params) 141 | // }, 142 | // ) 143 | // .expect("load_verifier_params should not fail") 144 | // } 145 | 146 | fn load_circuit_verifying_key( 147 | srs_path: PathBuf, 148 | degree: u32, 149 | circuit: &SuperCircuit, 150 | general_params: &ParamsKZG, 151 | ) -> VerifyingKey { 152 | read_or_gen( 153 | "circuit verifying key", 154 | srs_path.join(circuit_verifying_key_file_name( 155 | degree, 156 | circuit.circuits_params, 157 | )), 158 | |file| { 159 | Ok(VerifyingKey::::read::>( 160 | file, 161 | SERDE_FORMAT, 162 | circuit.params(), 163 | )?) 164 | }, 165 | |mut file| { 166 | let cvk = keygen_vk(&general_params.clone(), circuit)?; 167 | cvk.write(&mut file, SERDE_FORMAT)?; 168 | Ok(cvk) 169 | }, 170 | ) 171 | .expect("load_circuit_verifying_key should not fail") 172 | } 173 | 174 | fn load_circuit_proving_key( 175 | srs_path: PathBuf, 176 | degree: u32, 177 | circuit: &SuperCircuit, 178 | general_params: &ParamsKZG, 179 | circuit_verifying_key: &VerifyingKey, 180 | ) -> ProvingKey { 181 | read_or_gen( 182 | "circuit proving key", 183 | srs_path.join(circuit_proving_key_file_name( 184 | degree, 185 | circuit.circuits_params, 186 | )), 187 | |file| { 188 | Ok(ProvingKey::::read::>( 189 | file, 190 | SERDE_FORMAT, 191 | circuit.params(), 192 | )?) 193 | }, 194 | |mut file| { 195 | let cpk = keygen_pk(general_params, circuit_verifying_key.clone(), circuit)?; 196 | cpk.write(&mut file, SERDE_FORMAT)?; 197 | Ok(cpk) 198 | }, 199 | ) 200 | .expect("load_circuit_proving_key should not fail") 201 | } 202 | 203 | async fn read(srs_path: PathBuf, file_name: String, mut read: F) -> Result 204 | where 205 | F: FnMut(&mut File) -> Result, 206 | { 207 | let path = srs_path.join(file_name.clone()); 208 | if !path.exists() { 209 | if let Some(ipfs_hash) = get_ipfs_hash(file_name.clone()) { 210 | println!("Downloading {file_name} from IPFS"); 211 | ipfs::download_file(ipfs_hash, path.to_string_lossy().to_string()).await 212 | } 213 | } 214 | let mut file = File::open(path)?; 215 | read(&mut file) 216 | } 217 | 218 | fn read_or_gen(label: &str, path: PathBuf, mut read: F1, mut gen: F2) -> Result 219 | where 220 | F1: FnMut(&mut File) -> Result, 221 | F2: FnMut(&mut File) -> Result, 222 | { 223 | let file = File::open(path.clone()); 224 | if let Ok(mut file) = file { 225 | println!("Reading {label}..."); 226 | match read(&mut file) { 227 | Ok(result) => { 228 | return Ok(result); 229 | } 230 | Err(e) => { 231 | // Remove file and freshly create it in next step 232 | println!("Failed {e:?}"); 233 | remove_file(path.clone()) 234 | .unwrap_or_else(|_| panic!("Failed to remove file: {}", path.display())); 235 | } 236 | } 237 | } 238 | 239 | println!("Generating {label}..."); 240 | let result = gen(&mut File::create(path)?)?; 241 | Ok(result) 242 | } 243 | 244 | fn circuit_params_str(fcp: FixedCParams) -> String { 245 | format!( 246 | "{}_{}_{}_{}_{}_{}_{}_{}", 247 | fcp.max_rws, 248 | fcp.max_txs, 249 | fcp.max_calldata, 250 | fcp.max_copy_rows, 251 | fcp.max_exp_steps, 252 | fcp.max_bytecode, 253 | fcp.max_evm_rows, 254 | fcp.max_keccak_rows, 255 | ) 256 | } 257 | 258 | fn get_ipfs_hash(file_name: String) -> Option { 259 | // TODO improve this code 260 | if file_name == *"kzg_general_params_19" { 261 | Some("QmeJngu5KuP4NjCimnkZjoGHt5xUY2eSmoADiZTf6WUwHG".to_string()) 262 | } else if file_name 263 | == *"PoX_verifying_key_19_40000_1_256_40000_40000_10000_20000_50000".to_string() 264 | { 265 | Some("QmWGqxjCWrReL3WQy86g56dJ1hKY9miB91rnjLzHeeGivo".to_string()) 266 | } else { 267 | None 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/utils/helpers.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::derive_ord_xor_partial_ord)] 2 | macro_rules! hashmap { 3 | ($( $key: expr => $val: expr ),*) => {{ 4 | let mut map = ::std::collections::HashMap::new(); 5 | $( map.insert($key, $val); )* 6 | map 7 | }} 8 | } 9 | pub(crate) use hashmap; 10 | -------------------------------------------------------------------------------- /src/utils/huff.rs: -------------------------------------------------------------------------------- 1 | use eth_types::Bytes; 2 | use std::{process, str::FromStr}; 3 | 4 | pub fn compile_huff(source_path_string: String) -> Bytes { 5 | let mut cmd = process::Command::new("huffc"); 6 | cmd.arg(source_path_string); 7 | cmd.arg("-r"); 8 | cmd.args(["-e", "paris"]); // TODO put this behind a flag somehow 9 | let output = cmd.output().unwrap(); 10 | if !output.stderr.is_empty() { 11 | println!( 12 | "huffc error: {:?}", 13 | String::from_utf8(output.stderr).unwrap() 14 | ); 15 | process::exit(1); 16 | } 17 | 18 | let stdout = String::from_utf8(output.stdout).unwrap(); 19 | Bytes::from_str(stdout.as_str()).unwrap() 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/ipfs.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::File, io::Write}; 2 | 3 | use super::halo2::proof::Proof; 4 | use crate::error::Error; 5 | use pinata_sdk::{PinByFile, PinByJson, PinataApi}; 6 | use reqwest; 7 | 8 | fn pinata() -> PinataApi { 9 | PinataApi::new( 10 | // temp api key only allows pinning json, TODO allow passing own api key 11 | "d18a79ccbf06647b8d2e", 12 | "a44539bbc32ea2806a635a94070f74a9d70bcb24a2b9ef921881912f85d7c6ba", 13 | ) 14 | .unwrap() 15 | } 16 | 17 | pub async fn publish(proof: &Proof) -> Result { 18 | let pinned_object = pinata().pin_json(PinByJson::new(proof)).await?; 19 | Ok(pinned_object.ipfs_hash) 20 | } 21 | 22 | pub async fn get(hash: String) -> Result { 23 | let gateway = "https://gateway.pinata.cloud/ipfs/"; 24 | 25 | let client = reqwest::Client::new(); 26 | let res = client 27 | .get(gateway.to_owned() + hash.as_str()) 28 | .send() 29 | .await 30 | .unwrap(); 31 | 32 | let str = res.text().await.unwrap(); 33 | Ok(serde_json::from_str(str.as_str())?) 34 | } 35 | 36 | pub async fn publish_file(path: String) -> Result { 37 | let file = PinByFile::new(path); 38 | let hash = pinata().pin_file(file).await?.ipfs_hash; 39 | Ok(hash) 40 | } 41 | 42 | pub async fn download_file(hash: String, path: String) { 43 | let gateway = "https://gateway.pinata.cloud/ipfs/"; 44 | 45 | let client = reqwest::Client::new(); 46 | let res = client 47 | .get(gateway.to_owned() + hash.as_str()) 48 | .send() 49 | .await 50 | .unwrap(); 51 | 52 | let data = res.bytes().await.unwrap(); 53 | let mut file = File::create(path).unwrap(); 54 | file.write_all(&data).unwrap(); 55 | } 56 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod anvil; 2 | pub mod etherscan; 3 | pub mod geth; 4 | pub mod halo2; 5 | pub mod helpers; 6 | pub mod huff; 7 | pub mod ipfs; 8 | pub mod scaffold; 9 | pub mod solidity; 10 | -------------------------------------------------------------------------------- /src/utils/scaffold.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env::current_dir, 3 | process::{Command, Stdio}, 4 | }; 5 | 6 | const HUFF_TEMPLATE: &str = "https://github.com/zemse/proof-of-exploit-huff-template"; 7 | 8 | pub fn huff_template(project_name: &str) { 9 | use_template(HUFF_TEMPLATE, project_name); 10 | } 11 | 12 | pub fn use_template(git_url: &str, project_name: &str) { 13 | let next_dir = current_dir().unwrap().join(project_name); 14 | let next_dir = next_dir.to_str().unwrap(); 15 | run(format!("git clone --recursive {git_url} {project_name}",).as_str()); 16 | run_at(next_dir, "rm -rf .git"); 17 | run_at(next_dir, "git init"); 18 | run_at(next_dir, "git add ."); 19 | run_at(next_dir, "git commit -m init"); 20 | } 21 | 22 | pub fn run(cmd: &str) { 23 | let split: Vec<&str> = cmd.split(' ').collect(); 24 | let mut cmd = Command::new(split[0]); 25 | for arg in &split[1..] { 26 | cmd.arg(arg); 27 | } 28 | cmd.stdout(Stdio::piped()).stdout(Stdio::piped()); 29 | let mut child = cmd.spawn().unwrap(); 30 | child.wait().unwrap(); 31 | } 32 | 33 | pub fn run_at(dir: &str, cmd: &str) { 34 | let split: Vec<&str> = cmd.split(' ').collect(); 35 | 36 | let mut cmd = Command::new(split[0]); 37 | cmd.current_dir(dir); 38 | for arg in &split[1..] { 39 | cmd.arg(arg); 40 | } 41 | cmd.stdout(Stdio::piped()).stdout(Stdio::piped()); 42 | let mut child = cmd.spawn().unwrap(); 43 | child.wait().unwrap(); 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/solidity.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use eth_types::Bytes; 3 | use regex::Regex; 4 | use semver::Version; 5 | use serde::{Deserialize, Serialize}; 6 | use serde_json::Value; 7 | use std::{ 8 | collections::HashMap, 9 | fs, 10 | io::Write, 11 | path::{Path, PathBuf}, 12 | process::{self, Command, Stdio}, 13 | str::FromStr, 14 | }; 15 | 16 | use super::helpers::hashmap; 17 | 18 | pub fn compile_solidity(source_path_string: String, match_contract_name: &str) -> Bytes { 19 | let mut cmd = process::Command::new("solc"); 20 | cmd.arg(source_path_string); 21 | cmd.arg("--combined-json"); 22 | cmd.arg("bin-runtime"); 23 | let output = cmd.output().unwrap(); 24 | let output = if !output.stdout.is_empty() { 25 | String::from_utf8(output.stdout).unwrap() 26 | } else { 27 | String::from_utf8(output.stderr).unwrap() 28 | }; 29 | let solc_json_output: Value = serde_json::from_str(output.as_str()).unwrap_or_else(|_| { 30 | println!("solc error: {output}"); 31 | process::exit(1); 32 | }); 33 | let compiled_bytecode = 'cb: { 34 | let regx = Regex::new(r"(?m)^([^:]+):(.+)$").unwrap(); 35 | for (key, val) in solc_json_output 36 | .as_object() 37 | .unwrap() 38 | .get("contracts") 39 | .unwrap() 40 | .as_object() 41 | .unwrap() 42 | { 43 | let contract_name = regx 44 | .captures(key.as_str()) 45 | .unwrap() 46 | .get(2) 47 | .unwrap() 48 | .as_str(); 49 | if contract_name == match_contract_name { 50 | break 'cb val 51 | .as_object() 52 | .unwrap() 53 | .get("bin-runtime") 54 | .unwrap() 55 | .as_str() 56 | .unwrap(); 57 | } 58 | } 59 | println!("Could not find a Challenge solidity contract"); 60 | process::exit(1); 61 | }; 62 | Bytes::from_str(compiled_bytecode).unwrap() 63 | } 64 | 65 | #[derive(Clone, Debug, Serialize, Deserialize)] 66 | struct Input { 67 | language: String, 68 | sources: HashMap, 69 | settings: InputSettings, 70 | } 71 | 72 | #[derive(Clone, Debug, Serialize, Deserialize)] 73 | struct InputSettings { 74 | optimizer: InputSettingsOptimizer, 75 | #[serde(rename = "evmVersion")] 76 | evm_version: EvmVersion, 77 | #[serde(rename = "outputSelection")] 78 | output_selection: HashMap>>, 79 | } 80 | 81 | #[derive(Clone, Debug, Serialize, Deserialize)] 82 | struct InputSettingsOptimizer { 83 | enabled: bool, 84 | runs: usize, 85 | } 86 | 87 | #[derive(Clone, Debug, Serialize, Deserialize)] 88 | struct InputSource { 89 | content: String, 90 | } 91 | 92 | #[derive(Clone, Debug, Serialize, Deserialize)] 93 | #[serde(rename_all = "snake_case")] 94 | enum EvmVersion { 95 | Homestead, 96 | TangerineWhistle, 97 | SpuriousDragon, 98 | Byzantium, 99 | Constantinople, 100 | Petersburg, 101 | Istanbul, 102 | Berlin, 103 | London, 104 | Paris, 105 | Shanghai, 106 | } 107 | 108 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 109 | struct Output { 110 | contracts: HashMap>, 111 | } 112 | 113 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 114 | struct OutputContract { 115 | evm: OutputContractEvm, 116 | } 117 | 118 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 119 | struct OutputContractEvm { 120 | #[serde(rename = "deployedBytecode")] 121 | deployed_bytecode: OutputBytecode, 122 | } 123 | 124 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 125 | struct OutputBytecode { 126 | object: Bytes, 127 | } 128 | 129 | // TODO support multiple files 130 | fn file_to_artifact(source_path_string: String) -> Result { 131 | // TODO when there are multiple files, find the common initial path and strip it 132 | // For a single file, the stripped path is the file name itself 133 | let path = PathBuf::from(source_path_string.clone()); 134 | let stripped_path = path 135 | .file_name() 136 | .expect("solidity input should be a file") 137 | .to_string_lossy() 138 | .to_string(); 139 | 140 | Ok(Input { 141 | language: "Solidity".to_string(), 142 | sources: hashmap![stripped_path => InputSource { 143 | content: fs::read_to_string(source_path_string)?, 144 | }], 145 | settings: InputSettings { 146 | optimizer: InputSettingsOptimizer { 147 | enabled: true, 148 | runs: 200, 149 | }, 150 | evm_version: EvmVersion::Paris, 151 | output_selection: hashmap!["*".into() => hashmap!["*".into() => vec!["evm.deployedBytecode.object".into()]]], 152 | }, 153 | }) 154 | } 155 | 156 | fn compile_artifact(input: &Input) -> Result { 157 | let mut solc = Command::new("solc") 158 | .args(["--standard-json"]) 159 | .stdin(Stdio::piped()) 160 | .stdout(Stdio::piped()) 161 | .stderr(Stdio::piped()) 162 | .spawn()?; 163 | 164 | if let Some(mut stdin) = solc.stdin.take() { 165 | let input = serde_json::to_string(&input)?; 166 | stdin.write_all(input.as_bytes())?; 167 | } 168 | 169 | let output = solc.wait_with_output()?; 170 | if let Ok(output) = serde_json::from_str(std::str::from_utf8(&output.stdout).unwrap()) { 171 | Ok(output) 172 | } else { 173 | if !output.stdout.is_empty() { 174 | println!( 175 | "solc stdout: {:?}", 176 | std::str::from_utf8(&output.stdout).unwrap() 177 | ); 178 | } 179 | if !output.stderr.is_empty() { 180 | println!( 181 | "solc stderr {:?}", 182 | std::str::from_utf8(&output.stderr).unwrap() 183 | ); 184 | } 185 | Err(Error::InternalError("unexpected solc output")) 186 | } 187 | } 188 | 189 | #[derive(Clone, Debug, Serialize, Deserialize)] 190 | pub struct Artifact { 191 | solc_version: Version, 192 | input: Input, 193 | output: Output, 194 | } 195 | 196 | impl Artifact { 197 | pub fn from_source(source_path_string: String) -> Self { 198 | let solc_version = svm_lib::current_version().unwrap().unwrap(); 199 | let input = file_to_artifact(source_path_string).unwrap(); 200 | let output = compile_artifact(&input).unwrap(); 201 | Artifact { 202 | solc_version, 203 | input, 204 | output, 205 | } 206 | } 207 | 208 | pub async fn verify_compilation(&self) -> Result<(), Error> { 209 | let user_version = svm_lib::current_version().unwrap().unwrap(); 210 | if user_version != self.solc_version { 211 | let installed_versions = svm_lib::installed_versions().unwrap(); 212 | if !installed_versions.contains(&self.solc_version) { 213 | println!("Installing solc version {}...", self.solc_version); 214 | svm_lib::install(&self.solc_version).await.unwrap(); 215 | } 216 | // switch solc to proof's solc version 217 | svm_lib::use_version(&self.solc_version).unwrap(); 218 | } 219 | let fresh_compilation_output = compile_artifact(&self.input)?; 220 | if user_version != self.solc_version { 221 | // switch solc to user's original solc version 222 | svm_lib::use_version(&user_version).unwrap(); 223 | } 224 | if fresh_compilation_output != self.output { 225 | return Err(Error::InternalError("compilation not matching")); 226 | } 227 | Ok(()) 228 | } 229 | 230 | pub fn get_deployed_bytecode(&self, search_contract_name: String) -> Result { 231 | for (_, contracts) in self.output.contracts.iter() { 232 | for (contract_name, contract) in contracts.iter() { 233 | if &search_contract_name == contract_name { 234 | return Ok(contract.evm.deployed_bytecode.object.clone()); 235 | } 236 | } 237 | } 238 | Err(Error::InternalError("could not find contract")) 239 | } 240 | 241 | pub fn unpack(&self, unpack_dir: String) { 242 | for (path, source) in self.input.sources.iter() { 243 | let prefix = Path::new(&unpack_dir); 244 | let path = Path::new(path); 245 | let path = prefix.join(path); 246 | if let Some(parent) = path.parent() { 247 | fs::create_dir_all(parent).unwrap(); 248 | } 249 | let mut file = fs::File::create(path).unwrap(); 250 | file.write_all(source.content.as_bytes()).unwrap(); 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/verification.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | cli::VerifyArgs, 3 | utils::{self, halo2::real_verifier::RealVerifier}, 4 | }; 5 | use semver::Version; 6 | use std::{process, str::FromStr}; 7 | 8 | pub async fn handle_verify(args: VerifyArgs) { 9 | let my_version = Version::from_str(env!("CARGO_PKG_VERSION")).unwrap(); 10 | if my_version < args.proof.version { 11 | println!( 12 | "This proof was generated using a newer version of Proof of Exploit. Please upgrade to version v{} or latest if you are facing issues.\n", 13 | args.proof.version 14 | ); 15 | } 16 | 17 | let verifier = RealVerifier::load_srs(args.srs_path, &args.proof).await; 18 | if let Err(error) = verifier.verify(&args.proof).await { 19 | println!("Proof verification failed: {:?}", error); 20 | process::exit(1); 21 | } else { 22 | println!("Proof verification success!\n"); 23 | 24 | if let Some(summary) = args.proof.summary { 25 | println!("Summary: {}\n", summary); 26 | } 27 | 28 | println!("Public Inputs:"); 29 | println!(" Chain Id: {:?}", args.proof.public_data.chain_id,); 30 | println!( 31 | " Block: {:?} {}", 32 | args.proof.public_data.block_constants.number - 1, 33 | utils::etherscan::block_url( 34 | args.proof.public_data.chain_id.as_u64(), 35 | args.proof.public_data.block_constants.number.as_u64() - 1 36 | ) 37 | ); 38 | println!(" State Root: {:?}", args.proof.public_data.prev_state_root); 39 | println!( 40 | " Challenge Codehash: {:?}", 41 | args.proof.public_data.pox_challenge_codehash, 42 | ); 43 | println!( 44 | " Exploit Stipend: {} ether", 45 | ethers::utils::format_ether(args.proof.public_data.pox_exploit_balance) 46 | .parse::() 47 | .unwrap() 48 | ); 49 | } 50 | 51 | if let Some(unpack_dir) = args.unpack_dir { 52 | if let Some(challenge_artifact) = args.proof.challenge_artifact { 53 | println!("\nUnpacking challenge source code..."); 54 | challenge_artifact.unpack(unpack_dir); 55 | println!("Done!"); 56 | } else { 57 | println!("\nError: Proof does not contain challenge source code to unpack."); 58 | } 59 | } else { 60 | println!("\nTo view challenge source code, use --unpack flag."); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/wasm.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::halo2curves::bn256::{Bn256, Fr, G1Affine}; 2 | use halo2_proofs::plonk::{verify_proof, VerifyingKey}; 3 | use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; 4 | use halo2_proofs::poly::kzg::multiopen::VerifierSHPLONK; 5 | use halo2_proofs::poly::kzg::strategy::SingleStrategy; 6 | use halo2_proofs::transcript::{Blake2bRead, Challenge255, TranscriptReadBuffer}; 7 | use halo2_proofs::SerdeFormat; 8 | use js_sys::Uint8Array; 9 | use std::io::BufReader; 10 | use wasm_bindgen::prelude::*; 11 | use zkevm_circuits::super_circuit::{SuperCircuit, SuperCircuitParams}; 12 | 13 | const SERDE_FORMAT: SerdeFormat = SerdeFormat::RawBytes; 14 | 15 | #[wasm_bindgen] 16 | pub fn verify( 17 | proof_js: JsValue, 18 | params_js: JsValue, 19 | vk_js: JsValue, 20 | instance_0: JsValue, 21 | instance_1: JsValue, 22 | ) -> bool { 23 | console_error_panic_hook::set_once(); 24 | 25 | #[allow(deprecated)] 26 | let proof_vec = proof_js.into_serde::>().unwrap(); 27 | let params_vec = Uint8Array::new(¶ms_js).to_vec(); 28 | let vk_vec = Uint8Array::new(&vk_js).to_vec(); 29 | let mut instance_0 = instance_0.into_serde::<[u8; 32]>().unwrap(); 30 | let mut instance_1 = instance_1.into_serde::<[u8; 32]>().unwrap(); 31 | 32 | let params = 33 | ParamsKZG::::read_custom(&mut BufReader::new(¶ms_vec[..]), SERDE_FORMAT) 34 | .unwrap(); 35 | 36 | let vk = VerifyingKey::::read::, SuperCircuit>( 37 | &mut BufReader::new(&vk_vec[..]), 38 | SERDE_FORMAT, 39 | SuperCircuitParams { 40 | mock_randomness: Fr::from(0x100), 41 | }, 42 | ) 43 | .unwrap(); 44 | 45 | let strategy = SingleStrategy::new(¶ms); 46 | let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof_vec[..]); 47 | 48 | instance_0.reverse(); 49 | instance_1.reverse(); 50 | 51 | let instances = vec![ 52 | vec![ 53 | Fr::from_bytes(&instance_0).unwrap(), 54 | Fr::from_bytes(&instance_1).unwrap(), 55 | ], 56 | vec![], 57 | ]; 58 | let instances = instances.iter().map(|v| &v[..]).collect::>(); 59 | 60 | verify_proof::< 61 | KZGCommitmentScheme, 62 | VerifierSHPLONK<'_, Bn256>, 63 | Challenge255, 64 | Blake2bRead<&[u8], G1Affine, Challenge255>, 65 | SingleStrategy<'_, Bn256>, 66 | >(¶ms, &vk, strategy, &[&instances], &mut transcript) 67 | .is_ok() 68 | } 69 | 70 | #[wasm_bindgen] 71 | extern "C" { 72 | fn alert(s: &str); 73 | 74 | #[wasm_bindgen(js_namespace = console)] 75 | fn log(s: &str); 76 | } 77 | -------------------------------------------------------------------------------- /src/witness/inputs_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::Error, 3 | utils::{ 4 | anvil::{conversion::ConversionReverse, types::zkevm_types::*, AnvilClient}, 5 | geth::GethClient, 6 | }, 7 | }; 8 | pub use bus_mapping::{ 9 | circuit_input_builder::{ 10 | build_state_code_db, gen_state_access_trace, Access, AccessSet, AccessValue, Block, 11 | CircuitInputBuilder, CircuitsParams, FixedCParams, PoxInputs, 12 | }, 13 | operation::RW, 14 | state_db::{CodeDB, StateDB}, 15 | POX_CHALLENGE_ADDRESS, 16 | }; 17 | use eth_types::Fr; 18 | use ethers::utils::keccak256; 19 | use futures::future; 20 | use partial_mpt::StateTrie; 21 | use std::collections::HashMap; 22 | use zkevm_circuits::witness::block_convert; 23 | 24 | pub struct BuilderClient { 25 | pub anvil: AnvilClient, 26 | pub geth: Option, 27 | pub chain_id: eth_types::Word, 28 | pub circuits_params: FixedCParams, 29 | } 30 | 31 | pub fn get_state_accesses( 32 | block: &EthBlockFull, 33 | geth_traces: &[eth_types::GethExecTrace], 34 | ) -> Result { 35 | let mut block_access_trace = vec![Access::new( 36 | None, 37 | RW::WRITE, 38 | AccessValue::Account { 39 | address: block 40 | .author 41 | .ok_or(Error::InternalError("Incomplete block"))?, 42 | }, 43 | )]; 44 | for (tx_index, tx) in block.transactions.iter().enumerate() { 45 | let geth_trace = &geth_traces[tx_index]; 46 | let tx_access_trace = gen_state_access_trace(block, tx, geth_trace)?; 47 | block_access_trace.extend(tx_access_trace); 48 | } 49 | 50 | // the Challenge address has no code on the mainnet, however in the private block we assign it 51 | block_access_trace.push(Access::new( 52 | None, 53 | RW::WRITE, 54 | AccessValue::Code { 55 | address: POX_CHALLENGE_ADDRESS, 56 | }, 57 | )); 58 | 59 | Ok(AccessSet::from(block_access_trace)) 60 | } 61 | 62 | #[allow(dead_code)] 63 | impl BuilderClient { 64 | pub async fn default() -> Result { 65 | Self::from_circuits_params(FixedCParams::default()).await 66 | } 67 | 68 | pub async fn from_config( 69 | circuits_params: FixedCParams, 70 | eth_rpc_url: Option, 71 | geth_rpc_url: Option, 72 | fork_block_number: Option, 73 | ) -> Result { 74 | let anvil = AnvilClient::setup(eth_rpc_url.clone(), fork_block_number).await; 75 | let geth = geth_rpc_url.or(eth_rpc_url).map(GethClient::new); 76 | Self::new(anvil, geth, circuits_params) 77 | } 78 | 79 | pub async fn from_circuits_params(circuits_params: FixedCParams) -> Result { 80 | let anvil = AnvilClient::default().await; 81 | Self::new(anvil, None, circuits_params) 82 | } 83 | 84 | pub fn new( 85 | anvil: AnvilClient, 86 | geth: Option, 87 | circuits_params: FixedCParams, 88 | ) -> Result { 89 | if let Some(chain_id) = anvil.eth_chain_id()? { 90 | Ok(Self { 91 | anvil, 92 | geth, 93 | chain_id: Word::from(chain_id.as_usize()), 94 | circuits_params, 95 | }) 96 | } else { 97 | Err(Error::InternalError( 98 | "Unable to get chain id from ETH client", 99 | )) 100 | } 101 | } 102 | 103 | pub async fn gen_witness( 104 | &self, 105 | block_number: usize, 106 | pox_inputs: PoxInputs, 107 | use_geth_trace: bool, 108 | ) -> Result, Error> { 109 | let (circuit_input_builder, _) = self 110 | .gen_inputs(block_number, pox_inputs, use_geth_trace) 111 | .await?; 112 | Ok(block_convert::(&circuit_input_builder)?) 113 | } 114 | 115 | pub async fn gen_inputs( 116 | &self, 117 | block_number: usize, 118 | pox_inputs: PoxInputs, 119 | use_geth_trace: bool, 120 | ) -> Result<(CircuitInputBuilder, EthBlockFull), Error> { 121 | let (mut block, traces, history_hashes, prev_state_root) = self 122 | .get_block(block_number, pox_inputs.clone(), use_geth_trace) 123 | .await?; 124 | let access_set = get_state_accesses(&block, &traces)?; 125 | let (proofs, codes, new_state_root) = self.get_state(block_number, access_set).await?; 126 | if block.state_root.is_zero() { 127 | block.state_root = new_state_root; 128 | } 129 | let (state_db, code_db) = build_state_code_db(proofs, codes); 130 | let builder = self.gen_inputs_from_state( 131 | state_db, 132 | code_db, 133 | &block, 134 | &traces, 135 | history_hashes, 136 | prev_state_root, 137 | pox_inputs, 138 | )?; 139 | Ok((builder, block)) 140 | } 141 | 142 | #[allow(clippy::too_many_arguments)] 143 | fn gen_inputs_from_state( 144 | &self, 145 | sdb: StateDB, 146 | code_db: CodeDB, 147 | eth_block: &EthBlockFull, 148 | geth_traces: &[GethExecTrace], 149 | history_hashes: Vec, 150 | prev_state_root: Word, 151 | pox_inputs: PoxInputs, 152 | ) -> Result, Error> { 153 | let block = Block::new( 154 | self.chain_id, 155 | history_hashes, 156 | prev_state_root, 157 | eth_block, 158 | pox_inputs, 159 | )?; 160 | let mut builder = CircuitInputBuilder::new(sdb, code_db, block, self.circuits_params); 161 | builder.handle_block(eth_block, geth_traces)?; 162 | Ok(builder) 163 | } 164 | 165 | async fn get_block( 166 | &self, 167 | block_number: usize, 168 | pox_inputs: PoxInputs, 169 | use_geth_trace: bool, 170 | ) -> Result<(EthBlockFull, Vec, Vec, Word), Error> { 171 | let (block, traces) = self 172 | .get_block_traces(block_number, pox_inputs, use_geth_trace) 173 | .await?; 174 | 175 | // fetch up to 256 blocks 176 | let n_blocks = std::cmp::min(256, block_number); 177 | let mut futures = Vec::default(); 178 | for i in 1..n_blocks { 179 | let header_future = self.anvil.block_by_number(block_number - i); 180 | futures.push(header_future); 181 | } 182 | 183 | let mut prev_state_root: Option = None; 184 | let mut history_hashes = Vec::default(); 185 | let results = future::join_all(futures).await; 186 | for result in results { 187 | let header = result?.expect("parent block not found"); 188 | 189 | // set the previous state root 190 | if prev_state_root.is_none() { 191 | prev_state_root = Some(h256_to_u256(header.state_root)); 192 | } 193 | 194 | // latest block hash is the last item 195 | let block_hash = header 196 | .hash 197 | .ok_or(Error::InternalError("Incomplete block"))?; 198 | history_hashes.push(h256_to_u256(block_hash)); 199 | } 200 | 201 | Ok(( 202 | block, 203 | traces, 204 | history_hashes, 205 | prev_state_root.unwrap_or_default(), 206 | )) 207 | } 208 | 209 | async fn get_block_traces( 210 | &self, 211 | block_number: usize, 212 | pox_inputs: PoxInputs, 213 | use_geth_trace: bool, 214 | ) -> Result<(EthBlockFull, Vec), Error> { 215 | let block = self 216 | .anvil 217 | .block_by_number_full(block_number) 218 | .await? 219 | .expect("block not found"); 220 | 221 | let mut traces = Vec::default(); 222 | for tx in &block.transactions { 223 | let anvil_trace = if !use_geth_trace { 224 | self.anvil 225 | .debug_trace_transaction( 226 | tx.hash, 227 | GethDebugTracingOptions { 228 | enable_memory: Some(false), 229 | disable_stack: Some(false), 230 | disable_storage: Some(false), 231 | enable_return_data: Some(true), 232 | tracer: None, 233 | tracer_config: None, 234 | timeout: None, 235 | }, 236 | ) 237 | .await? 238 | } else { 239 | self.geth 240 | .clone() 241 | .unwrap() 242 | .simulate_exploit( 243 | tx, 244 | pox_inputs.challenge_bytecode.clone(), 245 | pox_inputs.exploit_bytecode.clone(), 246 | pox_inputs.exploit_balance, 247 | ) 248 | .await? 249 | }; 250 | traces.push(anvil_trace); 251 | } 252 | 253 | Ok((block, traces)) 254 | } 255 | 256 | async fn get_state( 257 | &self, 258 | block_number: usize, 259 | access_set: AccessSet, 260 | ) -> Result<(Vec, HashMap>, H256), Error> { 261 | let mut proofs = Vec::default(); 262 | for (address, key_set) in access_set.state.clone() { 263 | let mut keys: Vec = key_set.iter().cloned().collect(); 264 | keys.sort(); 265 | let proof = self 266 | .anvil 267 | .get_proof(address, keys, Some(block_number - 1)) 268 | .await 269 | .unwrap(); 270 | proofs.push(proof); 271 | } 272 | let mut codes: HashMap> = HashMap::default(); 273 | for address in access_set.code.clone() { 274 | let code = self 275 | .anvil 276 | .get_code(address, Some(block_number - 1)) 277 | .await 278 | .unwrap(); 279 | codes.insert(address, code.to_vec()); 280 | } 281 | 282 | // let new_state_root = self 283 | // .gen_state_root(block_number, access_set, &proofs) 284 | // .await?; 285 | Ok((proofs, codes, H256::zero())) 286 | } 287 | 288 | async fn gen_state_root( 289 | &self, 290 | block_number: usize, 291 | access_set: AccessSet, 292 | proofs: &Vec, 293 | ) -> Result { 294 | let mut trie = StateTrie::default(); 295 | 296 | for proof in proofs { 297 | trie.load_proof(proof.to_anvil_type())?; 298 | } 299 | 300 | let mut addresses = Vec::
::default(); 301 | 302 | for (address, key_set) in access_set.state.clone() { 303 | for key in key_set { 304 | let new_value = self 305 | .anvil 306 | .get_storage_at(address, key, Some(block_number)) 307 | .await?; 308 | trie.set_storage_value( 309 | address.to_anvil_type(), 310 | key.to_anvil_type(), 311 | h256_to_u256(new_value).to_anvil_type(), 312 | )?; 313 | } 314 | addresses.push(address); 315 | } 316 | for address in access_set.code.clone() { 317 | if !addresses.contains(&address) { 318 | addresses.push(address); 319 | } 320 | } 321 | for address in addresses { 322 | let new_code = self 323 | .anvil 324 | .get_code(address, Some(block_number)) 325 | .await 326 | .unwrap(); 327 | let new_code_hash = H256::from(keccak256(new_code)); 328 | let new_balance = self.anvil.get_balance(address, Some(block_number)).await?; 329 | let new_nonce = self.anvil.get_nonce(address, Some(block_number)).await?; 330 | 331 | let mut account_data = trie.account_trie.get(address.to_anvil_type())?; 332 | account_data.balance = new_balance.to_anvil_type(); 333 | account_data.code_hash = new_code_hash.to_anvil_type(); 334 | account_data.nonce = new_nonce.to_anvil_type(); 335 | trie.account_trie 336 | .set(address.to_anvil_type(), account_data)?; 337 | } 338 | 339 | Ok(trie.root().unwrap()) 340 | } 341 | } 342 | 343 | #[cfg(test)] 344 | mod tests { 345 | use super::BuilderClient; 346 | use crate::utils::anvil::AnvilClient; 347 | use bus_mapping::circuit_input_builder::{FixedCParams, PoxInputs}; 348 | 349 | #[tokio::test] 350 | async fn test() { 351 | let anvil = AnvilClient::setup(None, None).await; 352 | let bc = BuilderClient::new(anvil, None, FixedCParams::default()).unwrap(); 353 | assert_eq!(bc.chain_id.as_usize(), 31337); 354 | 355 | let hash = bc 356 | .anvil 357 | .fund_wallet( 358 | "0x2CA4c197AE776f675A114FBCB0B03Be845f0316d" 359 | .parse() 360 | .unwrap(), 361 | ) 362 | .await 363 | .unwrap(); 364 | 365 | loop { 366 | if let Some(tx) = bc.anvil.transaction_by_hash(hash).await.unwrap() { 367 | if let Some(block_number) = tx.block_number { 368 | let (block, traces) = bc 369 | .get_block_traces(block_number.as_usize(), PoxInputs::default(), false) 370 | .await 371 | .unwrap(); 372 | assert_eq!(block.transactions.len(), 1); 373 | assert_eq!(traces.len(), 1); 374 | break; 375 | } else { 376 | bc.anvil.mine_one().await; 377 | std::thread::sleep(std::time::Duration::from_secs(1)); 378 | } 379 | } else { 380 | panic!("transaction not available"); 381 | } 382 | } 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /src/witness/mod.rs: -------------------------------------------------------------------------------- 1 | mod inputs_builder; 2 | 3 | use crate::{ 4 | cli::ProveArgs, 5 | constants::{MAX_CALLDATA, MAX_TXS, RANDOMNESS}, 6 | utils::{ 7 | anvil::{conversion::Conversion, types::anvil_types}, 8 | halo2::real_prover::RealProver, 9 | ipfs, 10 | }, 11 | witness::inputs_builder::BuilderClient, 12 | }; 13 | use bus_mapping::{ 14 | circuit_input_builder::{FixedCParams, PoxInputs}, 15 | POX_CHALLENGE_ADDRESS, POX_EXPLOIT_ADDRESS, 16 | }; 17 | use core::slice::SlicePattern; 18 | use eth_types::{keccak256, Fr, U256, U64}; 19 | use ethers::{ 20 | signers::{LocalWallet, Signer}, 21 | types::{transaction::eip2718::TypedTransaction, NameOrAddress, TransactionRequest}, 22 | utils::hex, 23 | }; 24 | use halo2_proofs::dev::MockProver; 25 | use std::{ 26 | path::PathBuf, 27 | process, 28 | str::FromStr, 29 | time::{SystemTime, UNIX_EPOCH}, 30 | }; 31 | use zkevm_circuits::{ 32 | super_circuit::SuperCircuit, 33 | util::{log2_ceil, SubCircuit}, 34 | }; 35 | 36 | pub struct Witness { 37 | k: u32, 38 | instance: Vec>, 39 | circuit: SuperCircuit, 40 | } 41 | 42 | impl Witness { 43 | pub async fn gen(args: &ProveArgs) -> Witness { 44 | let challenge_bytecode = args 45 | .challenge_artifact 46 | .get_deployed_bytecode("Challenge".to_string()) 47 | .unwrap(); 48 | 49 | let builder = BuilderClient::from_config( 50 | FixedCParams { 51 | max_rws: args.max_rws, 52 | max_txs: MAX_TXS, 53 | max_calldata: MAX_CALLDATA, 54 | max_copy_rows: args.max_copy_rows, 55 | max_exp_steps: args.max_exp_steps, 56 | max_bytecode: args.max_bytecode, 57 | max_evm_rows: args.max_evm_rows, 58 | max_keccak_rows: args.max_keccak_rows, 59 | }, 60 | Some(args.rpc.clone()), 61 | args.geth_rpc.clone(), 62 | args.block, 63 | ) 64 | .await 65 | .unwrap(); 66 | 67 | let chain_id = builder.anvil.eth_chain_id().unwrap().unwrap(); 68 | let block_number = builder.anvil.block_number().unwrap(); 69 | println!("Anvil initialized with chain_id: {chain_id:?}, block_number: {block_number:?}"); 70 | 71 | // updating challenge bytecode in local mainnet fork chain 72 | builder 73 | .anvil 74 | .set_code(POX_CHALLENGE_ADDRESS, challenge_bytecode.clone()) 75 | .await 76 | .unwrap(); 77 | // updating exploit bytecode in local mainnet fork chain 78 | builder 79 | .anvil 80 | .set_code(POX_EXPLOIT_ADDRESS, args.exploit_bytecode.clone()) 81 | .await 82 | .unwrap(); 83 | 84 | let exploit_balance_before = builder 85 | .anvil 86 | .get_balance(POX_EXPLOIT_ADDRESS, args.block) 87 | .await 88 | .unwrap(); 89 | builder 90 | .anvil 91 | .set_balance(POX_EXPLOIT_ADDRESS, args.exploit_balance) 92 | .await 93 | .unwrap(); 94 | 95 | let signer = LocalWallet::from_str( 96 | "0x0000000000000000000000000000000000000000000000000000000000000001", 97 | ) 98 | .unwrap(); 99 | 100 | // generate transaction request 101 | let tx_req_estimate = anvil_types::EthTransactionRequest { 102 | from: Some(signer.address()), 103 | to: Some(POX_CHALLENGE_ADDRESS), 104 | gas_price: Some(U256::zero()), 105 | max_fee_per_gas: None, 106 | max_priority_fee_per_gas: None, 107 | gas: args.gas.or(Some(1_000_000)).map(U256::from), 108 | value: Some(U256::zero()), 109 | data: Some(anvil_types::Bytes::from_str("0xb0d691fe").unwrap()), 110 | nonce: Some( 111 | builder 112 | .anvil 113 | .get_nonce(signer.address(), Some(block_number)) 114 | .await 115 | .unwrap(), 116 | ), 117 | chain_id: Some(chain_id.as_u64().into()), 118 | access_list: None, 119 | transaction_type: None, 120 | }; 121 | let mut tx_req_sign = TypedTransaction::Legacy(TransactionRequest { 122 | from: tx_req_estimate.from, 123 | to: tx_req_estimate.to.map(NameOrAddress::Address), 124 | gas_price: tx_req_estimate.gas_price, 125 | gas: tx_req_estimate.gas, 126 | value: tx_req_estimate.value, 127 | data: tx_req_estimate.data.clone(), 128 | nonce: tx_req_estimate.nonce, 129 | chain_id: tx_req_estimate.chain_id, 130 | }); 131 | 132 | // check for reverts and panic out 133 | let gas_estimate = builder 134 | .anvil 135 | .estimate_gas(tx_req_estimate, None) 136 | .await 137 | .unwrap(); 138 | tx_req_sign.set_gas(args.gas.map(U256::from).unwrap_or(gas_estimate)); 139 | 140 | let signature = signer.sign_transaction_sync(&tx_req_sign).unwrap(); 141 | let raw_tx = tx_req_sign.rlp_signed(&signature); 142 | 143 | // confirm the tx on the local block 144 | let hash = builder 145 | .anvil 146 | .send_raw_transaction(raw_tx.to_zkevm_type()) 147 | .await 148 | .unwrap(); 149 | builder.anvil.wait_for_transaction(hash).await.unwrap(); 150 | 151 | let rc = builder 152 | .anvil 153 | .transaction_receipt(hash) 154 | .await 155 | .unwrap() 156 | .unwrap(); 157 | 158 | println!("Gas consumed: {}", rc.gas_used.unwrap()); 159 | 160 | if rc.status.unwrap() != U64::from(1) { 161 | // TODO make sure that storage is also updated and not just tx is successful 162 | // TODO make sure that storage update with reversion does not pass the lookup check 163 | println!("Error: Exploit transaction is not successful."); 164 | process::exit(1); 165 | } 166 | 167 | println!("Tx confirmed on Anvil: {}", hex::encode_prefixed(hash)); 168 | 169 | println!("Generating Witness..."); 170 | 171 | let tx = builder 172 | .anvil 173 | .transaction_by_hash(hash) 174 | .await 175 | .unwrap() 176 | .unwrap(); 177 | 178 | let mut witness = builder 179 | .gen_witness( 180 | tx.block_number.unwrap().as_usize(), 181 | PoxInputs { 182 | challenge_codehash: keccak256(challenge_bytecode.as_slice()).into(), 183 | challenge_bytecode, 184 | exploit_codehash: keccak256(args.exploit_bytecode.as_slice()).into(), 185 | exploit_bytecode: args.exploit_bytecode.clone(), 186 | exploit_balance: args.exploit_balance, 187 | exploit_balance_before, 188 | }, 189 | args.geth_rpc.is_some(), 190 | ) 191 | .await 192 | .unwrap(); 193 | witness.randomness = Fr::from(RANDOMNESS); 194 | 195 | println!("Witness generated!"); 196 | 197 | let (_, rows_needed) = SuperCircuit::::min_num_rows_block(&witness); 198 | let circuit = SuperCircuit::::new_from_block(&witness); 199 | let k = log2_ceil(64 + rows_needed); 200 | let instance = circuit.instance(); 201 | 202 | // println!("Instances: {instance:?}"); 203 | 204 | Witness { 205 | k, 206 | instance, 207 | circuit, 208 | } 209 | } 210 | 211 | pub fn assert(self) { 212 | println!("Running MockProver"); 213 | let prover = MockProver::run(self.k, &self.circuit, self.instance).unwrap(); 214 | println!("Verifying constraints"); 215 | prover.assert_satisfied_par(); 216 | println!("Success!"); 217 | } 218 | 219 | pub async fn prove(self, args: ProveArgs) { 220 | println!("Running RealProver"); 221 | let mut prover = RealProver::from(self.circuit, self.k, args.srs_path.clone()); 222 | 223 | println!("Generating proof..."); 224 | let mut proof = prover.prove().unwrap(); 225 | proof.challenge_artifact = Some(args.challenge_artifact); 226 | 227 | let proof_out_path = args.proof_out_path.unwrap_or(format!( 228 | "proof_{}.json", 229 | SystemTime::now() 230 | .duration_since(UNIX_EPOCH) 231 | .unwrap() 232 | .as_secs() 233 | )); 234 | 235 | let proof_path = PathBuf::from(proof_out_path); 236 | println!("Writing proof to {}", proof_path.display()); 237 | proof.write_to_file(&proof_path).unwrap(); 238 | println!("Success!"); 239 | 240 | // sanity check 241 | let verifier = prover.verifier(); 242 | verifier.verify(&proof).await.unwrap(); 243 | 244 | if args.ipfs { 245 | let hash = ipfs::publish(&proof).await.unwrap(); 246 | println!("Published proof to ipfs: {}", hash); 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /wasm_build.sh: -------------------------------------------------------------------------------- 1 | if RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+mutable-globals' \ 2 | wasm-pack build --target web --no-default-features --features \ 3 | wasm -Z build-std=panic_abort,std; 4 | then 5 | echo "\nBuild success!" 6 | du -h pkg/proof_of_exploit_bg.wasm 7 | else 8 | echo "\nBuild failed because of above error" 9 | exit 1 10 | fi 11 | 12 | 13 | if ! [ -z "$1" ] 14 | then 15 | rm -rf $1/wasm 16 | mkdir $1/wasm 17 | cp -r pkg/. $1/wasm/ 18 | rm $1/wasm/.gitignore 19 | rm $1/wasm/README.md 20 | echo "Updated the package at $1/wasm" 21 | fi --------------------------------------------------------------------------------