diff --git a/abis/DataAttestationVerifier.json b/abis/DataAttestation.json similarity index 96% rename from abis/DataAttestationVerifier.json rename to abis/DataAttestation.json index 14bdee458..729cb3266 100644 --- a/abis/DataAttestationVerifier.json +++ b/abis/DataAttestation.json @@ -156,13 +156,13 @@ { "inputs": [ { - "internalType": "uint256[]", - "name": "instances", - "type": "uint256[]" + "internalType": "address", + "name": "verifier", + "type": "address" }, { "internalType": "bytes", - "name": "proof", + "name": "encoded", "type": "bytes" } ], diff --git a/abis/Verifier.json b/abis/Verifier.json deleted file mode 100644 index ecdb6faf2..000000000 --- a/abis/Verifier.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "abi": [ - { - "inputs": [ - { - "internalType": "uint256[]", - "name": "pubInputs", - "type": "uint256[]" - }, - { - "internalType": "bytes", - "name": "proof", - "type": "bytes" - } - ], - "name": "verify", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - } - ], - "methodIdentifiers": { - "verify(uint256[],bytes)": "bd205a90" - }, - "rawMetadata": "{\"compiler\":{\"version\":\"0.8.17+commit.8df45f5f\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"pubInputs\",\"type\":\"uint256[]\"},{\"internalType\":\"bytes\",\"name\":\"proof\",\"type\":\"bytes\"}],\"name\":\"verify\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"sol/Verifier.sol\":\"Verifier\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"sol/Verifier.sol\":{\"keccak256\":\"0x237177e2b153dfd15856a71e05aadd099395f05d14569a4810b63d1ccd6cd4f8\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://4a210955efd753fe21e9aed441e6c5507a3daf857d2f94a6be4c20dbbc4e1c24\",\"dweb:/ipfs/QmeLBMnSdE5RKQ3kEySWaedFbcZ8GDkPc7vMLGQMBd2FUA\"]}},\"version\":1}", - "metadata": { - "compiler": { - "version": "0.8.17+commit.8df45f5f" - }, - "language": "Solidity", - "output": { - "abi": [ - { - "inputs": [ - { - "internalType": "uint256[]", - "name": "pubInputs", - "type": "uint256[]" - }, - { - "internalType": "bytes", - "name": "proof", - "type": "bytes" - } - ], - "stateMutability": "view", - "type": "function", - "name": "verify", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ] - } - ], - "devdoc": { - "kind": "dev", - "methods": {}, - "version": 1 - }, - "userdoc": { - "kind": "user", - "methods": {}, - "version": 1 - } - }, - "settings": { - "remappings": [ - ":ds-test/=lib/forge-std/lib/ds-test/src/", - ":forge-std/=lib/forge-std/src/" - ], - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "compilationTarget": { - "sol/Verifier.sol": "Verifier" - }, - "libraries": {} - }, - "sources": { - "sol/Verifier.sol": { - "keccak256": "0x237177e2b153dfd15856a71e05aadd099395f05d14569a4810b63d1ccd6cd4f8", - "urls": [ - "bzz-raw://4a210955efd753fe21e9aed441e6c5507a3daf857d2f94a6be4c20dbbc4e1c24", - "dweb:/ipfs/QmeLBMnSdE5RKQ3kEySWaedFbcZ8GDkPc7vMLGQMBd2FUA" - ], - "license": "MIT" - } - }, - "version": 1 - }, - "id": 0 -} diff --git a/contracts/AttestData.sol b/contracts/AttestData.sol index 8d0bf5313..106703e3d 100644 --- a/contracts/AttestData.sol +++ b/contracts/AttestData.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; +pragma solidity ^0.8.20; // This contract serves as a Data Attestation Verifier for the EZKL model. // It is designed to read and attest to instances of proofs generated from a specified circuit. @@ -11,10 +11,10 @@ pragma solidity ^0.8.17; // 3. Static Calls: Makes static calls to fetch data from other contracts. See the `staticCall` method. // 4. Field Element Conversion: The fixed-point representation is then converted into a field element modulo P using the `toFieldElement` method. // 5. Data Attestation: The `attestData` method validates that the public instances match the data fetched and processed by the contract. -// 6. Proof Verification: The `verifyWithDataAttestation` method has a stubbed assembly block to integrate proof verification. +// 6. Proof Verification: The `verifyWithDataAttestation` method parses the instances out of the encoded calldata and calls the `attestData` method to validate the public instances, +// then calls the `verifyProof` method to verify the proof on the verifier. - -contract DataAttestationVerifier { +contract DataAttestation { /** * @notice Struct used to make view only calls to accounts to fetch the data that EZKL reads from. * @param the address of the account to make calls to @@ -225,7 +225,7 @@ contract DataAttestationVerifier { * @dev Make the account calls to fetch the data that EZKL reads from and attest to the data. * @param instances - The public instances to the proof (the data in the proof that publicly accessible to the verifier). */ - function attestData(uint256[] calldata instances) internal view { + function attestData(uint256[] memory instances) internal view { require( instances.length >= INPUT_CALLS + OUTPUT_CALLS, "Invalid public inputs length" @@ -259,13 +259,48 @@ contract DataAttestationVerifier { } function verifyWithDataAttestation( - uint256[] calldata instances, - bytes calldata proof + address verifier, + bytes memory encoded ) public view returns (bool) { - bool success = true; - bytes32[] memory transcript; + require(verifier.code.length > 0,"Address: call to non-contract"); + bytes4 fnSelector; + uint256[] memory instances; + bytes memory paramData = new bytes(encoded.length - 4); + assembly { + /* + 4 (fun sig) + + 32 (verifier address) + + 32 (offset encoded) + + 32 (length encoded) = 100 bytes = 0x64 + */ + fnSelector := calldataload(0x64) + + mstore(add(paramData, 0x20), sub(mload(add(encoded, 0x20)), 4)) + for { + let i := 0 + } lt(i, sub(mload(encoded), 4)) { + i := add(i, 0x20) + } { + mstore(add(paramData, add(0x20, i)), mload(add(encoded, add(0x24, i)))) + } + } + if (fnSelector == 0xaf83a18d) { + // abi decode verifyProof(address,bytes,uint256[]) + (,,instances) = abi.decode(paramData, (address, bytes, uint256[])); + } else { + // abi decode verifyProof(bytes,uint256[]) + (,instances) = abi.decode(paramData, (bytes, uint256[])); + } attestData(instances); - assembly { /* This is where the proof verification happens*/ } - return success; + + // static call the verifier contract to verify the proof + (bool success, bytes memory returndata) = verifier.staticcall(encoded); + + if (success) { + return abi.decode(returndata, (bool)); + } else { + revert("low-level call to verifier failed"); + } + } } diff --git a/contracts/VerifierBase.sol b/contracts/VerifierBase.sol deleted file mode 100644 index 281c0fd4f..000000000 --- a/contracts/VerifierBase.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -contract Verifier { - - /** - * @notice EZKL P value - * @dev In order to prevent the verifier from accepting two version of the same pubInput, n and the quantity (n + P), where n + P <= 2^256, we require that all instances are stricly less than P. - * @dev The reason for this is that the assmebly code of the verifier performs all arithmetic operations modulo P and as a consequence can't distinguish between n and n + P values. - */ - - uint256 constant SIZE_LIMIT = uint256(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001); - - function verify( - uint256[] calldata instances, - bytes calldata proof - ) public view returns (bool) { - bool success = true; - bytes32[] memory transcript; - for (uint i = 0; i < instances.length; i++) { - require(instances[i] < SIZE_LIMIT); - } - assembly { /* This is where the proof verification happens*/ } - return success; - } -} diff --git a/examples/notebooks/data_attest.ipynb b/examples/notebooks/data_attest.ipynb index 1aa954dcc..b204054e2 100644 --- a/examples/notebooks/data_attest.ipynb +++ b/examples/notebooks/data_attest.ipynb @@ -537,12 +537,57 @@ "print(\"verified\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now create and then deploy a vanilla evm verifier." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "abi_path = 'test.abi'\n", + "sol_code_path = 'test.sol'\n", + "\n", + "res = ezkl.create_evm_verifier(\n", + " vk_path,\n", + " srs_path,\n", + " settings_path,\n", + " sol_code_path,\n", + " abi_path,\n", + " )\n", + "assert res == True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "addr_path_verifier = \"addr_verifier.txt\"\n", + "\n", + "res = ezkl.deploy_evm(\n", + " addr_path_verifier,\n", + " sol_code_path,\n", + " 'http://127.0.0.1:3030'\n", + ")\n", + "\n", + "assert res == True" + ] + }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "We can now create an EVM / `.sol` verifier that can be deployed on chain to verify submitted proofs and attest to on-chain EZKL inputs using a view function. Make sure to pass the `input_path` instead of the `witness_path` as the former contains the on-chain data that we are attesting to." + "With the vanilla verifier deployed, we can now create the data attestation contract, which will read in the instances from the calldata to the verifier, attest to them, call the verifier and then return the result. " ] }, { @@ -556,7 +601,7 @@ "sol_code_path = 'test.sol'\n", "input_path = 'input.json'\n", "\n", - "res = ezkl.create_evm_data_attestation_verifier(\n", + "res = ezkl.create_evm_data_attestation(\n", " vk_path,\n", " srs_path,\n", " settings_path,\n", @@ -581,10 +626,10 @@ "metadata": {}, "outputs": [], "source": [ - "addr_path = \"addr.txt\"\n", + "addr_path_da = \"addr_da.txt\"\n", "\n", "res = ezkl.deploy_da_evm(\n", - " addr_path,\n", + " addr_path_da,\n", " input_path,\n", " settings_path,\n", " sol_code_path,\n", @@ -606,16 +651,20 @@ "metadata": {}, "outputs": [], "source": [ - "# read the address from addr_path\n", - "addr = None\n", - "with open(addr_path, 'r') as f:\n", + "# read the verifier address\n", + "addr_verifier = None\n", + "with open(addr_path_verifier, 'r') as f:\n", " addr = f.read()\n", + "#read the data attestation address\n", + "addr_da = None\n", + "with open(addr_path_da, 'r') as f:\n", + " addr_da = f.read()\n", "\n", "res = ezkl.verify_evm(\n", " proof_path,\n", " addr,\n", " RPC_URL,\n", - " True\n", + " addr_da,\n", ")" ] } @@ -636,7 +685,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.6" + "version": "3.9.15" }, "orig_nbformat": 4 }, diff --git a/src/commands.rs b/src/commands.rs index 90290c17e..5d43e98bc 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -478,8 +478,8 @@ pub enum Commands { }, #[cfg(not(target_arch = "wasm32"))] /// Creates an EVM verifier that attests to on-chain inputs for a single proof - #[command(name = "create-evm-da-verifier", arg_required_else_help = true)] - CreateEVMDataAttestationVerifier { + #[command(name = "create-evm-da", arg_required_else_help = true)] + CreateEVMDataAttestation { /// The path to load the desired srs file from #[arg(long)] srs_path: PathBuf, @@ -573,8 +573,8 @@ pub enum Commands { optimizer_runs: usize, }, #[cfg(not(target_arch = "wasm32"))] - #[command(name = "deploy-evm-da-verifier", arg_required_else_help = true)] - DeployEvmDataAttestationVerifier { + #[command(name = "deploy-evm-da", arg_required_else_help = true)] + DeployEvmDataAttestation { /// The path to the .json data file, which should include both the network input (possibly private) and the network output (public input to the proof) #[arg(short = 'D', long)] data: PathBuf, @@ -603,13 +603,13 @@ pub enum Commands { proof_path: PathBuf, /// The path to verfier contract's address #[arg(long)] - addr: H160, + addr_verifier: H160, /// RPC URL for an Ethereum node, if None will use Anvil but WON'T persist state #[arg(short = 'U', long)] rpc_url: Option, /// does the verifier use data attestation ? - #[arg(long, default_value = "false")] - data_attestation: bool, + #[arg(long)] + addr_da: Option, }, /// Print the proof in hexadecimal diff --git a/src/eth.rs b/src/eth.rs index 97afe2cbf..edf21ca3d 100644 --- a/src/eth.rs +++ b/src/eth.rs @@ -30,7 +30,6 @@ use halo2curves::bn256::{Fr, G1Affine}; use halo2curves::group::ff::PrimeField; use log::{debug, info, warn}; use std::error::Error; -use std::fmt::Write; use std::path::PathBuf; #[cfg(not(target_arch = "wasm32"))] use std::time::Duration; @@ -41,17 +40,15 @@ pub type EthersClient = Arc, Wallet> // Generate contract bindings OUTSIDE the functions so they are part of library abigen!(TestReads, "./abis/TestReads.json"); -abigen!(Verifier, "./abis/Verifier.json"); abigen!( - DataAttestationVerifier, - "./abis/DataAttestationVerifier.json" + DataAttestation, + "./abis/DataAttestation.json" ); abigen!(QuantizeData, "./abis/QuantizeData.json"); const TESTREADS_SOL: &str = include_str!("../contracts/TestReads.sol"); const QUANTIZE_DATA_SOL: &str = include_str!("../contracts/QuantizeData.sol"); const ATTESTDATA_SOL: &str = include_str!("../contracts/AttestData.sol"); -const VERIFIERBASE_SOL: &str = include_str!("../contracts/VerifierBase.sol"); /// Return an instance of Anvil and a client for the given RPC URL. If none is provided, a local client is used. #[cfg(not(target_arch = "wasm32"))] @@ -165,7 +162,7 @@ pub async fn deploy_da_verifier_via_solidity( }; let (abi, bytecode, runtime_bytecode) = - get_contract_artifacts(sol_code_path, "DataAttestationVerifier", runs)?; + get_contract_artifacts(sol_code_path, "DataAttestation", runs)?; let factory = get_sol_contract_factory(abi, bytecode, runtime_bytecode, client.clone()).unwrap(); @@ -239,7 +236,7 @@ pub async fn update_account_calls( let (anvil, client) = setup_eth_backend(rpc_url).await?; - let contract = DataAttestationVerifier::new(addr, client.clone()); + let contract = DataAttestation::new(addr, client.clone()); contract .update_account_calls( @@ -256,7 +253,7 @@ pub async fn update_account_calls( let client = Arc::new(client.with_signer(wallet.with_chain_id(anvil.chain_id()))); // update contract signer with non admin account - let contract = DataAttestationVerifier::new(addr, client.clone()); + let contract = DataAttestation::new(addr, client.clone()); // call to update_account_calls should fail @@ -374,12 +371,13 @@ pub async fn setup_test_contract( Ok((contract, decimals)) } -/// Verify a proof using a Solidity DataAttestationVerifier contract. +/// Verify a proof using a Solidity DataAttestation contract. /// Used for testing purposes. #[cfg(not(target_arch = "wasm32"))] pub async fn verify_proof_with_data_attestation( proof: Snark, - addr: ethers::types::Address, + addr_verifier: ethers::types::Address, + addr_da: ethers::types::Address, rpc_url: Option<&str>, ) -> Result> { use ethers::abi::{Function, Param, ParamType, StateMutability, Token}; @@ -387,12 +385,20 @@ pub async fn verify_proof_with_data_attestation( let mut public_inputs: Vec = vec![]; let flattened_instances = proof.instances.into_iter().flatten(); - for val in flattened_instances { + for val in flattened_instances.clone() { let bytes = val.to_repr(); let u = U256::from_little_endian(bytes.as_slice()); public_inputs.push(u); } + let encoded_verifier = halo2_solidity_verifier::encode_calldata( + None, + &proof.proof, + &flattened_instances.collect::>(), + ); + + info!("encoded: {:#?}", hex::encode(&encoded_verifier)); + info!("public_inputs: {:#?}", public_inputs); info!( "proof: {:#?}", @@ -404,12 +410,12 @@ pub async fn verify_proof_with_data_attestation( name: "verifyWithDataAttestation".to_owned(), inputs: vec![ Param { - name: "pubInputs".to_owned(), - kind: ParamType::FixedArray(Box::new(ParamType::Uint(256)), public_inputs.len()), + name: "verifier".to_owned(), + kind: ParamType::Address, internal_type: None, }, Param { - name: "proof".to_owned(), + name: "encoded".to_owned(), kind: ParamType::Bytes, internal_type: None, }, @@ -424,14 +430,14 @@ pub async fn verify_proof_with_data_attestation( }; let encoded = func.encode_input(&[ - Token::FixedArray(public_inputs.into_iter().map(Token::Uint).collect()), - Token::Bytes(proof.proof), + Token::Address(addr_verifier), + Token::Bytes(encoded_verifier), ])?; info!("encoded: {:#?}", hex::encode(&encoded)); let (anvil, client) = setup_eth_backend(rpc_url).await?; let tx: TypedTransaction = TransactionRequest::default() - .to(addr) + .to(addr_da) .from(client.address()) .data(encoded) .into(); @@ -636,346 +642,39 @@ pub fn get_contract_artifacts( Ok((abi, bytecode, runtime_bytecode)) } -use regex::Regex; -use std::fs::File; -use std::io::{BufRead, BufReader}; - -/// Reads in raw bytes code and generates equivalent .sol file -/// Can optionally attest to on-chain inputs -pub fn fix_verifier_sol( - input_file: PathBuf, - num_instances: u32, +/// Sets the constants stored in the da verifier +pub fn fix_da_sol( input_data: Option<(u32, Vec)>, output_data: Option>, ) -> Result> { - let mut transcript_addrs: Vec = Vec::new(); - let mut modified_lines: Vec = Vec::new(); - - // convert calldataload 0x0 to 0x40 to read from pubInputs, and the rest - // from proof - let calldata_pattern = Regex::new(r"^.*(calldataload\((0x[a-f0-9]+)\)).*$")?; - let mstore_pattern = Regex::new(r"^\s*(mstore\(0x([0-9a-fA-F]+)+),.+\)")?; - let mstore8_pattern = Regex::new(r"^\s*(mstore8\((\d+)+),.+\)")?; - let mstoren_pattern = Regex::new(r"^\s*(mstore\((\d+)+),.+\)")?; - let mload_pattern = Regex::new(r"(mload\((0x[0-9a-fA-F]+))\)")?; - let keccak_pattern = Regex::new(r"(keccak256\((0x[0-9a-fA-F]+))")?; - let modexp_pattern = - Regex::new(r"(staticcall\(gas\(\), 0x5, (0x[0-9a-fA-F]+), 0xc0, (0x[0-9a-fA-F]+), 0x20)")?; - let ecmul_pattern = - Regex::new(r"(staticcall\(gas\(\), 0x7, (0x[0-9a-fA-F]+), 0x60, (0x[0-9a-fA-F]+), 0x40)")?; - let ecadd_pattern = - Regex::new(r"(staticcall\(gas\(\), 0x6, (0x[0-9a-fA-F]+), 0x80, (0x[0-9a-fA-F]+), 0x40)")?; - let ecpairing_pattern = - Regex::new(r"(staticcall\(gas\(\), 0x8, (0x[0-9a-fA-F]+), 0x180, (0x[0-9a-fA-F]+), 0x20)")?; - let bool_pattern = Regex::new(r":bool")?; - - let mut max_pubinputs_addr: u32 = 0; - if num_instances > 0 { - max_pubinputs_addr = num_instances * 32 - 32; - } - - let file = File::open(input_file.clone()) - .map_err(|_| format!("failed to load verfier at {}", input_file.display()))?; - let reader = BufReader::new(file); - - for line in reader.lines() { - let mut line = line?; - let m = bool_pattern.captures(&line); - if m.is_some() { - line = line.replace(":bool", ""); - } - - let m = calldata_pattern.captures(&line); - if let Some(m) = m { - let calldata_and_addr = m.get(1).unwrap().as_str(); - let addr = m.get(2).unwrap().as_str(); - let addr_as_num = u32::from_str_radix(addr.strip_prefix("0x").unwrap(), 16)?; - if addr_as_num <= max_pubinputs_addr { - let pub_addr = format!("{:#x}", addr_as_num); - line = line.replace( - calldata_and_addr, - &format!("calldataload(add(pubInputs, {}))", pub_addr), - ); - } else { - let proof_addr = format!("{:#x}", 32 + addr_as_num - max_pubinputs_addr); - line = line.replace( - calldata_and_addr, - &format!("calldataload(add(proof, {}))", proof_addr), - ); - } - } - - let m = mstore8_pattern.captures(&line); - if let Some(m) = m { - let mstore = m.get(1).unwrap().as_str(); - let addr = m.get(2).unwrap().as_str(); - let addr_as_num = addr.parse::()?; - let transcript_addr = format!("{:#x}", addr_as_num); - transcript_addrs.push(addr_as_num); - line = line.replace( - mstore, - &format!("mstore8(add(transcript, {})", transcript_addr), - ); - } - - let m = mstoren_pattern.captures(&line); - if let Some(m) = m { - let mstore = m.get(1).unwrap().as_str(); - let addr = m.get(2).unwrap().as_str(); - let addr_as_num = addr.parse::()?; - let transcript_addr = format!("{:#x}", addr_as_num); - transcript_addrs.push(addr_as_num); - line = line.replace( - mstore, - &format!("mstore(add(transcript, {})", transcript_addr), - ); - } - - let m = modexp_pattern.captures(&line); - if let Some(m) = m { - let modexp = m.get(1).unwrap().as_str(); - let start_addr = m.get(2).unwrap().as_str(); - let result_addr = m.get(3).unwrap().as_str(); - let start_addr_as_num = - u32::from_str_radix(start_addr.strip_prefix("0x").unwrap(), 16)?; - let result_addr_as_num = - u32::from_str_radix(result_addr.strip_prefix("0x").unwrap(), 16)?; - - let transcript_addr = format!("{:#x}", start_addr_as_num); - transcript_addrs.push(start_addr_as_num); - let result_addr = format!("{:#x}", result_addr_as_num); - line = line.replace( - modexp, - &format!( - "staticcall(gas(), 0x5, add(transcript, {}), 0xc0, add(transcript, {}), 0x20", - transcript_addr, result_addr - ), - ); - } - - let m = ecmul_pattern.captures(&line); - if let Some(m) = m { - let ecmul = m.get(1).unwrap().as_str(); - let start_addr = m.get(2).unwrap().as_str(); - let result_addr = m.get(3).unwrap().as_str(); - let start_addr_as_num = - u32::from_str_radix(start_addr.strip_prefix("0x").unwrap(), 16)?; - let result_addr_as_num = - u32::from_str_radix(result_addr.strip_prefix("0x").unwrap(), 16)?; - - let transcript_addr = format!("{:#x}", start_addr_as_num); - let result_addr = format!("{:#x}", result_addr_as_num); - transcript_addrs.push(start_addr_as_num); - transcript_addrs.push(result_addr_as_num); - line = line.replace( - ecmul, - &format!( - "staticcall(gas(), 0x7, add(transcript, {}), 0x60, add(transcript, {}), 0x40", - transcript_addr, result_addr - ), - ); - } - - let m = ecadd_pattern.captures(&line); - if let Some(m) = m { - let ecadd = m.get(1).unwrap().as_str(); - let start_addr = m.get(2).unwrap().as_str(); - let result_addr = m.get(3).unwrap().as_str(); - let start_addr_as_num = - u32::from_str_radix(start_addr.strip_prefix("0x").unwrap(), 16)?; - let result_addr_as_num = - u32::from_str_radix(result_addr.strip_prefix("0x").unwrap(), 16)?; - - let transcript_addr = format!("{:#x}", start_addr_as_num); - let result_addr = format!("{:#x}", result_addr_as_num); - transcript_addrs.push(start_addr_as_num); - transcript_addrs.push(result_addr_as_num); - line = line.replace( - ecadd, - &format!( - "staticcall(gas(), 0x6, add(transcript, {}), 0x80, add(transcript, {}), 0x40", - transcript_addr, result_addr - ), - ); - } - - let m = ecpairing_pattern.captures(&line); - if let Some(m) = m { - let ecpairing = m.get(1).unwrap().as_str(); - let start_addr = m.get(2).unwrap().as_str(); - let result_addr = m.get(3).unwrap().as_str(); - let start_addr_as_num = - u32::from_str_radix(start_addr.strip_prefix("0x").unwrap(), 16)?; - let result_addr_as_num = - u32::from_str_radix(result_addr.strip_prefix("0x").unwrap(), 16)?; - - let transcript_addr = format!("{:#x}", start_addr_as_num); - let result_addr = format!("{:#x}", result_addr_as_num); - transcript_addrs.push(start_addr_as_num); - transcript_addrs.push(result_addr_as_num); - line = line.replace( - ecpairing, - &format!( - "staticcall(gas(), 0x8, add(transcript, {}), 0x180, add(transcript, {}), 0x20", - transcript_addr, result_addr - ), - ); - } - - let m = mstore_pattern.captures(&line); - if let Some(m) = m { - let mstore = m.get(1).unwrap().as_str(); - let addr = m.get(2).unwrap().as_str(); - let addr_as_num = u32::from_str_radix(addr, 16)?; - let transcript_addr = format!("{:#x}", addr_as_num); - transcript_addrs.push(addr_as_num); - line = line.replace( - mstore, - &format!("mstore(add(transcript, {})", transcript_addr), - ); - } - - let m = keccak_pattern.captures(&line); - if let Some(m) = m { - let keccak = m.get(1).unwrap().as_str(); - let addr = m.get(2).unwrap().as_str(); - let addr_as_num = u32::from_str_radix(addr.strip_prefix("0x").unwrap(), 16)?; - let transcript_addr = format!("{:#x}", addr_as_num); - transcript_addrs.push(addr_as_num); - line = line.replace( - keccak, - &format!("keccak256(add(transcript, {})", transcript_addr), - ); - } - // mload can show up multiple times per line - loop { - let m = mload_pattern.captures(&line); - if m.is_none() { - break; - } - let mload = m.as_ref().unwrap().get(1).unwrap().as_str(); - let addr = m.as_ref().unwrap().get(2).unwrap().as_str(); - - let addr_as_num = u32::from_str_radix(addr.strip_prefix("0x").unwrap(), 16)?; - let transcript_addr = format!("{:#x}", addr_as_num); - transcript_addrs.push(addr_as_num); - line = line.replace( - mload, - &format!("mload(add(transcript, {})", transcript_addr), - ); - } + let mut accounts_len = 0; + let mut contract = ATTESTDATA_SOL.to_string(); + // fill in the quantization params and total calls + // as constants to the contract to save on gas + if let Some(input_data) = input_data { + let input_calls: usize = input_data.1.iter().map(|v| v.call_data.len()).sum(); + let input_scale = input_data.0; + accounts_len = input_data.1.len(); + contract = contract.replace( + "uint public constant INPUT_SCALE = 1 << 0;", + &format!("uint public constant INPUT_SCALE = 1 << {};", input_scale), + ); - modified_lines.push(line); + contract = contract.replace( + "uint256 constant INPUT_CALLS = 0;", + &format!("uint256 constant INPUT_CALLS = {};", input_calls), + ); } - - // get the max transcript addr - let max_transcript_addr = transcript_addrs.iter().max().unwrap() / 32; - - let contract = if input_data.is_some() || output_data.is_some() { - let mut accounts_len = 0; - let mut contract = ATTESTDATA_SOL.to_string(); - // fill in the quantization params and total calls - // as constants to the contract to save on gas - if let Some(input_data) = input_data { - let input_calls: usize = input_data.1.iter().map(|v| v.call_data.len()).sum(); - let input_scale = input_data.0; - accounts_len = input_data.1.len(); - contract = contract.replace( - "uint public constant INPUT_SCALE = 1 << 0;", - &format!("uint public constant INPUT_SCALE = 1 << {};", input_scale), - ); - - contract = contract.replace( - "uint256 constant INPUT_CALLS = 0;", - &format!("uint256 constant INPUT_CALLS = {};", input_calls), - ); - } - if let Some(output_data) = output_data { - let output_calls: usize = output_data.iter().map(|v| v.call_data.len()).sum(); - accounts_len += output_data.len(); - contract = contract.replace( - "uint256 constant OUTPUT_CALLS = 0;", - &format!("uint256 constant OUTPUT_CALLS = {};", output_calls), - ); - } - contract.replace("AccountCall[]", &format!("AccountCall[{}]", accounts_len)) - } else { - VERIFIERBASE_SOL.to_string() - }; - - // Insert the max_transcript_addr into the contract string at the correct position. - let mut contract = contract.replace( - "bytes32[] memory transcript", - &format!("bytes32[{}] memory transcript", max_transcript_addr), - ); - - // Hardcode the fixed array length of pubInputs param - contract = contract.replace( - "uint256[] calldata", - &format!("uint256[{}] calldata", num_instances), - ); - - // Find the index of "assembly {" - let end_index = - match contract.find("assembly { /* This is where the proof verification happens*/ }") { - Some(index) => index + 10, - None => { - panic!("assembly {{ not found in the contract"); - } - }; - - // Take a slice from the start of the contract string up to the "assembly {" position - let contract_slice = &contract[..end_index]; - - let mut contract_slice_string = contract_slice.to_string(); - - // using a boxed Write trait object here to show it works for any Struct impl'ing Write - // you may also use a std::fs::File here - let write: Box<&mut dyn Write> = Box::new(&mut contract_slice_string); - - for line in modified_lines[16..modified_lines.len() - 7].iter() { - write!(write, "{}", line).unwrap(); + if let Some(output_data) = output_data { + let output_calls: usize = output_data.iter().map(|v| v.call_data.len()).sum(); + accounts_len += output_data.len(); + contract = contract.replace( + "uint256 constant OUTPUT_CALLS = 0;", + &format!("uint256 constant OUTPUT_CALLS = {};", output_calls), + ); } - writeln!(write, "}} return success; }} }}")?; - - // free memory pointer initialization - let mut offset = 4; - - // replace all mload(add(pubInputs, 0x...))) with mload(0x... - contract_slice_string = replace_vars_with_offset( - &contract_slice_string, - r"add\(pubInputs, (0x[0-9a-fA-F]+)\)", - offset, - ); - - offset += 32 * num_instances; + contract = contract.replace("AccountCall[]", &format!("AccountCall[{}]", accounts_len)); - // replace all mload(add(proof, 0x...))) with mload(0x... - contract_slice_string = replace_vars_with_offset( - &contract_slice_string, - r"add\(proof, (0x[0-9a-fA-F]+)\)", - offset, - ); - - offset = 128; - - // replace all (add(transcript, 0x...))) with (0x...) - contract_slice_string = replace_vars_with_offset( - &contract_slice_string, - r"add\(transcript, (0x[0-9a-fA-F]+)\)", - offset, - ); - - Ok(contract_slice_string) -} - -fn replace_vars_with_offset(contract: &str, regex_pattern: &str, offset: u32) -> String { - let re = Regex::new(regex_pattern).unwrap(); - let replaced = re.replace_all(contract, |caps: ®ex::Captures| { - let addr_as_num = u32::from_str_radix(caps[1].strip_prefix("0x").unwrap(), 16).unwrap(); - let new_addr = addr_as_num + offset; - format!("{:#x}", new_addr) - }); - replaced.into_owned() -} + Ok(contract) +} \ No newline at end of file diff --git a/src/execute.rs b/src/execute.rs index ff8d0e29a..cb5a4a742 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -5,7 +5,7 @@ use crate::commands::{Cli, Commands}; #[cfg(not(target_arch = "wasm32"))] use crate::eth::{deploy_da_verifier_via_solidity, deploy_verifier_via_solidity}; #[cfg(not(target_arch = "wasm32"))] -use crate::eth::{fix_verifier_sol, get_contract_artifacts, verify_proof_via_solidity}; +use crate::eth::{fix_da_sol, get_contract_artifacts, verify_proof_via_solidity}; use crate::graph::input::GraphData; use crate::graph::{GraphCircuit, GraphSettings, GraphWitness, Model}; #[cfg(not(target_arch = "wasm32"))] @@ -162,7 +162,7 @@ pub async fn run(cli: Cli) -> Result<(), Box> { abi_path, } => create_evm_verifier(vk_path, srs_path, settings_path, sol_code_path, abi_path), #[cfg(not(target_arch = "wasm32"))] - Commands::CreateEVMDataAttestationVerifier { + Commands::CreateEVMDataAttestation { vk_path, srs_path, settings_path, @@ -296,7 +296,7 @@ pub async fn run(cli: Cli) -> Result<(), Box> { optimizer_runs, } => deploy_evm(sol_code_path, rpc_url, addr_path, optimizer_runs).await, #[cfg(not(target_arch = "wasm32"))] - Commands::DeployEvmDataAttestationVerifier { + Commands::DeployEvmDataAttestation { data, settings_path, sol_code_path, @@ -317,10 +317,10 @@ pub async fn run(cli: Cli) -> Result<(), Box> { #[cfg(not(target_arch = "wasm32"))] Commands::VerifyEVM { proof_path, - addr, + addr_verifier, rpc_url, - data_attestation, - } => verify_evm(proof_path, addr, rpc_url, data_attestation).await, + addr_da, + } => verify_evm(proof_path, addr_verifier, rpc_url, addr_da).await, Commands::PrintProofHex { proof_path } => print_proof_hex(proof_path), } } @@ -899,16 +899,14 @@ pub(crate) fn create_evm_data_attestation_verifier( }; if input_data.is_some() || output_data.is_some() { - let output = fix_verifier_sol( - sol_code_path.clone(), - num_instance as u32, + let output = fix_da_sol( input_data, output_data, )?; let mut f = File::create(sol_code_path.clone())?; let _ = f.write(output.as_bytes()); // fetch abi of the contract - let (abi, _, _) = get_contract_artifacts(sol_code_path, "DataAttestationVerifier", 0)?; + let (abi, _, _) = get_contract_artifacts(sol_code_path, "DataAttestation", 0)?; // save abi to file serde_json::to_writer(std::fs::File::create(abi_path)?, &abi)?; } else { @@ -966,19 +964,19 @@ pub(crate) async fn deploy_evm( #[cfg(not(target_arch = "wasm32"))] pub(crate) async fn verify_evm( proof_path: PathBuf, - addr: H160, + addr_verifier: H160, rpc_url: Option, - uses_data_attestation: bool, + addr_da: Option, ) -> Result<(), Box> { use crate::eth::verify_proof_with_data_attestation; check_solc_requirement(); let proof = Snark::load::>(&proof_path)?; - let result = if !uses_data_attestation { - verify_proof_via_solidity(proof.clone(), addr, rpc_url.as_deref()).await? + let result = if let Some(addr_da) = addr_da { + verify_proof_with_data_attestation(proof.clone(), addr_verifier, addr_da, rpc_url.as_deref()).await? } else { - verify_proof_with_data_attestation(proof.clone(), addr, rpc_url.as_deref()).await? + verify_proof_via_solidity(proof.clone(), addr_verifier, rpc_url.as_deref()).await? }; info!("Solidity verification result: {}", result); diff --git a/src/python.rs b/src/python.rs index c559983c3..f7241a3a2 100644 --- a/src/python.rs +++ b/src/python.rs @@ -908,7 +908,7 @@ fn create_evm_verifier( abi_path, input_data ))] -fn create_evm_data_attestation_verifier( +fn create_evm_data_attestation( vk_path: PathBuf, srs_path: PathBuf, settings_path: PathBuf, @@ -996,28 +996,38 @@ fn deploy_da_evm( /// verifies an evm compatible proof, you will need solc installed in your environment to run this #[pyfunction(signature = ( proof_path, - addr, + addr_verifier, rpc_url=None, - data_attestation = false, + addr_da = None, ))] fn verify_evm( proof_path: PathBuf, - addr: &str, + addr_verifier: &str, rpc_url: Option, - data_attestation: bool, + addr_da: Option<&str> ) -> Result { - let addr = H160::from_str(addr).map_err(|e| { + let addr_verifier = H160::from_str(addr_verifier).map_err(|e| { let err_str = format!("address is invalid: {}", e); PyRuntimeError::new_err(err_str) })?; + let addr_da = if let Some(addr_da) = addr_da { + let addr_da = H160::from_str(addr_da).map_err(|e| { + let err_str = format!("address is invalid: {}", e); + PyRuntimeError::new_err(err_str) + })?; + Some(addr_da) + } else { + None + }; + Runtime::new() .unwrap() .block_on(crate::execute::verify_evm( proof_path, - addr, + addr_verifier, rpc_url, - data_attestation, + addr_da, )) .map_err(|e| { let err_str = format!("Failed to run verify_evm: {}", e); @@ -1114,7 +1124,7 @@ fn ezkl(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(verify_evm, m)?)?; m.add_function(wrap_pyfunction!(print_proof_hex, m)?)?; m.add_function(wrap_pyfunction!(create_evm_verifier_aggr, m)?)?; - m.add_function(wrap_pyfunction!(create_evm_data_attestation_verifier, m)?)?; + m.add_function(wrap_pyfunction!(create_evm_data_attestation, m)?)?; Ok(()) } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index ad139d936..7c2fd4f38 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -2496,12 +2496,52 @@ mod native_tests { let vk_arg = format!("{}/{}/key.vk", test_dir, example_name); + let settings_arg = format!("--settings-path={}", settings_path); + + // create the verifier + let mut args = vec![ + "create-evm-verifier", + &srs_path, + "--vk-path", + &vk_arg, + &settings_arg, + ]; + + let sol_arg = format!("{}/{}/kzg.sol", test_dir, example_name); + + args.push("--sol-code-path"); + args.push(sol_arg.as_str()); + + let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) + .args(&args) + .status() + .expect("failed to execute process"); + assert!(status.success()); + + let addr_path_verifier_arg = format!("--addr-path={}/{}/addr_verifier.txt", test_dir, example_name); + + // deploy the verifier + let mut args = vec![ + "deploy-evm-verifier", + rpc_arg.as_str(), + addr_path_verifier_arg.as_str(), + ]; + + args.push("--sol-code-path"); + args.push(sol_arg.as_str()); + + let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) + .args(&args) + .status() + .expect("failed to execute process"); + assert!(status.success()); + let sol_arg = format!("{}/{}/kzg.sol", test_dir, example_name); let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) .args([ - "create-evm-da-verifier", - format!("--settings-path={}", settings_path).as_str(), + "create-evm-da", + &settings_arg, "--sol-code-path", sol_arg.as_str(), &srs_path, @@ -2514,36 +2554,42 @@ mod native_tests { .expect("failed to execute process"); assert!(status.success()); - let addr_path_arg = format!("--addr-path={}/{}/addr.txt", test_dir, example_name); + let addr_path_da_arg = format!("--addr-path={}/{}/addr_da.txt", test_dir, example_name); let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) .args([ - "deploy-evm-da-verifier", + "deploy-evm-da", format!("--settings-path={}", settings_path).as_str(), "-D", test_on_chain_data_path.as_str(), "--sol-code-path", sol_arg.as_str(), rpc_arg.as_str(), - addr_path_arg.as_str(), + addr_path_da_arg.as_str(), ]) .status() .expect("failed to execute process"); assert!(status.success()); let pf_arg = format!("{}/{}/proof.pf", test_dir, example_name); - let uses_data_attestation = "--data-attestation".to_string(); - // read in the address - let addr = std::fs::read_to_string(format!("{}/{}/addr.txt", test_dir, example_name)) + // read in the verifier address + let addr_verifier = std::fs::read_to_string(format!("{}/{}/addr_verifier.txt", test_dir, example_name)) .expect("failed to read address file"); - let deployed_addr_arg = format!("--addr={}", addr); + let deployed_addr_verifier_arg = format!("--addr-verifier={}", addr_verifier); + + // read in the da address + let addr_da = std::fs::read_to_string(format!("{}/{}/addr_da.txt", test_dir, example_name)) + .expect("failed to read address file"); + + let deployed_addr_da_arg = format!("--addr-da={}", addr_da); + let args = vec![ "verify-evm", "--proof-path", pf_arg.as_str(), - deployed_addr_arg.as_str(), - uses_data_attestation.as_str(), + deployed_addr_verifier_arg.as_str(), + deployed_addr_da_arg.as_str(), rpc_arg.as_str(), ]; let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) @@ -2570,6 +2616,8 @@ mod native_tests { assert!(status.success()); + let deployed_addr_arg = format!("--addr={}", addr_da); + let mut args = vec![ "test-update-account-calls", deployed_addr_arg.as_str(),