From 6924797e48fcc516f48256b3e0c364b32e6ba70b Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 25 Jun 2024 16:10:24 -0500 Subject: [PATCH 01/46] *reusable verifier example nb --- .github/workflows/rust.yml | 2 + Cargo.lock | 2 +- Cargo.toml | 17 +- examples/notebooks/reusable_verifier.ipynb | 328 +++++++++++++++++++++ tests/py_integration_tests.rs | 12 + 5 files changed, 355 insertions(+), 6 deletions(-) create mode 100644 examples/notebooks/reusable_verifier.ipynb diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9ee5cfe70..10a8ce3d0 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -677,3 +677,5 @@ jobs: run: source .env/bin/activate; cargo nextest run py_tests::tests::voice_ - name: NBEATS tutorial run: source .env/bin/activate; cargo nextest run py_tests::tests::nbeats_ + - name: Reusable verifier tutorial + run: source .env/bin/activate; cargo nextest run py_tests::tests::reusable_ diff --git a/Cargo.lock b/Cargo.lock index 43e087524..d2e402670 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2315,7 +2315,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=main#fd74f1da2ce51664e2d4349965987ee606551060" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#270112550a3e92ee8224aff9f60bdd16068c3cfa" dependencies = [ "askama", "blake2b_simd", diff --git a/Cargo.toml b/Cargo.toml index 8821be1f6..e991c5f45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ halo2_wrong_ecc = { git = "https://github.com/zkonduit/halo2wrong", branch = "ac snark-verifier = { git = "https://github.com/zkonduit/snark-verifier", branch = "ac/chunked-mv-lookup", features = [ "derive_serde", ] } -halo2_solidity_verifier = { git = "https://github.com/alexander-camuto/halo2-solidity-verifier", branch = "main" } +halo2_solidity_verifier = { git = "https://github.com/alexander-camuto/halo2-solidity-verifier", branch = "cache-lookup-consts-vk" } maybe-rayon = { version = "0.1.1", default_features = false } bincode = { version = "1.3.3", default_features = false } ark-std = { version = "^0.3.0", default-features = false } @@ -49,8 +49,15 @@ semver = "1.0.22" # evm related deps [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -alloy = { git = "https://github.com/alloy-rs/alloy", version = "0.1.0", rev="5fbf57bac99edef9d8475190109a7ea9fb7e5e83", features = ["provider-http", "signers", "contract", "rpc-types-eth", "signer-wallet", "node-bindings"] } -foundry-compilers = {version = "0.4.1", features = ["svm-solc"]} +alloy = { git = "https://github.com/alloy-rs/alloy", version = "0.1.0", rev = "5fbf57bac99edef9d8475190109a7ea9fb7e5e83", features = [ + "provider-http", + "signers", + "contract", + "rpc-types-eth", + "signer-wallet", + "node-bindings", +] } +foundry-compilers = { version = "0.4.1", features = ["svm-solc"] } ethabi = "18" indicatif = { version = "0.17.5", features = ["rayon"] } gag = { version = "1.0.0", default_features = false } @@ -70,7 +77,7 @@ plotters = { version = "0.3.0", default_features = false, optional = true } regex = { version = "1", default_features = false } tokio = { version = "1.35", default_features = false, features = [ "macros", - "rt-multi-thread" + "rt-multi-thread", ] } tokio-util = { version = "0.7.9", features = ["codec"] } pyo3 = { version = "0.21.2", features = [ @@ -78,7 +85,7 @@ pyo3 = { version = "0.21.2", features = [ "abi3-py37", "macros", ], default_features = false, optional = true } -pyo3-asyncio = { git = "https://github.com/jopemachine/pyo3-asyncio/", branch="migration-pyo3-0.21", features = [ +pyo3-asyncio = { git = "https://github.com/jopemachine/pyo3-asyncio/", branch = "migration-pyo3-0.21", features = [ "attributes", "tokio-runtime", ], default_features = false, optional = true } diff --git a/examples/notebooks/reusable_verifier.ipynb b/examples/notebooks/reusable_verifier.ipynb new file mode 100644 index 000000000..69e7ddd91 --- /dev/null +++ b/examples/notebooks/reusable_verifier.ipynb @@ -0,0 +1,328 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reusable Verifiers \n", + "\n", + "This notebook demonstrates how to create and reuse the same set of separated verifiers for different models. Specifically, we will use the same verifier for the following four models:\n", + "\n", + "- `1l_mlp sigmoid`\n", + "- `1l_mlp relu`\n", + "- `1l_conv sigmoid`\n", + "- `1l_conv relu`\n", + "\n", + "When deploying EZKL verifiers on the blockchain, each associated model typically requires its own unique verifier, leading to increased on-chain state usage. However, if the models have a sufficiently similar architecture, it is possible to deploy a single set of verifiers for multiple models. This notebook showcases how to achieve this by creating reusable verifiers for the four models listed above.\n", + "\n", + "By reusing the same verifier across multiple models, we significantly reduce the amount of state required on the blockchain. Instead of deploying a unique verifier for each model, we deploy a unique and much smaller verification key (VK) contract for each model while sharing a common separated verifier." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import torch.nn as nn\n", + "import torch.onnx\n", + "\n", + "# Define the models\n", + "class MLP_Sigmoid(nn.Module):\n", + " def __init__(self):\n", + " super(MLP_Sigmoid, self).__init__()\n", + " self.fc = nn.Linear(3, 3)\n", + " self.sigmoid = nn.Sigmoid()\n", + "\n", + " def forward(self, x):\n", + " x = self.fc(x)\n", + " x = self.sigmoid(x)\n", + " return x\n", + "\n", + "class MLP_Relu(nn.Module):\n", + " def __init__(self):\n", + " super(MLP_Relu, self).__init__()\n", + " self.fc = nn.Linear(3, 3)\n", + " self.relu = nn.ReLU()\n", + "\n", + " def forward(self, x):\n", + " x = self.fc(x)\n", + " x = self.relu(x)\n", + " return x\n", + "\n", + "class Conv_Sigmoid(nn.Module):\n", + " def __init__(self):\n", + " super(Conv_Sigmoid, self).__init__()\n", + " self.conv = nn.Conv1d(1, 1, kernel_size=3, stride=1)\n", + " self.sigmoid = nn.Sigmoid()\n", + "\n", + " def forward(self, x):\n", + " x = self.conv(x)\n", + " x = self.sigmoid(x)\n", + " return x\n", + "\n", + "class Conv_Relu(nn.Module):\n", + " def __init__(self):\n", + " super(Conv_Relu, self).__init__()\n", + " self.conv = nn.Conv1d(1, 1, kernel_size=3, stride=1)\n", + " self.relu = nn.ReLU()\n", + "\n", + " def forward(self, x):\n", + " x = self.conv(x)\n", + " x = self.relu(x)\n", + " return x\n", + "\n", + "# Instantiate the models\n", + "mlp_sigmoid = MLP_Sigmoid()\n", + "mlp_relu = MLP_Relu()\n", + "conv_sigmoid = Conv_Sigmoid()\n", + "conv_relu = Conv_Relu()\n", + "\n", + "# Dummy input tensor for mlp\n", + "dummy_input_mlp = torch.randn(1, 3)\n", + "input_mlp_path = 'mlp_input.json'\n", + "\n", + "# Dummy input tensor for conv\n", + "dummy_input_conv = torch.randn(1, 1, 3)\n", + "input_conv_path = 'conv_input.json'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "names = ['mlp_sigmoid', 'mlp_relu', 'conv_sigmoid', 'conv_relu']\n", + "models = [mlp_sigmoid, mlp_relu, conv_sigmoid, conv_relu]\n", + "inputs = [dummy_input_mlp, dummy_input_mlp, dummy_input_conv, dummy_input_conv]\n", + "input_paths = [input_mlp_path, input_mlp_path, input_conv_path, input_conv_path]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import json\n", + "import torch\n", + "import ezkl\n", + "\n", + "for name, model, x, input_path in zip(names, models, inputs, input_paths):\n", + " # Create a new directory for the model if it doesn't exist\n", + " if not os.path.exists(name):\n", + " os.mkdir(name)\n", + " # Store the paths in each of their respective directories\n", + " model_path = os.path.join(name, \"network.onnx\")\n", + " compiled_model_path = os.path.join(name, \"network.compiled\")\n", + " pk_path = os.path.join(name, \"test.pk\")\n", + " vk_path = os.path.join(name, \"test.vk\")\n", + " settings_path = os.path.join(name, \"settings.json\")\n", + "\n", + " witness_path = os.path.join(name, \"witness.json\")\n", + " sol_code_path = os.path.join(name, 'test.sol')\n", + " sol_key_code_path = os.path.join(name, 'test_key.sol')\n", + " abi_path = os.path.join(name, 'test.abi')\n", + " proof_path = os.path.join(name, \"proof.json\")\n", + "\n", + " # Flips the neural net into inference mode\n", + " model.eval()\n", + "\n", + " # Export the model\n", + " torch.onnx.export(model, x, model_path, export_params=True, opset_version=10,\n", + " do_constant_folding=True, input_names=['input'],\n", + " output_names=['output'], dynamic_axes={'input': {0: 'batch_size'},\n", + " 'output': {0: 'batch_size'}})\n", + "\n", + " data_array = ((x).detach().numpy()).reshape([-1]).tolist()\n", + " data = dict(input_data=[data_array])\n", + " json.dump(data, open(input_path, 'w'))\n", + "\n", + " py_run_args = ezkl.PyRunArgs()\n", + " py_run_args.input_visibility = \"private\"\n", + " py_run_args.output_visibility = \"public\"\n", + " py_run_args.param_visibility = \"fixed\" # private by default\n", + "\n", + " res = ezkl.gen_settings(model_path, settings_path, py_run_args=py_run_args)\n", + " assert res == True\n", + "\n", + " await ezkl.calibrate_settings(input_path, model_path, settings_path, \"resources\")\n", + "\n", + " res = ezkl.compile_circuit(model_path, compiled_model_path, settings_path)\n", + " assert res == True\n", + "\n", + " res = await ezkl.get_srs(settings_path)\n", + " assert res == True\n", + "\n", + " # now generate the witness file\n", + " res = await ezkl.gen_witness(input_path, compiled_model_path, witness_path)\n", + " assert os.path.isfile(witness_path) == True\n", + "\n", + " # SETUP \n", + " # We recommend disabling selector compression for the setup as it increase the separated verifier reusability. \n", + " res = ezkl.setup(compiled_model_path, vk_path, pk_path, disable_selector_compression=True)\n", + " assert res == True\n", + " assert os.path.isfile(vk_path)\n", + " assert os.path.isfile(pk_path)\n", + " assert os.path.isfile(settings_path)\n", + "\n", + " # GENERATE A PROOF\n", + " res = ezkl.prove(witness_path, compiled_model_path, pk_path, proof_path, \"single\")\n", + " assert os.path.isfile(proof_path)\n", + "\n", + " res = await ezkl.create_evm_verifier(vk_path, settings_path, sol_code_path, abi_path, render_vk_seperately=True)\n", + " assert res == True\n", + "\n", + " res = await ezkl.create_evm_vk(vk_path, settings_path, sol_key_code_path, abi_path)\n", + " assert res == True\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import subprocess\n", + "import time\n", + "\n", + "# make sure anvil is running locally\n", + "# $ anvil -p 3030\n", + "\n", + "RPC_URL = \"http://localhost:3030\"\n", + "\n", + "# Save process globally\n", + "anvil_process = None\n", + "\n", + "def start_anvil():\n", + " global anvil_process\n", + " if anvil_process is None:\n", + " anvil_process = subprocess.Popen([\"anvil\", \"-p\", \"3030\", \"--code-size-limit=41943040\"])\n", + " if anvil_process.returncode is not None:\n", + " raise Exception(\"failed to start anvil process\")\n", + " time.sleep(3)\n", + "\n", + "def stop_anvil():\n", + " global anvil_process\n", + " if anvil_process is not None:\n", + " anvil_process.terminate()\n", + " anvil_process = None\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Check that the generated verifiers are identical for all models." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import filecmp\n", + "\n", + "def compare_files(file1, file2):\n", + " return filecmp.cmp(file1, file2, shallow=False)\n", + "\n", + "sol_code_path_0 = os.path.join(\"mlp_sigmoid\", 'test.sol')\n", + "sol_code_path_1 = os.path.join(\"mlp_relu\", 'test.sol')\n", + "\n", + "sol_code_path_2 = os.path.join(\"conv_sigmoid\", 'test.sol')\n", + "sol_code_path_3 = os.path.join(\"conv_relu\", 'test.sol')\n", + "\n", + "\n", + "assert compare_files(sol_code_path_0, sol_code_path_1) == True\n", + "assert compare_files(sol_code_path_2, sol_code_path_3) == True" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we deploy separate verifier that will be shared by the four models. We picked the `1l_mlp sigmoid` model as an example but you could have used any of the generated verifiers since they are all identical. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os \n", + "addr_path_verifier = \"addr_verifier.txt\"\n", + "sol_code_path = os.path.join(\"mlp_sigmoid\", 'test.sol')\n", + "\n", + "res = await ezkl.deploy_evm(\n", + " addr_path_verifier,\n", + " sol_code_path,\n", + " 'http://127.0.0.1:3030'\n", + ")\n", + "\n", + "assert res == True\n", + "\n", + "with open(addr_path_verifier, 'r') as file:\n", + " addr = file.read().rstrip()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally we deploy each of the unique VKs and verify them using the shared verifier deployed in the previous step." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for name in names:\n", + " addr_path_vk = \"addr_vk.txt\"\n", + " sol_key_code_path = os.path.join(name, 'test_key.sol')\n", + " res = await ezkl.deploy_vk_evm(addr_path_vk, sol_key_code_path, 'http://127.0.0.1:3030')\n", + " assert res == True\n", + "\n", + " with open(addr_path_vk, 'r') as file:\n", + " addr_vk = file.read().rstrip()\n", + " \n", + " proof_path = os.path.join(name, \"proof.json\")\n", + " sol_code_path = os.path.join(name, 'vk.sol')\n", + " res = await ezkl.verify_evm(\n", + " addr,\n", + " proof_path,\n", + " \"http://127.0.0.1:3030\",\n", + " addr_vk = addr_vk\n", + " )\n", + " assert res == True" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".env", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tests/py_integration_tests.rs b/tests/py_integration_tests.rs index e84646422..d1915d284 100644 --- a/tests/py_integration_tests.rs +++ b/tests/py_integration_tests.rs @@ -201,6 +201,18 @@ mod py_tests { anvil_child.kill().unwrap(); } + #[test] + fn reusable_verifier_notebook_() { + crate::py_tests::init_binary(); + let mut anvil_child = crate::py_tests::start_anvil(false); + let test_dir: TempDir = TempDir::new("reusable_verifier").unwrap(); + let path = test_dir.path().to_str().unwrap(); + crate::py_tests::mv_test_(path, "reusable_verifier.ipynb"); + run_notebook(path, "reusable_verifier.ipynb"); + test_dir.close().unwrap(); + anvil_child.kill().unwrap(); + } + #[test] fn postgres_notebook_() { crate::py_tests::init_binary(); From c92be15b81e8ec6c38446b96f9227a2dec150593 Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 26 Jun 2024 16:11:06 -0500 Subject: [PATCH 02/46] *update lockfile --- Cargo.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index d2e402670..a73e6b948 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2315,13 +2315,14 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#270112550a3e92ee8224aff9f60bdd16068c3cfa" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#8e8e5d3a42909ef6475ba4d370a70ec53c6b4ff1" dependencies = [ "askama", "blake2b_simd", "halo2_proofs", "hex", "itertools 0.11.0", + "regex", "ruint", "sha3 0.10.8", ] From 83cb957299b34a2f40dd6e99704fa114d221dd1c Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 27 Jun 2024 19:46:05 -0500 Subject: [PATCH 03/46] *fix stuck integration tests. --- tests/integration_tests.rs | 57 +++++++++++++++++++++++++++++++++++ tests/py_integration_tests.rs | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 1efa6e9d4..0c07f8e27 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -982,6 +982,51 @@ mod native_tests { use tempdir::TempDir; use crate::native_tests::Hardfork; use crate::native_tests::run_js_tests; + use std::collections::{HashMap, HashSet}; + use std::fs; + use std::hash::{Hash, Hasher}; + use ezkl::logger::init_logger; + + // Global variables to store verifier hashes and identical verifiers + lazy_static! { + static ref VERIFIER_HASHES: std::sync::Mutex>> = std::sync::Mutex::new(HashMap::new()); + static ref IDENTICAL_VERIFIERS: std::sync::Mutex>> = std::sync::Mutex::new(Vec::new()); + } + + fn hash_file_content>(path: P) -> u64 { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + let content = fs::read_to_string(path).expect("failed to read file"); + content.hash(&mut hasher); + hasher.finish() + } + + fn update_verifier_sets(hash: u64, test_name: String) { + match VERIFIER_HASHES.try_lock() { + Ok(mut verifier_hashes) => { + if let Some(set) = verifier_hashes.get_mut(&hash) { + set.insert(test_name.clone()); + log::error!("Updated set of identical verifiers: {:?}", set); + } else { + let mut new_set: HashSet = HashSet::new(); + new_set.insert(test_name); + verifier_hashes.insert(hash, new_set.clone()); + match IDENTICAL_VERIFIERS.try_lock() { + Ok(mut identical_verifiers) => { + identical_verifiers.push(new_set.clone()); + log::error!("New set of identical verifiers: {:?}", new_set); + } + Err(_) => { + log::error!("Failed to acquire lock on IDENTICAL_VERIFIERS"); + } + } + } + } + Err(_) => { + log::error!("Failed to acquire lock on VERIFIER_HASHES"); + } + } + } + /// Currently only on chain inputs that return a non-negative value are supported. const TESTS_ON_CHAIN_INPUT: [&str; 17] = [ @@ -1115,7 +1160,19 @@ mod native_tests { let test_dir = TempDir::new(test).unwrap(); let path = test_dir.path().to_str().unwrap(); crate::native_tests::mv_test_(path, test); let _anvil_child = crate::native_tests::start_anvil(false, Hardfork::Latest); + init_logger(); + log::error!("Running kzg_evm_prove_and_verify_render_seperately_ for test: {}", test); kzg_evm_prove_and_verify_render_seperately(2, path, test.to_string(), "private", "private", "public"); + + // Check for identical verifiers and store the test name + let verifier_path = format!("{}/{}/kzg.sol", path, test); + let hash = hash_file_content(&verifier_path); + + let test_name = test.clone().to_string(); // Clone test name + + update_verifier_sets(hash, test_name); + + #[cfg(not(feature = "icicle"))] run_js_tests(path, test.to_string(), "testBrowserEvmVerify", true); test_dir.close().unwrap(); diff --git a/tests/py_integration_tests.rs b/tests/py_integration_tests.rs index d1915d284..70ea5433a 100644 --- a/tests/py_integration_tests.rs +++ b/tests/py_integration_tests.rs @@ -11,7 +11,7 @@ mod py_tests { static ENV_SETUP: Once = Once::new(); static DOWNLOAD_VOICE_DATA: Once = Once::new(); - //Sure to run this once + // Sure to run this once lazy_static! { static ref CARGO_TARGET_DIR: String = From b3997cd325ffd9ba706e8259849bb4e422332e12 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 28 Jun 2024 09:36:18 -0500 Subject: [PATCH 04/46] lazy static import --- .github/workflows/rust.yml | 2 +- tests/integration_tests.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 10a8ce3d0..7c540b322 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -328,7 +328,7 @@ jobs: - name: Install Anvil run: cargo install --git https://github.com/foundry-rs/foundry --rev c2233ec9fe61e0920c61c6d779bc707252852037 --profile local --locked anvil --force - name: KZG prove and verify tests (EVM + VK rendered seperately) - run: cargo nextest run --release --verbose tests_evm::kzg_evm_prove_and_verify_render_seperately_ --test-threads 1 + run: RUST_LOG=error cargo nextest run --release --verbose tests_evm::kzg_evm_prove_and_verify_render_seperately_ --no-capture - name: KZG prove and verify tests (EVM + kzg all) run: cargo nextest run --release --verbose tests_evm::kzg_evm_kzg_all_prove_and_verify --test-threads 1 - name: KZG prove and verify tests (EVM + kzg inputs) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 0c07f8e27..9fde82373 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -986,6 +986,7 @@ mod native_tests { use std::fs; use std::hash::{Hash, Hasher}; use ezkl::logger::init_logger; + use crate::native_tests::lazy_static; // Global variables to store verifier hashes and identical verifiers lazy_static! { From 985205ae40598c6bbe19068f33a7e2307fea1568 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 2 Jul 2024 00:49:34 -0500 Subject: [PATCH 05/46] *update lock *hardcode sampel inputs for resuable verifiers nb --- Cargo.lock | 2 +- examples/notebooks/reusable_verifier.ipynb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 877364c2c..9ccd16cce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2302,7 +2302,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#8e8e5d3a42909ef6475ba4d370a70ec53c6b4ff1" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#164fa25c9cfd74e52b39cc23f68cda4d577f75d8" dependencies = [ "askama", "blake2b_simd", diff --git a/examples/notebooks/reusable_verifier.ipynb b/examples/notebooks/reusable_verifier.ipynb index 69e7ddd91..17688a9d0 100644 --- a/examples/notebooks/reusable_verifier.ipynb +++ b/examples/notebooks/reusable_verifier.ipynb @@ -20,7 +20,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -80,11 +80,11 @@ "conv_relu = Conv_Relu()\n", "\n", "# Dummy input tensor for mlp\n", - "dummy_input_mlp = torch.randn(1, 3)\n", + "dummy_input_mlp = torch.tensor([[-1.5737053155899048, -1.708398461341858, 0.19544155895709991]])\n", "input_mlp_path = 'mlp_input.json'\n", "\n", "# Dummy input tensor for conv\n", - "dummy_input_conv = torch.randn(1, 1, 3)\n", + "dummy_input_conv = torch.tensor([[[1.4124163389205933, 0.6938204169273376, 1.0664031505584717]]])\n", "input_conv_path = 'conv_input.json'" ] }, From a17aad064b941fbc52fa15b05298b712e98fedfb Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 2 Jul 2024 21:43:38 -0500 Subject: [PATCH 06/46] *update lock --- .github/workflows/rust.yml | 4 ++-- Cargo.lock | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7c540b322..9fc3d5e07 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -677,5 +677,5 @@ jobs: run: source .env/bin/activate; cargo nextest run py_tests::tests::voice_ - name: NBEATS tutorial run: source .env/bin/activate; cargo nextest run py_tests::tests::nbeats_ - - name: Reusable verifier tutorial - run: source .env/bin/activate; cargo nextest run py_tests::tests::reusable_ + # - name: Reusable verifier tutorial + # run: source .env/bin/activate; cargo nextest run py_tests::tests::reusable_ diff --git a/Cargo.lock b/Cargo.lock index 9ccd16cce..6e57eae37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2302,7 +2302,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#164fa25c9cfd74e52b39cc23f68cda4d577f75d8" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#25f27aba5145fd4ce24de7d28e422e4bf9cf70af" dependencies = [ "askama", "blake2b_simd", From bdad19b83c553daf8b1c6249be6e0afaa6e00f1a Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 3 Jul 2024 21:00:58 -0500 Subject: [PATCH 07/46] *update lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 6e57eae37..d4dc865bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2302,7 +2302,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#25f27aba5145fd4ce24de7d28e422e4bf9cf70af" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#85a0d2c9c4be4f91e468bc900bafbaf44d754406" dependencies = [ "askama", "blake2b_simd", From 2fe0eb4b2745971f30d2df744e4b487547855bce Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 6 Jul 2024 23:05:48 -0500 Subject: [PATCH 08/46] *update lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index d4dc865bd..28e6cc016 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2302,7 +2302,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#85a0d2c9c4be4f91e468bc900bafbaf44d754406" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#57a969b0815761288995120684fbab40e9e83896" dependencies = [ "askama", "blake2b_simd", From 257e27577346779c91a12e2b7df34202ca9c0a93 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 11 Jul 2024 00:23:19 -0500 Subject: [PATCH 09/46] *update lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 28e6cc016..b385e7a0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2302,7 +2302,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#57a969b0815761288995120684fbab40e9e83896" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#591389d0ad29e4a44b3a013c267cbbed2f1a14bc" dependencies = [ "askama", "blake2b_simd", From f59aaf80c599b87518bc3272809ed187c5c0907b Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 11 Jul 2024 15:24:28 -0500 Subject: [PATCH 10/46] *update lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index b385e7a0e..dfe971819 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2302,7 +2302,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#591389d0ad29e4a44b3a013c267cbbed2f1a14bc" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#37945c185b14bff85fc204ce7db76a39995705f0" dependencies = [ "askama", "blake2b_simd", From f25b420429c5ca94e08c7867b7c943193f9df46b Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 11 Jul 2024 22:47:42 -0500 Subject: [PATCH 11/46] *update lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index dfe971819..13c15e211 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2302,7 +2302,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#37945c185b14bff85fc204ce7db76a39995705f0" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#f9149c7c90a0e14d276f6d87cd0e454c94e69e00" dependencies = [ "askama", "blake2b_simd", From 36188ab5423a00379c488ada0eb5b62264a77253 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 12 Jul 2024 16:00:46 -0500 Subject: [PATCH 12/46] *comment out JS tests for reusable verifier CI tests --- tests/integration_tests.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 70a4a139c..597f11cda 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1176,7 +1176,8 @@ mod native_tests { #[cfg(not(feature = "icicle"))] - run_js_tests(path, test.to_string(), "testBrowserEvmVerify", true); + // TODO: Add path for reusable verifier in in browser evm verifier + // run_js_tests(path, test.to_string(), "testBrowserEvmVerify", true); test_dir.close().unwrap(); } From dbe812b88d3b32199b1ec2d9765a96faf224d5b6 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 12 Jul 2024 22:14:13 -0500 Subject: [PATCH 13/46] *update lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 861e5b941..683ecd243 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2302,7 +2302,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#f9149c7c90a0e14d276f6d87cd0e454c94e69e00" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#792d609c7f49dfb29d66a631627424241b247bcb" dependencies = [ "askama", "blake2b_simd", From a1dd82a3c1df9a87166fbf42cc8ac97129cd4900 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 19 Jul 2024 18:20:26 -0500 Subject: [PATCH 14/46] *update lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 04331ac75..e98b26417 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2317,7 +2317,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#792d609c7f49dfb29d66a631627424241b247bcb" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#d3894926e5356ff9bb8f895986db63ce241a5432" dependencies = [ "askama", "blake2b_simd", From 3e5153db9f68eb9ac6add99cb19f1a3224674a45 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 23 Jul 2024 14:59:49 -0500 Subject: [PATCH 15/46] *update lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index e98b26417..c583b4601 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2317,7 +2317,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#d3894926e5356ff9bb8f895986db63ce241a5432" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#5c5f76deaa7a70a0dce44954228fb745ebb2b437" dependencies = [ "askama", "blake2b_simd", From 6b71bdc920ca3e427dd4620d3a7c41f943f10bf3 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 23 Jul 2024 15:01:42 -0500 Subject: [PATCH 16/46] use latest version of solc --- .github/workflows/rust.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 61e4d46b5..bb9ad0de9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -326,7 +326,7 @@ jobs: pnpm build:commonjs cd .. - name: Install solc - run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.20 && solc --version + run: cargo install svm-rs && svm install 0.8.26 && svm use 0.8.26 && solc --version - name: Install Anvil run: cargo install --git https://github.com/foundry-rs/foundry --rev c2233ec9fe61e0920c61c6d779bc707252852037 --profile local --locked anvil --force - name: KZG prove and verify tests (EVM + VK rendered seperately) @@ -535,7 +535,7 @@ jobs: crate: cargo-nextest locked: true - name: Install solc - run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.20 && solc --version + run: cargo install svm-rs && svm install 0.8.26 && svm use 0.8.26 && solc --version - name: Install Anvil run: cargo install --git https://github.com/foundry-rs/foundry --rev c2233ec9fe61e0920c61c6d779bc707252852037 --profile local --locked anvil --force - name: KZG prove and verify aggr tests @@ -574,7 +574,7 @@ jobs: - name: Install cmake run: sudo apt-get install -y cmake - name: Install solc - run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.20 && solc --version + run: cargo install svm-rs && svm install 0.8.26 && svm use 0.8.26 && solc --version - name: Setup Virtual Env and Install python dependencies run: python -m venv .env --clear; source .env/bin/activate; pip install -r requirements.txt; - name: Install Anvil @@ -651,7 +651,7 @@ jobs: crate: cargo-nextest locked: true - name: Install solc - run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.20 && solc --version + run: cargo install svm-rs && svm install 0.8.26 && svm use 0.8.26 && solc --version - name: Install Anvil run: cargo install --git https://github.com/foundry-rs/foundry --rev c2233ec9fe61e0920c61c6d779bc707252852037 --profile local --locked anvil --force - name: Install pip From d5f18495dec33678fce3a701bbcedf438835d615 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 26 Jul 2024 15:10:41 -0500 Subject: [PATCH 17/46] *update lock. --- .github/workflows/rust.yml | 8 ++++---- Cargo.lock | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4a6de21b0..4055df544 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -328,7 +328,7 @@ jobs: pnpm build:commonjs cd .. - name: Install solc - run: cargo install svm-rs && svm install 0.8.26 && svm use 0.8.26 && solc --version + run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.26 && svm use 0.8.26 && solc --version - name: Install Anvil run: cargo install --git https://github.com/foundry-rs/foundry --rev 62cdea8ff9e6efef011f77e295823b5f2dbeb3a1 --locked anvil --force - name: KZG prove and verify tests (EVM + VK rendered seperately) @@ -537,7 +537,7 @@ jobs: crate: cargo-nextest locked: true - name: Install solc - run: cargo install svm-rs && svm install 0.8.26 && svm use 0.8.26 && solc --version + run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.26 && svm use 0.8.26 && solc --version - name: Install Anvil run: cargo install --git https://github.com/foundry-rs/foundry --rev 62cdea8ff9e6efef011f77e295823b5f2dbeb3a1 --locked anvil --force - name: KZG prove and verify aggr tests @@ -576,7 +576,7 @@ jobs: - name: Install cmake run: sudo apt-get install -y cmake - name: Install solc - run: cargo install svm-rs && svm install 0.8.26 && svm use 0.8.26 && solc --version + run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.26 && svm use 0.8.26 && solc --version - name: Setup Virtual Env and Install python dependencies run: python -m venv .env --clear; source .env/bin/activate; pip install -r requirements.txt; - name: Install Anvil @@ -653,7 +653,7 @@ jobs: crate: cargo-nextest locked: true - name: Install solc - run: cargo install svm-rs && svm install 0.8.26 && svm use 0.8.26 && solc --version + run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.26 && svm use 0.8.26 && solc --version - name: Install Anvil run: cargo install --git https://github.com/foundry-rs/foundry --rev 62cdea8ff9e6efef011f77e295823b5f2dbeb3a1 --locked anvil --force - name: Install pip diff --git a/Cargo.lock b/Cargo.lock index f4a67fff5..5f9c9aeb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2326,7 +2326,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#5c5f76deaa7a70a0dce44954228fb745ebb2b437" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#c6a19a54c36ec08297c8a22c9ea5e81b907c6dc2" dependencies = [ "askama", "blake2b_simd", From 099726b245f6c83c792d068daae5bbfc557c43c0 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 26 Jul 2024 22:14:41 -0500 Subject: [PATCH 18/46] update lock. --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 5f9c9aeb0..930720364 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2326,7 +2326,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#c6a19a54c36ec08297c8a22c9ea5e81b907c6dc2" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#4c25caa2eacaab37f8cd28c175cc95000833b01b" dependencies = [ "askama", "blake2b_simd", From 00f8dd42f689bf0af35b9a4276f197a07004ba5d Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 28 Jul 2024 18:06:56 -0500 Subject: [PATCH 19/46] *update lock *revert to svm 0.8.20 --- .github/workflows/rust.yml | 8 ++++---- Cargo.lock | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4055df544..799f917c0 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -328,7 +328,7 @@ jobs: pnpm build:commonjs cd .. - name: Install solc - run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.26 && svm use 0.8.26 && solc --version + run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.20 && solc --version - name: Install Anvil run: cargo install --git https://github.com/foundry-rs/foundry --rev 62cdea8ff9e6efef011f77e295823b5f2dbeb3a1 --locked anvil --force - name: KZG prove and verify tests (EVM + VK rendered seperately) @@ -537,7 +537,7 @@ jobs: crate: cargo-nextest locked: true - name: Install solc - run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.26 && svm use 0.8.26 && solc --version + run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.20 && solc --version - name: Install Anvil run: cargo install --git https://github.com/foundry-rs/foundry --rev 62cdea8ff9e6efef011f77e295823b5f2dbeb3a1 --locked anvil --force - name: KZG prove and verify aggr tests @@ -576,7 +576,7 @@ jobs: - name: Install cmake run: sudo apt-get install -y cmake - name: Install solc - run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.26 && svm use 0.8.26 && solc --version + run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.20 && solc --version - name: Setup Virtual Env and Install python dependencies run: python -m venv .env --clear; source .env/bin/activate; pip install -r requirements.txt; - name: Install Anvil @@ -653,7 +653,7 @@ jobs: crate: cargo-nextest locked: true - name: Install solc - run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.26 && svm use 0.8.26 && solc --version + run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.20 && solc --version - name: Install Anvil run: cargo install --git https://github.com/foundry-rs/foundry --rev 62cdea8ff9e6efef011f77e295823b5f2dbeb3a1 --locked anvil --force - name: Install pip diff --git a/Cargo.lock b/Cargo.lock index 930720364..29955c803 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2326,7 +2326,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#4c25caa2eacaab37f8cd28c175cc95000833b01b" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#0654a72b007e8002c2a3eede3565205ea560311a" dependencies = [ "askama", "blake2b_simd", From f3e531c3e730cc1a8a1a61bcf344c7e8572efd58 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 28 Jul 2024 21:52:29 -0500 Subject: [PATCH 20/46] *update lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 29955c803..7bd0636e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2326,7 +2326,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#0654a72b007e8002c2a3eede3565205ea560311a" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#1f69bbb10e12663fafd9e79e8f844a979c7ec27e" dependencies = [ "askama", "blake2b_simd", From 72f1892d54bea6da94149e1ff59f87aaad0d5805 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 29 Jul 2024 06:18:31 -0500 Subject: [PATCH 21/46] *update lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 7bd0636e0..1ac0659da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2326,7 +2326,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#1f69bbb10e12663fafd9e79e8f844a979c7ec27e" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#a3ff8d527afd5dec9ccb28b067f9f20b85a18f32" dependencies = [ "askama", "blake2b_simd", From fab08bbe9d996bc5c9ef6a5be86334df58f31f94 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 29 Jul 2024 14:08:16 -0500 Subject: [PATCH 22/46] *update cargo lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 1ac0659da..83859ee98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2326,7 +2326,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#a3ff8d527afd5dec9ccb28b067f9f20b85a18f32" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#2095a707ddb47b11d377b9043a06bc6e1dea69a2" dependencies = [ "askama", "blake2b_simd", From 889db3a6feddb0a317f93045bf80c1a804a867a5 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 29 Jul 2024 18:16:33 -0500 Subject: [PATCH 23/46] *update lock. --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 83859ee98..49106dc84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2326,7 +2326,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#2095a707ddb47b11d377b9043a06bc6e1dea69a2" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#8e21bfff956800eca67685a9caeb1a09656106d2" dependencies = [ "askama", "blake2b_simd", From 779f82e0dc1659fa5415535cb05b1603bcefc497 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 29 Jul 2024 20:35:10 -0500 Subject: [PATCH 24/46] *vanish computations pcs --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 49106dc84..579195ef1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2326,7 +2326,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#8e21bfff956800eca67685a9caeb1a09656106d2" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#030051f67598cb7c515cb862ec1b7cfc1cd75f31" dependencies = [ "askama", "blake2b_simd", From 3df63d540df1318ba7b6af71bed0a255cacb1afd Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 30 Jul 2024 19:18:23 -0500 Subject: [PATCH 25/46] coeff_computations. --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 579195ef1..349cf4467 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2326,7 +2326,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#030051f67598cb7c515cb862ec1b7cfc1cd75f31" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#9fe8835888e986e398b2795d6cc99e060cb8e417" dependencies = [ "askama", "blake2b_simd", From d5944d36fe711ecadf6b1c5a79aa91815b368618 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 1 Aug 2024 15:18:17 -0500 Subject: [PATCH 26/46] *r_evals_computation --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 349cf4467..e99e73e42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2326,7 +2326,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#9fe8835888e986e398b2795d6cc99e060cb8e417" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#bcb85f71c3b8045077b46da168b0568f8a542b86" dependencies = [ "askama", "blake2b_simd", From 35bb286d40fe51fdc9a0804c59c2ac1f83552888 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 1 Aug 2024 21:11:47 -0500 Subject: [PATCH 27/46] *coeff_sums_computation --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index e99e73e42..4b0e50347 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2326,7 +2326,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#bcb85f71c3b8045077b46da168b0568f8a542b86" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#cfeea110e952fec45fe1c6e26122631e4218b30f" dependencies = [ "askama", "blake2b_simd", From 31168b0a99a523d8e15c2141cdf8f05b752a3204 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 3 Aug 2024 01:10:26 -0500 Subject: [PATCH 28/46] *fully reusable verifier --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 4b0e50347..7f49dcfa2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2326,7 +2326,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#cfeea110e952fec45fe1c6e26122631e4218b30f" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#b2b636cfc49ee36d243cd46933cc338ed31e10af" dependencies = [ "askama", "blake2b_simd", From 23f71873ceb8ced2156d0db0b36f2db1450ba2a7 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 5 Aug 2024 10:37:06 -0400 Subject: [PATCH 29/46] *update lock --- Cargo.lock | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f49dcfa2..0e6f08cd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2306,7 +2306,7 @@ dependencies = [ [[package]] name = "halo2_proofs" version = "0.3.0" -source = "git+https://github.com/zkonduit/halo2#8cfca221f53069a0374687654882b99e729041d7#8cfca221f53069a0374687654882b99e729041d7" +source = "git+https://github.com/zkonduit/halo2#8cfca221f53069a0374687654882b99e729041d7#098ac0ef3b29255e0e2524ecbb4e7e325ae6e7fd" dependencies = [ "blake2b_simd", "env_logger", @@ -2319,6 +2319,7 @@ dependencies = [ "rand_chacha", "rand_core 0.6.4", "rustacuda", + "rustc-hash 2.0.0", "sha3 0.9.1", "tracing", ] @@ -4029,7 +4030,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 1.1.0", "rustls", "thiserror", "tokio", @@ -4045,7 +4046,7 @@ dependencies = [ "bytes", "rand 0.8.5", "ring", - "rustc-hash", + "rustc-hash 1.1.0", "rustls", "slab", "thiserror", @@ -4469,6 +4470,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc-hex" version = "2.1.0" From a616fbb75937e880d635ac304dac61411bf961b5 Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 7 Aug 2024 16:34:39 -0400 Subject: [PATCH 30/46] packed permutation evals and challenge data. --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index aa0214e20..3fd4cc8f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2327,7 +2327,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#b2b636cfc49ee36d243cd46933cc338ed31e10af" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#27f6052d59b4b04b05ba9f762e05efaea8357a56" dependencies = [ "askama", "blake2b_simd", From fd29794cdd80178817d18d6cddd12dddbf72e6af Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 8 Aug 2024 13:55:10 -0400 Subject: [PATCH 31/46] hardcode coeff_ptr --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 3fd4cc8f4..8faf4806e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2327,7 +2327,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#27f6052d59b4b04b05ba9f762e05efaea8357a56" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#18ed00ae867c6234837471a3eedbf53d1d77f2d9" dependencies = [ "askama", "blake2b_simd", From 9d876d5bf97715151c3a9eb159ce2609073dd2f0 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 9 Aug 2024 17:34:22 -0400 Subject: [PATCH 32/46] MV lookup packed. --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 8faf4806e..21cbf5c5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2327,7 +2327,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#18ed00ae867c6234837471a3eedbf53d1d77f2d9" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#1d60e0c66c7a63dac5f6efdde4c7b36b214bf180" dependencies = [ "askama", "blake2b_simd", From 1588c9735ec0e84c73c7deb47ec5b9c44c3440c5 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 13 Aug 2024 20:22:52 -0400 Subject: [PATCH 33/46] *update lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 21cbf5c5b..30951ce53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2327,7 +2327,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#1d60e0c66c7a63dac5f6efdde4c7b36b214bf180" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#3fac35c24745ae0c9e8925fcc4a62a7caede58e8" dependencies = [ "askama", "blake2b_simd", From 472a505063e1dac7a282f8ca3e2aeed338e768dc Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 14 Aug 2024 13:07:37 -0400 Subject: [PATCH 34/46] *update separate vk contract name --- src/execute.rs | 4 ++-- src/python.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/execute.rs b/src/execute.rs index 662a3b5a8..646a62934 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -465,7 +465,7 @@ pub async fn run(command: Commands) -> Result { addr_path.unwrap_or(DEFAULT_CONTRACT_ADDRESS_VK.into()), optimizer_runs, private_key, - "Halo2VerifyingKey", + "Halo2VerifyingArtifact", ) .await } @@ -1498,7 +1498,7 @@ pub(crate) async fn create_evm_vk( File::create(sol_code_path.clone())?.write_all(vk_solidity.as_bytes())?; // fetch abi of the contract - let (abi, _, _) = get_contract_artifacts(sol_code_path, "Halo2VerifyingKey", 0).await?; + let (abi, _, _) = get_contract_artifacts(sol_code_path, "Halo2VerifyingArtifact", 0).await?; // save abi to file serde_json::to_writer(std::fs::File::create(abi_path)?, &abi)?; diff --git a/src/python.rs b/src/python.rs index 69fa7be82..31c1e4018 100644 --- a/src/python.rs +++ b/src/python.rs @@ -1756,7 +1756,7 @@ fn deploy_vk_evm( addr_path, optimizer_runs, private_key, - "Halo2VerifyingKey", + "Halo2VerifyingArtifact", ) .await .map_err(|e| { From cd1860246c856cf21fcb3ab1112a2416c4c90245 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 29 Aug 2024 16:43:16 -0400 Subject: [PATCH 35/46] main lock --- Cargo.lock | 211 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 124 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c97bc22f4..6f29e6f56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1191,7 +1191,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.6", ] [[package]] @@ -2107,9 +2107,9 @@ dependencies = [ [[package]] name = "fs4" -version = "0.8.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e180ac76c23b45e767bd7ae9579bc0bb458618c4bc71835926e098e61d15f8" +checksum = "e8c6b3bd49c37d2aa3f3f2220233b29a7cd23f79d1fe70e5337d25fb390793de" dependencies = [ "rustix", "windows-sys 0.52.0", @@ -2332,7 +2332,7 @@ dependencies = [ "rand_chacha", "rand_core 0.6.4", "rustacuda", - "rustc-hash 2.0.0", + "rustc-hash", "serde", "sha3 0.9.1", "tracing", @@ -2341,7 +2341,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#3fac35c24745ae0c9e8925fcc4a62a7caede58e8" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#bf04cd4e0b3e364a9989d0e0d54e5da59cb1ed6c" dependencies = [ "askama", "blake2b_simd", @@ -2552,9 +2552,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -2641,9 +2641,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" dependencies = [ "bytes", "futures-channel", @@ -2901,9 +2901,9 @@ dependencies = [ [[package]] name = "keccak-asm" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a3633291834c4fbebf8673acbc1b04ec9d151418ff9b8e26dcd79129928758" +checksum = "422fbc7ff2f2f5bdffeb07718e5a5324dca72b0c9293d50df4026652385e3314" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -3078,9 +3078,9 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "lru" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" dependencies = [ "hashbrown 0.14.3", ] @@ -3169,8 +3169,8 @@ dependencies = [ [[package]] name = "metal" -version = "0.27.0" -source = "git+https://github.com/gfx-rs/metal-rs#ff8fd3d6dc7792852f8a015458d7e6d42d7fb352" +version = "0.29.0" +source = "git+https://github.com/gfx-rs/metal-rs#0e1918b34689c4b8cd13a43372f9898680547ee9" dependencies = [ "bitflags 2.5.0", "block", @@ -4072,16 +4072,17 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quinn" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" +checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" dependencies = [ "bytes", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 1.1.0", + "rustc-hash", "rustls", + "socket2", "thiserror", "tokio", "tracing", @@ -4089,14 +4090,14 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe" +checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" dependencies = [ "bytes", "rand 0.8.5", "ring", - "rustc-hash 1.1.0", + "rustc-hash", "rustls", "slab", "thiserror", @@ -4106,9 +4107,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" dependencies = [ "libc", "once_cell", @@ -4306,9 +4307,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.5" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ "base64 0.22.1", "bytes", @@ -4343,6 +4344,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls", + "tokio-socks", "tokio-util", "tower-service", "url", @@ -4351,7 +4353,7 @@ dependencies = [ "wasm-streams", "web-sys", "webpki-roots", - "winreg", + "windows-registry", ] [[package]] @@ -4514,12 +4516,6 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.0.0" @@ -4580,9 +4576,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.11" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "once_cell", "ring", @@ -4594,9 +4590,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" +checksum = "04182dffc9091a404e0fc069ea5cd60e5b866c3adf881eff99a32d048242dffa" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -4607,9 +4603,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ "base64 0.22.1", "rustls-pki-types", @@ -4617,15 +4613,15 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" -version = "0.102.5" +version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "ring", "rustls-pki-types", @@ -4886,9 +4882,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9b57fd861253bff08bb1919e995f90ba8f4889de2726091c8876f3a4e823b40" +checksum = "57d79b758b7cb2085612b11a235055e485605a5103faccdd633f35bd7aee69dd" dependencies = [ "cc", "cfg-if", @@ -4943,7 +4939,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "snark-verifier" version = "0.1.1" -source = "git+https://github.com/zkonduit/snark-verifier?branch=ac/chunked-mv-lookup#574b65ea6b4d43eebac5565146519a95b435815c" +source = "git+https://github.com/zkonduit/snark-verifier?branch=ac/chunked-mv-lookup#8762701ab8fa04e7d243a346030afd85633ec970" dependencies = [ "ecc", "halo2_proofs", @@ -5081,14 +5077,13 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "svm-rs" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af5910befd515534a92e9424f250d952fe6f6dba6a92bd001dfeba1fb4a2f87c" +checksum = "7b638fb4b6a74ef632a18935d240596b2618c8eb5c888e9cbf3c689af9a4aded" dependencies = [ "const-hex", "dirs", "fs4", - "once_cell", "reqwest", "semver 1.0.22", "serde", @@ -5101,9 +5096,9 @@ dependencies = [ [[package]] name = "svm-rs-builds" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5ea000fdbeab0b2739315f9093c75ea63030e5c44f92daa72401d11b48adda" +checksum = "813b21b9858cc493134b899c96e73c60f2e6043f050a17020e98ad8c2d9c9912" dependencies = [ "build_const", "const-hex", @@ -5151,6 +5146,9 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "tabled" @@ -5434,6 +5432,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-socks" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" +dependencies = [ + "either", + "futures-util", + "thiserror", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.15" @@ -5513,9 +5523,9 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" @@ -6111,7 +6121,37 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", ] [[package]] @@ -6129,7 +6169,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", ] [[package]] @@ -6149,17 +6189,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -6170,9 +6211,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -6182,9 +6223,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -6194,9 +6235,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -6206,9 +6253,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -6218,9 +6265,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -6230,9 +6277,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -6242,9 +6289,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -6264,16 +6311,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wyz" version = "0.5.1" From 4d18a5a903bb4d44864651662a3f5dd165ab747a Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 2 Sep 2024 18:01:40 -0500 Subject: [PATCH 36/46] *rmv create_evm_vk cmd *test reusable verifier after h2 curve updates --- .github/workflows/rust.yml | 4 +- Cargo.lock | 3 +- examples/notebooks/reusable_verifier.ipynb | 29 ++-- src/commands.rs | 148 ++++++++++++----- src/execute.rs | 60 +++---- src/python.rs | 69 +++----- tests/integration_tests.rs | 176 +++++++++------------ tests/python/binding_tests.py | 18 ++- 8 files changed, 261 insertions(+), 246 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index aaafa74ba..63b70771a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -331,8 +331,8 @@ jobs: # run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.20 && solc --version - name: Install Anvil run: cargo install --git https://github.com/foundry-rs/foundry --rev 62cdea8ff9e6efef011f77e295823b5f2dbeb3a1 --locked anvil --force - - name: KZG prove and verify tests (EVM + VK rendered seperately) - run: RUST_LOG=error cargo nextest run --release --verbose tests_evm::kzg_evm_prove_and_verify_render_seperately_ --no-capture + - name: KZG prove and verify tests (EVM + reusable verifier) + run: cargo nextest run --release --verbose tests_evm::kzg_evm_prove_and_verify_reusable_verifier_ --test-threads 1 - name: KZG prove and verify tests (EVM + kzg all) run: cargo nextest run --release --verbose tests_evm::kzg_evm_kzg_all_prove_and_verify --test-threads 1 - name: KZG prove and verify tests (EVM + kzg inputs) diff --git a/Cargo.lock b/Cargo.lock index 6f29e6f56..dcf86bba1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2341,14 +2341,13 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#bf04cd4e0b3e364a9989d0e0d54e5da59cb1ed6c" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#b544e4b1ca3a0456fd3483dca6b1cd559e6780af" dependencies = [ "askama", "blake2b_simd", "halo2_proofs", "hex", "itertools 0.11.0", - "regex", "ruint", "sha3 0.10.8", ] diff --git a/examples/notebooks/reusable_verifier.ipynb b/examples/notebooks/reusable_verifier.ipynb index 17688a9d0..8af0b52ac 100644 --- a/examples/notebooks/reusable_verifier.ipynb +++ b/examples/notebooks/reusable_verifier.ipynb @@ -13,9 +13,10 @@ "- `1l_conv sigmoid`\n", "- `1l_conv relu`\n", "\n", - "When deploying EZKL verifiers on the blockchain, each associated model typically requires its own unique verifier, leading to increased on-chain state usage. However, if the models have a sufficiently similar architecture, it is possible to deploy a single set of verifiers for multiple models. This notebook showcases how to achieve this by creating reusable verifiers for the four models listed above.\n", + "When deploying EZKL verifiers on the blockchain, each associated model typically requires its own unique verifier, leading to increased on-chain state usage. \n", + "However, with the reusable verifier, we can deploy a single verifier that can be used to verify proofs for any valid H2 circuit. This notebook shows how to do so. \n", "\n", - "By reusing the same verifier across multiple models, we significantly reduce the amount of state required on the blockchain. Instead of deploying a unique verifier for each model, we deploy a unique and much smaller verification key (VK) contract for each model while sharing a common separated verifier." + "By reusing the same verifier across multiple models, we significantly reduce the amount of state bloat on the blockchain. Instead of deploying a unique verifier for each model, we deploy a unique and much smaller verifying key artifact (VKA) contract for each model while sharing a common separated verifier. The VKA contains the VK for the model as well circuit specific metadata that was otherwise hardcoded into the stack of the original non-reusable verifier." ] }, { @@ -162,7 +163,7 @@ " assert os.path.isfile(witness_path) == True\n", "\n", " # SETUP \n", - " # We recommend disabling selector compression for the setup as it increase the separated verifier reusability. \n", + " # We recommend disabling selector compression for the setup as it decreases the size of the VK artifact\n", " res = ezkl.setup(compiled_model_path, vk_path, pk_path, disable_selector_compression=True)\n", " assert res == True\n", " assert os.path.isfile(vk_path)\n", @@ -173,10 +174,10 @@ " res = ezkl.prove(witness_path, compiled_model_path, pk_path, proof_path, \"single\")\n", " assert os.path.isfile(proof_path)\n", "\n", - " res = await ezkl.create_evm_verifier(vk_path, settings_path, sol_code_path, abi_path, render_vk_seperately=True)\n", + " res = await ezkl.create_evm_verifier(vk_path, settings_path, sol_code_path, abi_path, reusable=True)\n", " assert res == True\n", "\n", - " res = await ezkl.create_evm_vk(vk_path, settings_path, sol_key_code_path, abi_path)\n", + " res = await ezkl.create_evm_vka(vk_path, settings_path, sol_key_code_path, abi_path)\n", " assert res == True\n" ] }, @@ -219,6 +220,15 @@ "Check that the generated verifiers are identical for all models." ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "start_anvil()" + ] + }, { "cell_type": "code", "execution_count": null, @@ -261,7 +271,8 @@ "res = await ezkl.deploy_evm(\n", " addr_path_verifier,\n", " sol_code_path,\n", - " 'http://127.0.0.1:3030'\n", + " 'http://127.0.0.1:3030',\n", + " \"verifier/reusable\"\n", ")\n", "\n", "assert res == True\n", @@ -274,7 +285,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Finally we deploy each of the unique VKs and verify them using the shared verifier deployed in the previous step." + "Finally we deploy each of the unique VK-artifacts and verify them using the shared verifier deployed in the previous step." ] }, { @@ -286,7 +297,7 @@ "for name in names:\n", " addr_path_vk = \"addr_vk.txt\"\n", " sol_key_code_path = os.path.join(name, 'test_key.sol')\n", - " res = await ezkl.deploy_vk_evm(addr_path_vk, sol_key_code_path, 'http://127.0.0.1:3030')\n", + " res = await ezkl.deploy_evm(addr_path_vk, sol_key_code_path, 'http://127.0.0.1:3030', \"vka\")\n", " assert res == True\n", "\n", " with open(addr_path_vk, 'r') as file:\n", @@ -320,7 +331,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.11.5" } }, "nbformat": 4, diff --git a/src/commands.rs b/src/commands.rs index 4c5570031..baba9b874 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -81,8 +81,10 @@ pub const DEFAULT_CALIBRATION_FILE: &str = "calibration.json"; pub const DEFAULT_LOOKUP_SAFETY_MARGIN: &str = "2"; /// Default Compress selectors pub const DEFAULT_DISABLE_SELECTOR_COMPRESSION: &str = "false"; -/// Default render vk separately -pub const DEFAULT_RENDER_VK_SEPERATELY: &str = "false"; +/// Default render reusable verifier +pub const DEFAULT_RENDER_REUSABLE: &str = "false"; +/// Default contract deployment type +pub const DEFAULT_CONTRACT_DEPLOYMENT_TYPE: &str = "verifier"; /// Default VK sol path pub const DEFAULT_VK_SOL: &str = "vk.sol"; /// Default VK abi path @@ -181,6 +183,67 @@ impl From<&str> for CalibrationTarget { } } +#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, PartialOrd)] +/// Determines what type of contract (verifier, verifier/reusable, vka) should be deployed +pub enum ContractType { + /// Deploys a verifier contrat tailored to the circuit and not reusable + Verifier { + /// Whether to deploy a reusable verifier. This can reduce state bloat on-chain since you need only deploy a verifying key artifact (vka) for a given circuit which is significantly smaller than the verifier contract (up to 4 times smaller for large circuits) + /// Can also be used as an alternative to aggregation for verifiers that are otherwise too large to fit on-chain. + reusable: bool, + }, + /// Deploys a verifying key artifact that the reusable verifier loads into memory during runtime. Encodes the circuit specific data that was otherwise hardcoded onto the stack. + VerifyingKeyArtifact, +} + +impl Default for ContractType { + fn default() -> Self { + ContractType::Verifier { + reusable: false, + } + } +} + +impl std::fmt::Display for ContractType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + ContractType::Verifier { reusable: true } => { + "verifier/reusable".to_string() + }, + ContractType::Verifier { + reusable: false, + } => "verifier".to_string(), + ContractType::VerifyingKeyArtifact => "vka".to_string(), + } + ) + } +} + +impl ToFlags for ContractType { + fn to_flags(&self) -> Vec { + vec![format!("{}", self)] + } +} + +impl From<&str> for ContractType { + fn from(s: &str) -> Self { + match s { + "verifier" => ContractType::Verifier { reusable: false }, + "verifier/reusable" => ContractType::Verifier { reusable: true }, + "vka" => ContractType::VerifyingKeyArtifact, + _ => { + log::error!("Invalid value for ContractType"); + log::warn!("Defaulting to verifier"); + ContractType::default() + } + } + } +} + + #[cfg(not(target_arch = "wasm32"))] #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, PartialOrd)] /// wrapper for H160 to make it easy to parse into flag vals @@ -243,6 +306,39 @@ impl<'source> FromPyObject<'source> for CalibrationTarget { } } } + +#[cfg(feature = "python-bindings")] +/// Converts ContractType into a PyObject (Required for ContractType to be compatible with Python) +impl IntoPy for ContractType { + fn into_py(self, py: Python) -> PyObject { + match self { + ContractType::Verifier { reusable: true } => { + "verifier/reusable".to_object(py) + } + ContractType::Verifier { + reusable: false, + } => "verifier".to_object(py), + ContractType::VerifyingKeyArtifact => "vka".to_object(py), + } + } +} + +#[cfg(feature = "python-bindings")] +/// Obtains ContractType from PyObject (Required for ContractType to be compatible with Python) +impl<'source> FromPyObject<'source> for ContractType { + fn extract(ob: &'source PyAny) -> PyResult { + let trystr = ::try_from(ob)?; + let strval = trystr.to_string(); + match strval.to_lowercase().as_str() { + "verifier" => Ok(ContractType::Verifier { + reusable: false, + }), + "verifier/reusable" => Ok(ContractType::Verifier { reusable: true }), + "vka" => Ok(ContractType::VerifyingKeyArtifact), + _ => Err(PyValueError::new_err("Invalid value for ContractType")), + } + } +} // not wasm use lazy_static::lazy_static; @@ -666,16 +762,14 @@ pub enum Commands { /// The path to output the Solidity verifier ABI #[arg(long, default_value = DEFAULT_VERIFIER_ABI, value_hint = clap::ValueHint::FilePath)] abi_path: Option, - /// Whether the verifier key should be rendered as a separate contract. - /// We recommend disabling selector compression if this is enabled. - /// To save the verifier key as a separate contract, set this to true and then call the create-evm-vk command. - #[arg(long, default_value = DEFAULT_RENDER_VK_SEPERATELY, action = clap::ArgAction::SetTrue)] - render_vk_seperately: Option, + /// Whether the to render the verifier as reusable or not. If true, you will need to deploy a VK artifact, passing it as part of the calldata to the verifier. + #[arg(long, default_value = DEFAULT_RENDER_REUSABLE, action = clap::ArgAction::SetTrue)] + reusable: Option, }, #[cfg(not(target_arch = "wasm32"))] - /// Creates an Evm verifier for a single proof - #[command(name = "create-evm-vk")] - CreateEvmVK { + /// Creates an Evm verifier artifact for a single proof to be used by the reusable verifier + #[command(name = "create-evm-vka")] + CreateEvmVKArtifact { /// The path to SRS, if None will use $EZKL_REPO_PATH/srs/kzg{logrows}.srs #[arg(long, value_hint = clap::ValueHint::FilePath)] srs_path: Option, @@ -739,11 +833,9 @@ pub enum Commands { // logrows used for aggregation circuit #[arg(long, default_value = DEFAULT_AGGREGATED_LOGROWS, value_hint = clap::ValueHint::Other)] logrows: Option, - /// Whether the verifier key should be rendered as a separate contract. - /// We recommend disabling selector compression if this is enabled. - /// To save the verifier key as a separate contract, set this to true and then call the create-evm-vk command. - #[arg(long, default_value = DEFAULT_RENDER_VK_SEPERATELY, action = clap::ArgAction::SetTrue)] - render_vk_seperately: Option, + /// Whether the to render the verifier as reusable or not. If true, you will need to deploy a VK artifact, passing it as part of the calldata to the verifier. + #[arg(long, default_value = DEFAULT_RENDER_REUSABLE, action = clap::ArgAction::SetTrue)] + reusable: Option, }, /// Verifies a proof, returning accept or reject Verify { @@ -785,8 +877,8 @@ pub enum Commands { commitment: Option, }, #[cfg(not(target_arch = "wasm32"))] - /// Deploys an evm verifier that is generated by ezkl - DeployEvmVerifier { + /// Deploys an evm contract (verifier, reusable verifier, or vk artifact) that is generated by ezkl + DeployEvm { /// The path to the Solidity code (generated using the create-evm-verifier command) #[arg(long, default_value = DEFAULT_SOL_CODE, value_hint = clap::ValueHint::FilePath)] sol_code_path: Option, @@ -802,25 +894,9 @@ pub enum Commands { /// Private secp256K1 key in hex format, 64 chars, no 0x prefix, of the account signing transactions. If None the private key will be generated by Anvil #[arg(short = 'P', long, value_hint = clap::ValueHint::Other)] private_key: Option, - }, - #[cfg(not(target_arch = "wasm32"))] - /// Deploys an evm verifier that is generated by ezkl - DeployEvmVK { - /// The path to the Solidity code (generated using the create-evm-verifier command) - #[arg(long, default_value = DEFAULT_VK_SOL, value_hint = clap::ValueHint::FilePath)] - sol_code_path: Option, - /// RPC URL for an Ethereum node, if None will use Anvil but WON'T persist state - #[arg(short = 'U', long, value_hint = clap::ValueHint::Url)] - rpc_url: Option, - #[arg(long, default_value = DEFAULT_CONTRACT_ADDRESS_VK, value_hint = clap::ValueHint::Other)] - /// The path to output the contract address - addr_path: Option, - /// The optimizer runs to set on the verifier. Lower values optimize for deployment cost, while higher values optimize for gas cost. - #[arg(long, default_value = DEFAULT_OPTIMIZER_RUNS, value_hint = clap::ValueHint::Other)] - optimizer_runs: usize, - /// Private secp256K1 key in hex format, 64 chars, no 0x prefix, of the account signing transactions. If None the private key will be generated by Anvil - #[arg(short = 'P', long, value_hint = clap::ValueHint::Other)] - private_key: Option, + /// Contract type to be deployed + #[arg(long = "contract-type", short = 'C', default_value = DEFAULT_CONTRACT_DEPLOYMENT_TYPE, value_hint = clap::ValueHint::Other)] + contract: ContractType, }, #[cfg(not(target_arch = "wasm32"))] /// Deploys an evm verifier that allows for data attestation diff --git a/src/execute.rs b/src/execute.rs index 646a62934..e9ed8f7e3 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -195,7 +195,7 @@ pub async fn run(command: Commands) -> Result { settings_path, sol_code_path, abi_path, - render_vk_seperately, + reusable, } => { create_evm_verifier( vk_path.unwrap_or(DEFAULT_VK.into()), @@ -203,7 +203,7 @@ pub async fn run(command: Commands) -> Result { settings_path.unwrap_or(DEFAULT_SETTINGS.into()), sol_code_path.unwrap_or(DEFAULT_SOL_CODE.into()), abi_path.unwrap_or(DEFAULT_VERIFIER_ABI.into()), - render_vk_seperately.unwrap_or(DEFAULT_RENDER_VK_SEPERATELY.parse().unwrap()), + reusable.unwrap_or(DEFAULT_RENDER_REUSABLE.parse().unwrap()), ) .await } @@ -219,14 +219,14 @@ pub async fn run(command: Commands) -> Result { ) .map(|e| serde_json::to_string(&e).unwrap()), - Commands::CreateEvmVK { + Commands::CreateEvmVKArtifact { vk_path, srs_path, settings_path, sol_code_path, abi_path, } => { - create_evm_vk( + create_evm_vka( vk_path.unwrap_or(DEFAULT_VK.into()), srs_path, settings_path.unwrap_or(DEFAULT_SETTINGS.into()), @@ -260,7 +260,7 @@ pub async fn run(command: Commands) -> Result { abi_path, aggregation_settings, logrows, - render_vk_seperately, + reusable, } => { create_evm_aggregate_verifier( vk_path.unwrap_or(DEFAULT_VK.into()), @@ -269,7 +269,7 @@ pub async fn run(command: Commands) -> Result { abi_path.unwrap_or(DEFAULT_VERIFIER_AGGREGATED_ABI.into()), aggregation_settings, logrows.unwrap_or(DEFAULT_AGGREGATED_LOGROWS.parse().unwrap()), - render_vk_seperately.unwrap_or(DEFAULT_RENDER_VK_SEPERATELY.parse().unwrap()), + reusable.unwrap_or(DEFAULT_RENDER_REUSABLE.parse().unwrap()), ) .await } @@ -434,12 +434,13 @@ pub async fn run(command: Commands) -> Result { ) .map(|e| serde_json::to_string(&e).unwrap()), #[cfg(not(target_arch = "wasm32"))] - Commands::DeployEvmVerifier { + Commands::DeployEvm { sol_code_path, rpc_url, addr_path, optimizer_runs, private_key, + contract, } => { deploy_evm( sol_code_path.unwrap_or(DEFAULT_SOL_CODE.into()), @@ -447,25 +448,7 @@ pub async fn run(command: Commands) -> Result { addr_path.unwrap_or(DEFAULT_CONTRACT_ADDRESS.into()), optimizer_runs, private_key, - "Halo2Verifier", - ) - .await - } - #[cfg(not(target_arch = "wasm32"))] - Commands::DeployEvmVK { - sol_code_path, - rpc_url, - addr_path, - optimizer_runs, - private_key, - } => { - deploy_evm( - sol_code_path.unwrap_or(DEFAULT_VK_SOL.into()), - rpc_url, - addr_path.unwrap_or(DEFAULT_CONTRACT_ADDRESS_VK.into()), - optimizer_runs, - private_key, - "Halo2VerifyingArtifact", + contract, ) .await } @@ -1426,7 +1409,7 @@ pub(crate) async fn create_evm_verifier( settings_path: PathBuf, sol_code_path: PathBuf, abi_path: PathBuf, - render_vk_seperately: bool, + reusable: bool, ) -> Result { let settings = GraphSettings::load(&settings_path)?; let commitment: Commitments = settings.run_args.commitment.into(); @@ -1448,16 +1431,16 @@ pub(crate) async fn create_evm_verifier( halo2_solidity_verifier::BatchOpenScheme::Bdfg21, num_instance, ); - let verifier_solidity = if render_vk_seperately { - generator.render_separately()?.0 // ignore the rendered vk for now and generate it in create_evm_vk + let (verifier_solidity, name) = if reusable { + (generator.render_separately()?.0, "Halo2VerifierReusable") // ignore the rendered vk artifact for now and generate it in create_evm_vka } else { - generator.render()? + (generator.render()?, "Halo2Verifier") }; File::create(sol_code_path.clone())?.write_all(verifier_solidity.as_bytes())?; // fetch abi of the contract - let (abi, _, _) = get_contract_artifacts(sol_code_path, "Halo2Verifier", 0).await?; + let (abi, _, _) = get_contract_artifacts(sol_code_path, name, 0).await?; // save abi to file serde_json::to_writer(std::fs::File::create(abi_path)?, &abi)?; @@ -1465,7 +1448,7 @@ pub(crate) async fn create_evm_verifier( } #[cfg(not(target_arch = "wasm32"))] -pub(crate) async fn create_evm_vk( +pub(crate) async fn create_evm_vka( vk_path: PathBuf, srs_path: Option, settings_path: PathBuf, @@ -1616,8 +1599,13 @@ pub(crate) async fn deploy_evm( addr_path: PathBuf, runs: usize, private_key: Option, - contract_name: &str, + contract: ContractType, ) -> Result { + let contract_name = match contract { + ContractType::Verifier { reusable: false } => "Halo2Verifier", + ContractType::Verifier { reusable: true } => "Halo2VerifierReusable", + ContractType::VerifyingKeyArtifact => "Halo2VerifyingArtifact", + }; let contract_address = deploy_contract_via_solidity( sol_code_path, rpc_url.as_deref(), @@ -1708,7 +1696,7 @@ pub(crate) async fn create_evm_aggregate_verifier( abi_path: PathBuf, circuit_settings: Vec, logrows: u32, - render_vk_seperately: bool, + reusable: bool, ) -> Result { let srs_path = get_srs_path(logrows, srs_path, Commitments::KZG); let params: ParamsKZG = load_srs_verifier::>(srs_path)?; @@ -1746,8 +1734,8 @@ pub(crate) async fn create_evm_aggregate_verifier( generator = generator.set_acc_encoding(Some(acc_encoding)); - let verifier_solidity = if render_vk_seperately { - generator.render_separately()?.0 // ignore the rendered vk for now and generate it in create_evm_vk + let verifier_solidity = if reusable { + generator.render_separately()?.0 // ignore the rendered vk artifact for now and generate it in create_evm_vka } else { generator.render()? }; diff --git a/src/python.rs b/src/python.rs index 31c1e4018..0c121e70b 100644 --- a/src/python.rs +++ b/src/python.rs @@ -1490,8 +1490,8 @@ fn encode_evm_calldata<'a>( /// srs_path: str /// The path to the SRS file /// -/// render_vk_separately: bool -/// Whether the verifier key should be rendered as a separate contract. We recommend disabling selector compression if this is enabled. To save the verifier key as a separate contract, set this to true and then call the create_evm_vk command +/// reusable: bool +/// Whether the verifier should be rendered as a reusable contract. If so, then you will need to deploy the VK artifact separately which you can generate using the create_evm_vka command /// /// Returns /// ------- @@ -1503,7 +1503,7 @@ fn encode_evm_calldata<'a>( sol_code_path=PathBuf::from(DEFAULT_SOL_CODE), abi_path=PathBuf::from(DEFAULT_VERIFIER_ABI), srs_path=None, - render_vk_seperately = DEFAULT_RENDER_VK_SEPERATELY.parse().unwrap(), + reusable = DEFAULT_RENDER_REUSABLE.parse().unwrap(), ))] fn create_evm_verifier( py: Python, @@ -1512,7 +1512,7 @@ fn create_evm_verifier( sol_code_path: PathBuf, abi_path: PathBuf, srs_path: Option, - render_vk_seperately: bool, + reusable: bool, ) -> PyResult> { pyo3_asyncio::tokio::future_into_py(py, async move { crate::execute::create_evm_verifier( @@ -1521,7 +1521,7 @@ fn create_evm_verifier( settings_path, sol_code_path, abi_path, - render_vk_seperately, + reusable, ) .await .map_err(|e| { @@ -1533,7 +1533,8 @@ fn create_evm_verifier( }) } -/// Creates an Evm verifer key. This command should be called after create_evm_verifier with the render_vk_separately arg set to true. By rendering a verification key separately you can reuse the same verifier for similar circuit setups with different verifying keys, helping to reduce the amount of state our verifiers store on the blockchain. +/// Creates an Evm VK artifact. This command generated a VK with circuit specific meta data encoding in memory for use by the reusable H2 verifier. +/// This is useful for deploying verifier that were otherwise too big to fit on chain and required aggregation. /// /// Arguments /// --------- @@ -1563,7 +1564,7 @@ fn create_evm_verifier( abi_path=PathBuf::from(DEFAULT_VERIFIER_ABI), srs_path=None ))] -fn create_evm_vk( +fn create_evm_vka( py: Python, vk_path: PathBuf, settings_path: PathBuf, @@ -1572,7 +1573,7 @@ fn create_evm_vk( srs_path: Option, ) -> PyResult> { pyo3_asyncio::tokio::future_into_py(py, async move { - crate::execute::create_evm_vk(vk_path, srs_path, settings_path, sol_code_path, abi_path) + crate::execute::create_evm_vka(vk_path, srs_path, settings_path, sol_code_path, abi_path) .await .map_err(|e| { let err_str = format!("Failed to run create_evm_verifier: {}", e); @@ -1703,6 +1704,7 @@ fn setup_test_evm_witness( addr_path, sol_code_path=PathBuf::from(DEFAULT_SOL_CODE), rpc_url=None, + contract_type=ContractType::default(), optimizer_runs=DEFAULT_OPTIMIZER_RUNS.parse().unwrap(), private_key=None, ))] @@ -1711,6 +1713,7 @@ fn deploy_evm( addr_path: PathBuf, sol_code_path: PathBuf, rpc_url: Option, + contract_type: ContractType, optimizer_runs: usize, private_key: Option, ) -> PyResult> { @@ -1721,42 +1724,7 @@ fn deploy_evm( addr_path, optimizer_runs, private_key, - "Halo2Verifier", - ) - .await - .map_err(|e| { - let err_str = format!("Failed to run deploy_evm: {}", e); - PyRuntimeError::new_err(err_str) - })?; - - Ok(true) - }) -} - -/// deploys the solidity vk verifier -#[pyfunction(signature = ( - addr_path, - sol_code_path=PathBuf::from(DEFAULT_VK_SOL), - rpc_url=None, - optimizer_runs=DEFAULT_OPTIMIZER_RUNS.parse().unwrap(), - private_key=None, -))] -fn deploy_vk_evm( - py: Python, - addr_path: PathBuf, - sol_code_path: PathBuf, - rpc_url: Option, - optimizer_runs: usize, - private_key: Option, -) -> PyResult> { - pyo3_asyncio::tokio::future_into_py(py, async move { - crate::execute::deploy_evm( - sol_code_path, - rpc_url, - addr_path, - optimizer_runs, - private_key, - "Halo2VerifyingArtifact", + contract_type, ) .await .map_err(|e| { @@ -1892,8 +1860,8 @@ fn verify_evm<'a>( /// srs_path: str /// The path to the SRS file /// -/// render_vk_separately: bool -/// Whether the verifier key should be rendered as a separate contract. We recommend disabling selector compression if this is enabled. To save the verifier key as a separate contract, set this to true and then call the create-evm-vk command +/// reusable: bool +/// Whether the verifier should be rendered as a reusable contract. If so, then you will need to deploy the VK artifact separately which you can generate using the create_evm_vka command /// /// Returns /// ------- @@ -1906,7 +1874,7 @@ fn verify_evm<'a>( abi_path=PathBuf::from(DEFAULT_VERIFIER_ABI), logrows=DEFAULT_AGGREGATED_LOGROWS.parse().unwrap(), srs_path=None, - render_vk_seperately = DEFAULT_RENDER_VK_SEPERATELY.parse().unwrap(), + reusable = DEFAULT_RENDER_REUSABLE.parse().unwrap(), ))] fn create_evm_verifier_aggr( py: Python, @@ -1916,7 +1884,7 @@ fn create_evm_verifier_aggr( abi_path: PathBuf, logrows: u32, srs_path: Option, - render_vk_seperately: bool, + reusable: bool, ) -> PyResult> { pyo3_asyncio::tokio::future_into_py(py, async move { crate::execute::create_evm_aggregate_verifier( @@ -1926,7 +1894,7 @@ fn create_evm_verifier_aggr( abi_path, aggregation_settings, logrows, - render_vk_seperately, + reusable, ) .await .map_err(|e| { @@ -1975,9 +1943,8 @@ fn ezkl(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(compile_circuit, m)?)?; m.add_function(wrap_pyfunction!(verify_aggr, m)?)?; m.add_function(wrap_pyfunction!(create_evm_verifier, m)?)?; - m.add_function(wrap_pyfunction!(create_evm_vk, m)?)?; + m.add_function(wrap_pyfunction!(create_evm_vka, m)?)?; m.add_function(wrap_pyfunction!(deploy_evm, m)?)?; - m.add_function(wrap_pyfunction!(deploy_vk_evm, m)?)?; m.add_function(wrap_pyfunction!(deploy_da_evm, m)?)?; m.add_function(wrap_pyfunction!(verify_evm, m)?)?; m.add_function(wrap_pyfunction!(setup_test_evm_witness, m)?)?; diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 168761216..7c00457b7 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -986,57 +986,20 @@ mod native_tests { use crate::native_tests::TESTS_EVM_AGGR; use test_case::test_case; use crate::native_tests::kzg_evm_prove_and_verify; - use crate::native_tests::kzg_evm_prove_and_verify_render_seperately; + use crate::native_tests::kzg_evm_prove_and_verify_reusable_verifier; use crate::native_tests::kzg_evm_on_chain_input_prove_and_verify; use crate::native_tests::kzg_evm_aggr_prove_and_verify; use tempdir::TempDir; use crate::native_tests::Hardfork; use crate::native_tests::run_js_tests; - use std::collections::{HashMap, HashSet}; - use std::fs; - use std::hash::{Hash, Hasher}; use ezkl::logger::init_logger; use crate::native_tests::lazy_static; // Global variables to store verifier hashes and identical verifiers lazy_static! { - static ref VERIFIER_HASHES: std::sync::Mutex>> = std::sync::Mutex::new(HashMap::new()); - static ref IDENTICAL_VERIFIERS: std::sync::Mutex>> = std::sync::Mutex::new(Vec::new()); - } - - fn hash_file_content>(path: P) -> u64 { - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - let content = fs::read_to_string(path).expect("failed to read file"); - content.hash(&mut hasher); - hasher.finish() - } - - fn update_verifier_sets(hash: u64, test_name: String) { - match VERIFIER_HASHES.try_lock() { - Ok(mut verifier_hashes) => { - if let Some(set) = verifier_hashes.get_mut(&hash) { - set.insert(test_name.clone()); - log::error!("Updated set of identical verifiers: {:?}", set); - } else { - let mut new_set: HashSet = HashSet::new(); - new_set.insert(test_name); - verifier_hashes.insert(hash, new_set.clone()); - match IDENTICAL_VERIFIERS.try_lock() { - Ok(mut identical_verifiers) => { - identical_verifiers.push(new_set.clone()); - log::error!("New set of identical verifiers: {:?}", new_set); - } - Err(_) => { - log::error!("Failed to acquire lock on IDENTICAL_VERIFIERS"); - } - } - } - } - Err(_) => { - log::error!("Failed to acquire lock on VERIFIER_HASHES"); - } - } + // create a new variable of type + static ref REUSABLE_VERIFIER_ADDR: std::sync::Mutex> = std::sync::Mutex::new(None); } @@ -1167,27 +1130,25 @@ mod native_tests { } #(#[test_case(TESTS_EVM[N])])* - fn kzg_evm_prove_and_verify_render_seperately_(test: &str) { + fn kzg_evm_prove_and_verify_reusable_verifier_(test: &str) { crate::native_tests::init_binary(); let test_dir = TempDir::new(test).unwrap(); let path = test_dir.path().to_str().unwrap(); crate::native_tests::mv_test_(path, test); let _anvil_child = crate::native_tests::start_anvil(false, Hardfork::Latest); init_logger(); - log::error!("Running kzg_evm_prove_and_verify_render_seperately_ for test: {}", test); - kzg_evm_prove_and_verify_render_seperately(2, path, test.to_string(), "private", "private", "public"); - - // Check for identical verifiers and store the test name - let verifier_path = format!("{}/{}/kzg.sol", path, test); - let hash = hash_file_content(&verifier_path); - - let test_name = test.clone().to_string(); // Clone test name - - update_verifier_sets(hash, test_name); + log::error!("Running kzg_evm_prove_and_verify_reusable_verifier_ for test: {}", test); + let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "private", "private", "public", &mut REUSABLE_VERIFIER_ADDR.lock().unwrap()); + match REUSABLE_VERIFIER_ADDR.try_lock() { + Ok(mut addr) => { + *addr = Some(reusable_verifier_address.clone()); + log::error!("Reusing the same verifeir deployed at address: {}", reusable_verifier_address); + } + Err(_) => { + log::error!("Failed to acquire lock on REUSABLE_VERIFIER_ADDR"); + } + } - #[cfg(not(feature = "icicle"))] - // TODO: Add path for reusable verifier in in browser evm verifier - // run_js_tests(path, test.to_string(), "testBrowserEvmVerify", true); test_dir.close().unwrap(); } @@ -1942,7 +1903,7 @@ mod native_tests { // deploy the verifier let args = vec![ - "deploy-evm-verifier", + "deploy-evm", rpc_arg.as_str(), addr_path_arg.as_str(), "--sol-code-path", @@ -2173,11 +2134,7 @@ mod native_tests { assert!(status.success()); // deploy the verifier - let mut args = vec![ - "deploy-evm-verifier", - rpc_arg.as_str(), - addr_path_arg.as_str(), - ]; + let mut args = vec!["deploy-evm", rpc_arg.as_str(), addr_path_arg.as_str()]; args.push("--sol-code-path"); args.push(sol_arg.as_str()); @@ -2219,14 +2176,15 @@ mod native_tests { } // prove-serialize-verify, the usual full path - fn kzg_evm_prove_and_verify_render_seperately( + fn kzg_evm_prove_and_verify_reusable_verifier( num_inner_columns: usize, test_dir: &str, example_name: String, input_visibility: &str, param_visibility: &str, output_visibility: &str, - ) { + reusable_verifier_address: &mut Option, + ) -> String { let anvil_url = ANVIL_URL.as_str(); prove_and_verify( @@ -2253,27 +2211,58 @@ mod native_tests { let settings_arg = format!("--settings-path={}", settings_path); let sol_arg = format!("--sol-code-path={}/{}/kzg.sol", test_dir, example_name); - // create the verifier - let args = vec![ - "create-evm-verifier", - "--vk-path", - &vk_arg, - &settings_arg, - &sol_arg, - "--render-vk-seperately", - ]; - - let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) - .args(&args) - .status() - .expect("failed to execute process"); - assert!(status.success()); + // if the reusable verifier address is not set, create the verifier + let deployed_addr_arg = match reusable_verifier_address { + Some(addr) => addr.clone(), + None => { + // create the reusable verifier + let args = vec![ + "create-evm-verifier", + "--vk-path", + &vk_arg, + &settings_arg, + &sol_arg, + "--reusable", + ]; + + let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) + .args(&args) + .status() + .expect("failed to execute process"); + assert!(status.success()); + + // deploy the verifier + let args = vec![ + "deploy-evm", + rpc_arg.as_str(), + addr_path_arg.as_str(), + sol_arg.as_str(), + "-C=verifier/reusable", + ]; + + let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) + .args(&args) + .status() + .expect("failed to execute process"); + assert!(status.success()); + + // read in the address + let addr = + std::fs::read_to_string(format!("{}/{}/addr.txt", test_dir, example_name)) + .expect("failed to read address file"); + + let deployed_addr_arg = format!("--addr-verifier={}", addr); + // set the reusable verifier address + *reusable_verifier_address = Some(addr); + deployed_addr_arg + } + }; let addr_path_arg_vk = format!("--addr-path={}/{}/addr_vk.txt", test_dir, example_name); - let sol_arg_vk = format!("--sol-code-path={}/{}/vk.sol", test_dir, example_name); + let sol_arg_vk: String = format!("--sol-code-path={}/{}/vk.sol", test_dir, example_name); // create the verifier let args = vec![ - "create-evm-vk", + "create-evm-vka", "--vk-path", &vk_arg, &settings_arg, @@ -2286,32 +2275,13 @@ mod native_tests { .expect("failed to execute process"); assert!(status.success()); - // deploy the verifier - let args = vec![ - "deploy-evm-verifier", - rpc_arg.as_str(), - addr_path_arg.as_str(), - 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()); - - // read in the address - let addr = std::fs::read_to_string(format!("{}/{}/addr.txt", test_dir, example_name)) - .expect("failed to read address file"); - - let deployed_addr_arg = format!("--addr-verifier={}", addr); - - // deploy the vk + // deploy the vka let args = vec![ - "deploy-evm-vk", + "deploy-evm", rpc_arg.as_str(), addr_path_arg_vk.as_str(), sol_arg_vk.as_str(), + "-C=vka", ]; let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) @@ -2362,6 +2332,8 @@ mod native_tests { .status() .expect("failed to execute process"); assert!(!status.success()); + // Returned deploy_addr_arg for reusable verifier + deployed_addr_arg } // run js browser evm verify tests for a given example @@ -2563,7 +2535,7 @@ mod native_tests { // deploy the verifier let mut args = vec![ - "deploy-evm-verifier", + "deploy-evm", rpc_arg.as_str(), addr_path_verifier_arg.as_str(), ]; diff --git a/tests/python/binding_tests.py b/tests/python/binding_tests.py index b2758917a..b2a3e10be 100644 --- a/tests/python/binding_tests.py +++ b/tests/python/binding_tests.py @@ -450,10 +450,10 @@ async def test_create_evm_verifier_separate_vk(): sol_code_path, abi_path, srs_path=srs_path, - render_vk_seperately=True + reusable=True ) - res = await ezkl.create_evm_vk( + res = await ezkl.create_evm_vka( vk_path, settings_path, vk_code_path, @@ -465,9 +465,9 @@ async def test_create_evm_verifier_separate_vk(): assert os.path.isfile(sol_code_path) -async def test_deploy_evm_separate_vk(): +async def test_deploy_evm_reusable_and_vka(): """ - Test deployment of the separate verifier smart contract + vk + Test deployment of the reusable verifier smart contract + vka In order to run this you will need to install solc in your environment """ addr_path_verifier = os.path.join(folder_path, 'address_separate.json') @@ -481,13 +481,15 @@ async def test_deploy_evm_separate_vk(): res = await ezkl.deploy_evm( addr_path_verifier, sol_code_path, - rpc_url=anvil_url, + anvil_url, + "verifier/reusable", ) - res = await ezkl.deploy_vk_evm( + res = await ezkl.deploy_evm( addr_path_vk, vk_code_path, - rpc_url=anvil_url, + anvil_url, + "vka", ) assert res == True @@ -506,7 +508,7 @@ async def test_deploy_evm(): res = await ezkl.deploy_evm( addr_path, sol_code_path, - rpc_url=anvil_url, + anvil_url, ) assert res == True From 9908f5c214ac3cab0fa9bc8f248657649d5d4f05 Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 4 Sep 2024 21:45:29 -0500 Subject: [PATCH 37/46] *add col overflow testing for reusable verifier. --- .github/workflows/rust.yml | 4 +++- Cargo.lock | 2 +- tests/integration_tests.rs | 29 +++++++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b643a0c64..cec19698c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -335,7 +335,9 @@ jobs: - name: Install Anvil run: cargo install --git https://github.com/foundry-rs/foundry --rev 62cdea8ff9e6efef011f77e295823b5f2dbeb3a1 --locked anvil --force - name: KZG prove and verify tests (EVM + reusable verifier) - run: cargo nextest run --release --verbose tests_evm::kzg_evm_prove_and_verify_reusable_verifier_ --test-threads 1 + run: cargo nextest run --release --verbose tests_evm::kzg_evm_prove_and_verify_reusable_verifier --test-threads 1 + - name: KZG prove and verify tests (EVM + reusable verifier + col-overflow) + run: cargo nextest run --release --verbose tests_evm::kzg_evm_prove_and_verify_reusable_verifier_with_overflow --test-threads 1 - name: KZG prove and verify tests (EVM + kzg all) run: cargo nextest run --release --verbose tests_evm::kzg_evm_kzg_all_prove_and_verify --test-threads 1 - name: KZG prove and verify tests (EVM + kzg inputs) diff --git a/Cargo.lock b/Cargo.lock index dcf86bba1..bbf92dc0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2341,7 +2341,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#b544e4b1ca3a0456fd3483dca6b1cd559e6780af" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#d600b141bdd241f6af07970c119bfbdf3418c2ef" dependencies = [ "askama", "blake2b_simd", diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 7c00457b7..295b3aa18 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1137,7 +1137,31 @@ mod native_tests { let _anvil_child = crate::native_tests::start_anvil(false, Hardfork::Latest); init_logger(); log::error!("Running kzg_evm_prove_and_verify_reusable_verifier_ for test: {}", test); - let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "private", "private", "public", &mut REUSABLE_VERIFIER_ADDR.lock().unwrap()); + let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "private", "private", "public", &mut REUSABLE_VERIFIER_ADDR.lock().unwrap(), false); + + match REUSABLE_VERIFIER_ADDR.try_lock() { + Ok(mut addr) => { + *addr = Some(reusable_verifier_address.clone()); + log::error!("Reusing the same verifeir deployed at address: {}", reusable_verifier_address); + } + Err(_) => { + log::error!("Failed to acquire lock on REUSABLE_VERIFIER_ADDR"); + } + } + + test_dir.close().unwrap(); + + } + + #(#[test_case(TESTS_EVM[N])])* + fn kzg_evm_prove_and_verify_reusable_verifier_with_overflow_(test: &str) { + crate::native_tests::init_binary(); + let test_dir = TempDir::new(test).unwrap(); + let path = test_dir.path().to_str().unwrap(); crate::native_tests::mv_test_(path, test); + let _anvil_child = crate::native_tests::start_anvil(false, Hardfork::Latest); + init_logger(); + log::error!("Running kzg_evm_prove_and_verify_reusable_verifier_with_overflow_ for test: {}", test); + let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "private", "private", "public", &mut REUSABLE_VERIFIER_ADDR.lock().unwrap(), true); match REUSABLE_VERIFIER_ADDR.try_lock() { Ok(mut addr) => { @@ -2184,6 +2208,7 @@ mod native_tests { param_visibility: &str, output_visibility: &str, reusable_verifier_address: &mut Option, + overflow: bool, ) -> String { let anvil_url = ANVIL_URL.as_str(); @@ -2196,7 +2221,7 @@ mod native_tests { output_visibility, num_inner_columns, None, - false, + overflow, "single", Commitments::KZG, 2, From c9351c0607f07a6093d2409903c24a81d2d8ad54 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 5 Sep 2024 20:10:16 -0500 Subject: [PATCH 38/46] *reduce `extcodeopy` call by 1 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index bbf92dc0a..94ec3d094 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2341,7 +2341,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#d600b141bdd241f6af07970c119bfbdf3418c2ef" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#d1464f901046ad0641c8a0b72e1b8bcedc406d0b" dependencies = [ "askama", "blake2b_simd", From 9fd3799ed1c293421a0fa63d4c6cc5a673a34b7b Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 7 Sep 2024 17:46:32 -0500 Subject: [PATCH 39/46] *expand reusable verifier test examples --- tests/integration_tests.rs | 43 ++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 295b3aa18..e00b605aa 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -241,8 +241,8 @@ mod native_tests { "1l_conv_transpose", "1l_upsample", "1l_identity", //35 - "idolmodel", - "trig", + "idolmodel", // too big evm + "trig", // too big evm "prelu_gmm", "lstm", "rnn", //40 @@ -983,6 +983,7 @@ mod native_tests { mod tests_evm { use seq_macro::seq; use crate::native_tests::TESTS_EVM; + use crate::native_tests::TESTS; use crate::native_tests::TESTS_EVM_AGGR; use test_case::test_case; use crate::native_tests::kzg_evm_prove_and_verify; @@ -1113,23 +1114,8 @@ mod native_tests { }); - - seq!(N in 0..=22 { - - #(#[test_case(TESTS_EVM[N])])* - fn kzg_evm_prove_and_verify_(test: &str) { - crate::native_tests::init_binary(); - let test_dir = TempDir::new(test).unwrap(); - let path = test_dir.path().to_str().unwrap(); crate::native_tests::mv_test_(path, test); - let _anvil_child = crate::native_tests::start_anvil(false, Hardfork::Latest); - kzg_evm_prove_and_verify(2, path, test.to_string(), "private", "private", "public"); - #[cfg(not(feature = "icicle"))] - run_js_tests(path, test.to_string(), "testBrowserEvmVerify", false); - test_dir.close().unwrap(); - - } - - #(#[test_case(TESTS_EVM[N])])* + seq!(N in 0..=93 { + #(#[test_case(TESTS[N])])* fn kzg_evm_prove_and_verify_reusable_verifier_(test: &str) { crate::native_tests::init_binary(); let test_dir = TempDir::new(test).unwrap(); @@ -1153,7 +1139,7 @@ mod native_tests { } - #(#[test_case(TESTS_EVM[N])])* + #(#[test_case(TESTS[N])])* fn kzg_evm_prove_and_verify_reusable_verifier_with_overflow_(test: &str) { crate::native_tests::init_binary(); let test_dir = TempDir::new(test).unwrap(); @@ -1176,6 +1162,23 @@ mod native_tests { test_dir.close().unwrap(); } + }); + + + seq!(N in 0..=22 { + + #(#[test_case(TESTS[N])])* + fn kzg_evm_prove_and_verify_(test: &str) { + crate::native_tests::init_binary(); + let test_dir = TempDir::new(test).unwrap(); + let path = test_dir.path().to_str().unwrap(); crate::native_tests::mv_test_(path, test); + let _anvil_child = crate::native_tests::start_anvil(false, Hardfork::Latest); + kzg_evm_prove_and_verify(2, path, test.to_string(), "private", "private", "public"); + #[cfg(not(feature = "icicle"))] + run_js_tests(path, test.to_string(), "testBrowserEvmVerify", false); + test_dir.close().unwrap(); + + } #(#[test_case(TESTS_EVM[N])])* From 6788a9c7268fdebbfdaca336199a696cabed79f6 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 9 Sep 2024 17:00:42 -0500 Subject: [PATCH 40/46] *comprehensive test coverage for reusable verifier --- Cargo.lock | 2 +- tests/integration_tests.rs | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 94ec3d094..5b40204fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2341,7 +2341,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#d1464f901046ad0641c8a0b72e1b8bcedc406d0b" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#d9d4b080c9f3349370aa5225e452f6517492dea5" dependencies = [ "askama", "blake2b_simd", diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index e00b605aa..46cdefefd 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1120,10 +1120,15 @@ mod native_tests { crate::native_tests::init_binary(); let test_dir = TempDir::new(test).unwrap(); let path = test_dir.path().to_str().unwrap(); crate::native_tests::mv_test_(path, test); - let _anvil_child = crate::native_tests::start_anvil(false, Hardfork::Latest); + let _anvil_child = crate::native_tests::start_anvil(true, Hardfork::Latest); init_logger(); log::error!("Running kzg_evm_prove_and_verify_reusable_verifier_ for test: {}", test); + // default vis let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "private", "private", "public", &mut REUSABLE_VERIFIER_ADDR.lock().unwrap(), false); + // public/public vis + let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "public", "private", "public", &mut Some(reusable_verifier_address), false); + // hashed input + let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "hashed", "private", "public", &mut Some(reusable_verifier_address), false); match REUSABLE_VERIFIER_ADDR.try_lock() { Ok(mut addr) => { @@ -1141,13 +1146,22 @@ mod native_tests { #(#[test_case(TESTS[N])])* fn kzg_evm_prove_and_verify_reusable_verifier_with_overflow_(test: &str) { + // verifier too big to fit on chain + if test == "1l_eltwise_div" || test == "lenet_5" || test == "ltsf" { + return; + } crate::native_tests::init_binary(); let test_dir = TempDir::new(test).unwrap(); let path = test_dir.path().to_str().unwrap(); crate::native_tests::mv_test_(path, test); let _anvil_child = crate::native_tests::start_anvil(false, Hardfork::Latest); init_logger(); log::error!("Running kzg_evm_prove_and_verify_reusable_verifier_with_overflow_ for test: {}", test); + // default vis let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "private", "private", "public", &mut REUSABLE_VERIFIER_ADDR.lock().unwrap(), true); + // public/public vis + let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "public", "private", "public", &mut Some(reusable_verifier_address), true); + // hashed input + let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "hashed", "private", "public", &mut Some(reusable_verifier_address), true); match REUSABLE_VERIFIER_ADDR.try_lock() { Ok(mut addr) => { @@ -1167,7 +1181,7 @@ mod native_tests { seq!(N in 0..=22 { - #(#[test_case(TESTS[N])])* + #(#[test_case(TESTS_EVM[N])])* fn kzg_evm_prove_and_verify_(test: &str) { crate::native_tests::init_binary(); let test_dir = TempDir::new(test).unwrap(); From d4ebf8f1202aae8feb97c97c5071c556b47f5a75 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 10 Sep 2024 13:48:36 -0500 Subject: [PATCH 41/46] skip lstm_large in overflow b/c too big to fit on chain. --- tests/integration_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 46cdefefd..99b281f19 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1146,8 +1146,8 @@ mod native_tests { #(#[test_case(TESTS[N])])* fn kzg_evm_prove_and_verify_reusable_verifier_with_overflow_(test: &str) { - // verifier too big to fit on chain - if test == "1l_eltwise_div" || test == "lenet_5" || test == "ltsf" { + // verifier too big to fit on chain with overflow calibration target + if test == "1l_eltwise_div" || test == "lenet_5" || test == "ltsf" || test == "lstm_large" { return; } crate::native_tests::init_binary(); From e3051b79cdad3fdb4223284159ae22d8caef3e73 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 4 Oct 2024 17:24:57 +0800 Subject: [PATCH 42/46] *bit flip fuzzing tests --- .github/workflows/rust.yml | 4 +-- tests/integration_tests.rs | 59 ++++++++++++++++++++++++++++++++------ 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index af9ade04f..f97f7fd65 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -333,10 +333,8 @@ jobs: # run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.20 && solc --version - name: Install Anvil run: cargo install --git https://github.com/foundry-rs/foundry --rev 62cdea8ff9e6efef011f77e295823b5f2dbeb3a1 --locked anvil --force - - name: KZG prove and verify tests (EVM + reusable verifier) - run: cargo nextest run --release --verbose tests_evm::kzg_evm_prove_and_verify_reusable_verifier --test-threads 1 - name: KZG prove and verify tests (EVM + reusable verifier + col-overflow) - run: cargo nextest run --release --verbose tests_evm::kzg_evm_prove_and_verify_reusable_verifier_with_overflow --test-threads 1 + run: cargo nextest run --release --verbose tests_evm::kzg_evm_prove_and_verify_reusable_verifier --test-threads 1 - name: KZG prove and verify tests (EVM + kzg all) run: cargo nextest run --release --verbose tests_evm::kzg_evm_kzg_all_prove_and_verify --test-threads 1 - name: KZG prove and verify tests (EVM + kzg inputs) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 99b281f19..30eb731ac 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -7,11 +7,15 @@ mod native_tests { // use ezkl::circuit::table::RESERVED_BLINDING_ROWS_PAD; use ezkl::graph::input::{FileSource, FileSourceInner, GraphData}; use ezkl::graph::{DataSource, GraphSettings, GraphWitness}; + use ezkl::pfsys::Snark; use ezkl::Commitments; + use halo2_proofs::poly::kzg::commitment::KZGCommitmentScheme; + use halo2curves::bn256::Bn256; use lazy_static::lazy_static; use rand::Rng; use std::env::var; use std::io::{Read, Write}; + use std::path::PathBuf; use std::process::{Child, Command}; use std::sync::Once; static COMPILE: Once = Once::new(); @@ -1120,7 +1124,7 @@ mod native_tests { crate::native_tests::init_binary(); let test_dir = TempDir::new(test).unwrap(); let path = test_dir.path().to_str().unwrap(); crate::native_tests::mv_test_(path, test); - let _anvil_child = crate::native_tests::start_anvil(true, Hardfork::Latest); + let _anvil_child = crate::native_tests::start_anvil(false, Hardfork::Latest); init_logger(); log::error!("Running kzg_evm_prove_and_verify_reusable_verifier_ for test: {}", test); // default vis @@ -2353,7 +2357,7 @@ mod native_tests { // now verify the proof let pf_arg = format!("{}/{}/proof.pf", test_dir, example_name); - let mut args = vec![ + let args = vec![ "verify-evm", "--proof-path", pf_arg.as_str(), @@ -2367,13 +2371,50 @@ mod native_tests { .status() .expect("failed to execute process"); assert!(status.success()); - // As sanity check, add example that should fail. - args[2] = PF_FAILURE; - let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) - .args(args) - .status() - .expect("failed to execute process"); - assert!(!status.success()); + // Read the original proof file + let original_proof_data: ezkl::pfsys::Snark< + halo2curves::bn256::Fr, + halo2curves::bn256::G1Affine, + > = Snark::load::>(&PathBuf::from(format!( + "{}/{}/proof.pf", + test_dir, example_name + ))) + .expect("Failed to read proof file"); + + for i in 0..1 { + // Create a copy of the original proof data + let mut modified_proof_data = original_proof_data.clone(); + + // Flip a random bit + let random_byte = rand::thread_rng().gen_range(0..modified_proof_data.proof.len()); + let random_bit = rand::thread_rng().gen_range(0..8); + modified_proof_data.proof[random_byte] ^= 1 << random_bit; + + // Write the modified proof to a new file + let modified_pf_arg = format!("{}/{}/modified_proof_{}.pf", test_dir, example_name, i); + modified_proof_data + .save(&PathBuf::from(modified_pf_arg.clone())) + .expect("Failed to save modified proof file"); + + // Verify the modified proof (should fail) + let mut args_mod = args.clone(); + args_mod[2] = &modified_pf_arg; + let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) + .args(&args_mod) + .status() + .expect("failed to execute process"); + + if status.success() { + log::error!("Verification unexpectedly succeeded for modified proof {}. Flipped bit {} in byte {}", i, random_bit, random_byte); + } + + assert!( + !status.success(), + "Modified proof {} should have failed verification", + i + ); + } + // Returned deploy_addr_arg for reusable verifier deployed_addr_arg } From 97c1397fe6b16b235d03417776c5480c45a62fe5 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 8 Oct 2024 20:47:58 +0800 Subject: [PATCH 43/46] verifier manager contract. --- Cargo.lock | 2 +- abis/EZKLVerifierManager.json | 264 ++++++++++++++++++++++++++++++++++ src/commands.rs | 7 + src/eth.rs | 76 +++++++++- src/execute.rs | 46 ++++-- 5 files changed, 382 insertions(+), 13 deletions(-) create mode 100644 abis/EZKLVerifierManager.json diff --git a/Cargo.lock b/Cargo.lock index 5b40204fb..5b9e8203c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2341,7 +2341,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#d9d4b080c9f3349370aa5225e452f6517492dea5" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#fa1c577c135dc51ada2d048fabe7663908a7d953" dependencies = [ "askama", "blake2b_simd", diff --git a/abis/EZKLVerifierManager.json b/abis/EZKLVerifierManager.json new file mode 100644 index 000000000..71dfa8a04 --- /dev/null +++ b/abis/EZKLVerifierManager.json @@ -0,0 +1,264 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "verifier", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "vka", + "type": "address" + } + ], + "name": "DeployedVKArtifact", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + } + ], + "name": "DeployedVerifier", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "bytecode", + "type": "bytes" + } + ], + "name": "deployVerifier", + "outputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "runtimeCodeHash", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "bytecode", + "type": "bytes" + }, + { + "internalType": "address", + "name": "verifier", + "type": "address" + } + ], + "name": "deployVkArtifact", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "bytecode", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "_salt", + "type": "uint256" + } + ], + "name": "precomputeAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "verifiers", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vkArtifact", + "type": "address" + }, + { + "internalType": "bytes", + "name": "proof", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "instances", + "type": "uint256[]" + } + ], + "name": "verifyProof", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "vkArtifactToVerifier", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/src/commands.rs b/src/commands.rs index baba9b874..6c21c71a8 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -97,6 +97,9 @@ pub const DEFAULT_USE_REDUCED_SRS_FOR_VERIFICATION: &str = "false"; pub const DEFAULT_ONLY_RANGE_CHECK_REBASE: &str = "false"; /// Default commitment pub const DEFAULT_COMMITMENT: &str = "kzg"; +// TODO: In prod this will be the same across all chains we deploy to using the EZKL multisig create2 deployment. +/// Default address of the verifier manager. +pub const DEFAULT_VERIFIER_MANAGER_ADDRESS: &str = "contract_verifier_manager.address"; #[cfg(feature = "python-bindings")] /// Converts TranscriptType into a PyObject (Required for TranscriptType to be compatible with Python) @@ -894,6 +897,10 @@ pub enum Commands { /// Private secp256K1 key in hex format, 64 chars, no 0x prefix, of the account signing transactions. If None the private key will be generated by Anvil #[arg(short = 'P', long, value_hint = clap::ValueHint::Other)] private_key: Option, + /// Deployed verifier manager contract's address + /// Use to facilitate reusable verifier and vk artifact deployment + #[arg(long, default_value = DEFAULT_CONTRACT_ADDRESS, value_hint = clap::ValueHint::Other)] + verifier_manager: Option, /// Contract type to be deployed #[arg(long = "contract-type", short = 'C', default_value = DEFAULT_CONTRACT_DEPLOYMENT_TYPE, value_hint = clap::ValueHint::Other)] contract: ContractType, diff --git a/src/eth.rs b/src/eth.rs index 0b01b4547..1ba955305 100644 --- a/src/eth.rs +++ b/src/eth.rs @@ -17,7 +17,7 @@ use alloy::prelude::Wallet; use alloy::json_abi::JsonAbi; use alloy::node_bindings::Anvil; use alloy::primitives::ruint::ParseError; -use alloy::primitives::{ParseSignedError, B256, I256}; +use alloy::primitives::{keccak256, ParseSignedError, B256, I256}; use alloy::providers::fillers::{ ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, SignerFiller, }; @@ -216,6 +216,16 @@ abigen!( } ); +// The bytecode here was generated from running solc compiler version 0.8.20 with optimization enabled and runs param set to 200. +abigen!( + #[allow(missing_docs)] + #[sol( + rpc, + bytecode = "608060405234801561000f575f80fd5b5060405161093138038061093183398101604081905261002e91610130565b806001600160a01b03811661005d57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b61006681610076565b50610070816100c5565b5061015d565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6100cd610102565b6001600160a01b0381166100f657604051631e4fbdf760e01b81525f6004820152602401610054565b6100ff81610076565b50565b5f546001600160a01b0316331461012e5760405163118cdaa760e01b8152336004820152602401610054565b565b5f60208284031215610140575f80fd5b81516001600160a01b0381168114610156575f80fd5b9392505050565b6107c78061016a5f395ff3fe608060405234801561000f575f80fd5b5060043610610090575f3560e01c80638da5cb5b116100635780638da5cb5b14610128578063af83a18d14610138578063ebce97301461015b578063efe1c950146101c5578063f2fde38b146101ed575f80fd5b806326fbc2b0146100945780635d34fd56146100d9578063715018a61461010b5780637224b7e114610115575b5f80fd5b6100bc6100a2366004610494565b60026020525f90815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100ec6100e7366004610551565b610200565b604080516001600160a01b0390931683526020830191909152016100d0565b61011361028a565b005b6100bc61012336600461058b565b61029d565b5f546001600160a01b03166100bc565b61014b6101463660046105d6565b610329565b60405190151581526020016100d0565b6100bc6101693660046106a6565b8151602092830120604080516001600160f81b0319818601523060601b6bffffffffffffffffffffffff191660218201526035810193909352605580840192909252805180840390920182526075909201909152805191012090565b6100bc6101d33660046106e8565b60016020525f90815260409020546001600160a01b031681565b6101136101fb366004610494565b6103bc565b5f8061020a6103fe565b5f8351602085015ff59150813b61021f575f80fd5b50803f5f8181526001602090815260409182902080546001600160a01b0319166001600160a01b03861690811790915582519081529081018390527fc76faa42f66c1ea9e596e467b87dec63ad41cb0e6217b2e4206fc43d8464ca6a910160405180910390a1915091565b6102926103fe565b61029b5f61042a565b565b5f6102a66103fe565b5f808451602086015ff59050803b6102bc575f80fd5b6001600160a01b038181165f8181526002602090815260409182902080546001600160a01b031916948816948517905581519384528301919091527fea544395368ef77bf2480023a11e12abf361a2f69e6efc513fb281a5a10e663e910160405180910390a19392505050565b6001600160a01b038086165f9081526002602052604080822054905163af83a18d60e01b815291921690819063af83a18d906103719083908a908a908a908a906004016106ff565b6020604051808303815f875af115801561038d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103b19190610772565b979650505050505050565b6103c46103fe565b6001600160a01b0381166103f257604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6103fb8161042a565b50565b5f546001600160a01b0316331461029b5760405163118cdaa760e01b81523360048201526024016103e9565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80356001600160a01b038116811461048f575f80fd5b919050565b5f602082840312156104a4575f80fd5b6104ad82610479565b9392505050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f8301126104d7575f80fd5b813567ffffffffffffffff808211156104f2576104f26104b4565b604051601f8301601f19908116603f0116810190828211818310171561051a5761051a6104b4565b81604052838152866020858801011115610532575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f60208284031215610561575f80fd5b813567ffffffffffffffff811115610577575f80fd5b610583848285016104c8565b949350505050565b5f806040838503121561059c575f80fd5b823567ffffffffffffffff8111156105b2575f80fd5b6105be858286016104c8565b9250506105cd60208401610479565b90509250929050565b5f805f805f606086880312156105ea575f80fd5b6105f386610479565b9450602086013567ffffffffffffffff8082111561060f575f80fd5b818801915088601f830112610622575f80fd5b813581811115610630575f80fd5b896020828501011115610641575f80fd5b60208301965080955050604088013591508082111561065e575f80fd5b818801915088601f830112610671575f80fd5b81358181111561067f575f80fd5b8960208260051b8501011115610693575f80fd5b9699959850939650602001949392505050565b5f80604083850312156106b7575f80fd5b823567ffffffffffffffff8111156106cd575f80fd5b6106d9858286016104c8565b95602094909401359450505050565b5f602082840312156106f8575f80fd5b5035919050565b6001600160a01b03861681526060602082018190528101849052838560808301375f60808583018101829052601f19601f8701168301838103820160408501529081018490526001600160fb1b03841115610758575f80fd5b8360051b808660a08401370160a001979650505050505050565b5f60208284031215610782575f80fd5b815180151581146104ad575f80fdfea264697066735822122020b829266d031cbdab7a497c6c1b55609ea4792c7aba586af26a55eb3e7cf29064736f6c63430008140033" + )] + EZKLVerifierManager, + "./abis/EZKLVerifierManager.json" +); #[derive(Debug, thiserror::Error)] pub enum EthError { #[error("a transport error occurred: {0}")] @@ -356,6 +366,70 @@ pub async fn deploy_contract_via_solidity( Ok(contract) } +pub async fn deploy_via_verifier_manager( + sol_code_path: PathBuf, + rpc_url: Option<&str>, + runs: usize, + private_key: Option<&str>, + contract_name: &str, + verifier_manager: H160, +) -> Result { + let (client, _) = setup_eth_backend(rpc_url, private_key).await?; + + // Create an instance of the EZKLVerifierManager contract + let verifier_manager_contract = EZKLVerifierManager::new(verifier_manager, client.clone()); + + // Get the bytecode of the contract to be deployed + let (_, bytecode, _) = + get_contract_artifacts(sol_code_path.clone(), contract_name, runs).await?; + + if contract_name == "Halo2VerifierReusable" { + // Deploy the contract using the EZKLVerifierManager + let output = verifier_manager_contract + .deployVerifier(bytecode.clone().into()) + .call() + .await?; + // Get the deployed contract address from the receipt + let contract = output.addr; + let _ = verifier_manager_contract + .deployVerifier(bytecode.into()) + .send() + .await?; + return Ok(contract); + } else { + let (_, bytecode, runtime_bytecode) = + get_contract_artifacts(sol_code_path, "Halo2VerifierReusable", runs).await?; + + // Hash the runtime bytecode of the reusable verifier + let reusable_verifier_hash = keccak256(&runtime_bytecode); + + // Check if the reusable verifier is already deployed + let deployed_verifier = verifier_manager_contract + .verifiers(reusable_verifier_hash) + .call() + .await? + ._0; + + if deployed_verifier == H160::new([0_u8; 20]) { + panic!("The reusable verifier for this VKA has not been deployed yet."); + } + // Statically call the deployVKArtifact to get the receipt back. + let receipt = verifier_manager_contract + .deployVkArtifact(bytecode.clone().into(), deployed_verifier) + .call() + .await?; + + // Get the deployed VkArtifact address from the receipt + let contract = receipt._0; + // Deploy the VkArtifact using the EZKLVerifierManager + let _ = verifier_manager_contract + .deployVkArtifact(bytecode.into(), deployed_verifier) + .send() + .await?; + return Ok(contract); + } +} + /// pub async fn deploy_da_verifier_via_solidity( settings_path: PathBuf, diff --git a/src/execute.rs b/src/execute.rs index 97ccb4455..a47e57bd0 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -3,7 +3,9 @@ use crate::circuit::CheckMode; #[cfg(not(target_arch = "wasm32"))] use crate::commands::CalibrationTarget; #[cfg(not(target_arch = "wasm32"))] -use crate::eth::{deploy_contract_via_solidity, deploy_da_verifier_via_solidity}; +use crate::eth::{ + deploy_contract_via_solidity, deploy_da_verifier_via_solidity, deploy_via_verifier_manager, +}; #[cfg(not(target_arch = "wasm32"))] #[allow(unused_imports)] use crate::eth::{fix_da_sol, get_contract_artifacts, verify_proof_via_solidity}; @@ -440,6 +442,7 @@ pub async fn run(command: Commands) -> Result { addr_path, optimizer_runs, private_key, + verifier_manager, contract, } => { deploy_evm( @@ -448,6 +451,7 @@ pub async fn run(command: Commands) -> Result { addr_path.unwrap_or(DEFAULT_CONTRACT_ADDRESS.into()), optimizer_runs, private_key, + verifier_manager.unwrap_or(DEFAULT_VERIFIER_MANAGER_ADDRESS.into()), contract, ) .await @@ -1482,9 +1486,13 @@ pub(crate) async fn create_evm_vka( num_instance, ); - let vk_solidity = generator.render_separately()?.1; + let (reusable_verifier, vk_solidity) = generator.render_separately()?; - File::create(sol_code_path.clone())?.write_all(vk_solidity.as_bytes())?; + // We store each contracts to the same file... + // We need to do this so that during the deployment transaction we make sure + // verifier manager links the VKA to the correct reusable_verifier. + let combined_solidity = format!("{}\n\n{}", reusable_verifier, vk_solidity); + File::create(sol_code_path.clone())?.write_all(combined_solidity.as_bytes())?; // fetch abi of the contract let (abi, _, _) = get_contract_artifacts(sol_code_path, "Halo2VerifyingArtifact", 0).await?; @@ -1605,6 +1613,7 @@ pub(crate) async fn deploy_evm( addr_path: PathBuf, runs: usize, private_key: Option, + verifier_manager: H160Flag, contract: ContractType, ) -> Result { let contract_name = match contract { @@ -1612,14 +1621,29 @@ pub(crate) async fn deploy_evm( ContractType::Verifier { reusable: true } => "Halo2VerifierReusable", ContractType::VerifyingKeyArtifact => "Halo2VerifyingArtifact", }; - let contract_address = deploy_contract_via_solidity( - sol_code_path, - rpc_url.as_deref(), - runs, - private_key.as_deref(), - contract_name, - ) - .await?; + + let contract_address = + if contract_name == "Halo2VerifierReusable" || contract_name == "Halo2VerifyingArtifact" { + // Use VerifierManager to deploy the contract + deploy_via_verifier_manager( + sol_code_path, + rpc_url.as_deref(), + runs, + private_key.as_deref(), + contract_name, + verifier_manager.into(), + ) + .await? + } else { + deploy_contract_via_solidity( + sol_code_path, + rpc_url.as_deref(), + runs, + private_key.as_deref(), + contract_name, + ) + .await? + }; info!("Contract deployed at: {:#?}", contract_address); From 1ddbce537fab428987367d7f1ba19da96a580151 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 25 Oct 2024 22:31:38 +0800 Subject: [PATCH 44/46] ci testing --- .gitignore | 1 + Cargo.lock | 2 +- Cargo.toml | 2 +- abis/EZKLVerifierManager.json | 120 +--------------------- contracts/VerifierManager.sol | 184 ++++++++++++++++++++++++++++++++++ src/commands.rs | 16 ++- src/eth.rs | 123 ++++++++++++++--------- src/execute.rs | 93 +++++++++++------ tests/integration_tests.rs | 145 +++++++++++++++++---------- verifier_abi.json | 2 +- vk.abi | 2 +- 11 files changed, 434 insertions(+), 256 deletions(-) create mode 100644 contracts/VerifierManager.sol diff --git a/.gitignore b/.gitignore index cbebdcf1e..61a021076 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ pkg !AttestData.sol !VerifierBase.sol !LoadInstances.sol +!VerifierManager.sol *.pf *.vk *.pk diff --git a/Cargo.lock b/Cargo.lock index 4bf754d26..3f391473a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2341,7 +2341,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=cache-lookup-consts-vk#fa1c577c135dc51ada2d048fabe7663908a7d953" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=vka-log#76b37db6e78558767362024f9770caa249e2b49d" dependencies = [ "askama", "blake2b_simd", diff --git a/Cargo.toml b/Cargo.toml index b8b6d2709..ce229c259 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ halo2_wrong_ecc = { git = "https://github.com/zkonduit/halo2wrong", branch = "ac snark-verifier = { git = "https://github.com/zkonduit/snark-verifier", branch = "ac/chunked-mv-lookup", features = [ "derive_serde", ] } -halo2_solidity_verifier = { git = "https://github.com/alexander-camuto/halo2-solidity-verifier", branch = "cache-lookup-consts-vk" } +halo2_solidity_verifier = { git = "https://github.com/alexander-camuto/halo2-solidity-verifier", branch = "vka-log" } maybe-rayon = { version = "0.1.1", default_features = false } bincode = { version = "1.3.3", default_features = false } unzip-n = "0.1.2" diff --git a/abis/EZKLVerifierManager.json b/abis/EZKLVerifierManager.json index 71dfa8a04..211dfd53b 100644 --- a/abis/EZKLVerifierManager.json +++ b/abis/EZKLVerifierManager.json @@ -1,15 +1,4 @@ [ - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, { "inputs": [ { @@ -32,25 +21,6 @@ "name": "OwnableUnauthorizedAccount", "type": "error" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "verifier", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "vka", - "type": "address" - } - ], - "name": "DeployedVKArtifact", - "type": "event" - }, { "anonymous": false, "inputs": [ @@ -59,12 +29,6 @@ "internalType": "address", "name": "addr", "type": "address" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "hash", - "type": "bytes32" } ], "name": "DeployedVerifier", @@ -103,35 +67,6 @@ "internalType": "address", "name": "addr", "type": "address" - }, - { - "internalType": "bytes32", - "name": "runtimeCodeHash", - "type": "bytes32" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "bytecode", - "type": "bytes" - }, - { - "internalType": "address", - "name": "verifier", - "type": "address" - } - ], - "name": "deployVkArtifact", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" } ], "stateMutability": "nonpayable", @@ -156,11 +91,6 @@ "internalType": "bytes", "name": "bytecode", "type": "bytes" - }, - { - "internalType": "uint256", - "name": "_salt", - "type": "uint256" } ], "name": "precomputeAddress", @@ -196,42 +126,13 @@ }, { "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "verifiers", - "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "vkArtifact", - "type": "address" - }, - { - "internalType": "bytes", - "name": "proof", - "type": "bytes" - }, - { - "internalType": "uint256[]", - "name": "instances", - "type": "uint256[]" - } - ], - "name": "verifyProof", + "name": "verifierAddresses", "outputs": [ { "internalType": "bool", @@ -239,25 +140,6 @@ "type": "bool" } ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "vkArtifactToVerifier", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], "stateMutability": "view", "type": "function" } diff --git a/contracts/VerifierManager.sol b/contracts/VerifierManager.sol new file mode 100644 index 000000000..e83b258be --- /dev/null +++ b/contracts/VerifierManager.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +// lib/openzeppelin-contracts/contracts/utils/Context.sol + +// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } + + function _contextSuffixLength() internal view virtual returns (uint256) { + return 0; + } +} + +// lib/openzeppelin-contracts/contracts/access/Ownable.sol + +// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol) + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * The initial owner is set to the address provided by the deployer. This can + * later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + /// set the owener initialy to be the anvil test account + address private _owner = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; + + /** + * @dev The caller account is not authorized to perform an operation. + */ + error OwnableUnauthorizedAccount(address account); + + /** + * @dev The owner is not a valid owner account. (eg. `address(0)`) + */ + error OwnableInvalidOwner(address owner); + + event OwnershipTransferred( + address indexed previousOwner, + address indexed newOwner + ); + + /** + * @dev Initializes the contract setting the address provided by the deployer as the initial owner. + */ + constructor() { + _transferOwnership(msg.sender); + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + if (owner() != _msgSender()) { + revert OwnableUnauthorizedAccount(_msgSender()); + } + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby disabling any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + if (newOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + +// interface for the reusable verifier. +interface Halo2VerifierReusable { + function verifyProof( + address vkArtifact, + bytes calldata proof, + uint256[] calldata instances + ) external returns (bool); +} + +// Manages the deployment of all EZKL reusbale verifiers (ezkl version specific), verifiying key artifacts (circuit specific) and +// routing proof verifications to the correct VKA and associate reusable verifier. +// Helps to prevent the deployment of duplicate verifiers. +contract EZKLVerifierManager is Ownable { + /// @dev Mapping that checks if a given reusable verifier has been deployed + mapping(address => bool) public verifierAddresses; + + event DeployedVerifier(address addr); + + // 1. Compute the address of the verifier to be deployed + function precomputeAddress( + bytes memory bytecode + ) public view returns (address) { + bytes32 hash = keccak256( + abi.encodePacked( + bytes1(0xff), + address(this), + uint(0), + keccak256(bytecode) + ) + ); + + return address(uint160(uint(hash))); + } + + // 2. Deploy the reusable verifier using create2 + /// @param bytecode The bytecode of the reusable verifier to deploy + function deployVerifier( + bytes memory bytecode + ) public returns (address addr) { + assembly { + addr := create2( + 0x0, // value, hardcode to 0 + add(bytecode, 0x20), + mload(bytecode), + 0x0 // salt, hardcode to 0 + ) + if iszero(extcodesize(addr)) { + revert(0, 0) + } + } + verifierAddresses[addr] = true; + emit DeployedVerifier(addr); + } +} diff --git a/src/commands.rs b/src/commands.rs index 6c21c71a8..8fc835a3e 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -99,7 +99,7 @@ pub const DEFAULT_ONLY_RANGE_CHECK_REBASE: &str = "false"; pub const DEFAULT_COMMITMENT: &str = "kzg"; // TODO: In prod this will be the same across all chains we deploy to using the EZKL multisig create2 deployment. /// Default address of the verifier manager. -pub const DEFAULT_VERIFIER_MANAGER_ADDRESS: &str = "contract_verifier_manager.address"; +pub const DEFAULT_VERIFIER_MANAGER_ADDRESS: &str = "0xdc64a140aa3e981100a9beca4e685f962f0cf6c9"; #[cfg(feature = "python-bindings")] /// Converts TranscriptType into a PyObject (Required for TranscriptType to be compatible with Python) @@ -197,6 +197,8 @@ pub enum ContractType { }, /// Deploys a verifying key artifact that the reusable verifier loads into memory during runtime. Encodes the circuit specific data that was otherwise hardcoded onto the stack. VerifyingKeyArtifact, + /// Manages the deployments of all reusable verifier and verifying artifact keys. Routes all the verification tx to the correct artifacts. + VerifierManager } impl Default for ContractType { @@ -220,6 +222,7 @@ impl std::fmt::Display for ContractType { reusable: false, } => "verifier".to_string(), ContractType::VerifyingKeyArtifact => "vka".to_string(), + ContractType::VerifierManager => "manager".to_string() } ) } @@ -237,11 +240,12 @@ impl From<&str> for ContractType { "verifier" => ContractType::Verifier { reusable: false }, "verifier/reusable" => ContractType::Verifier { reusable: true }, "vka" => ContractType::VerifyingKeyArtifact, + "manager" => ContractType::VerifierManager, _ => { log::error!("Invalid value for ContractType"); log::warn!("Defaulting to verifier"); ContractType::default() - } + }, } } } @@ -899,8 +903,12 @@ pub enum Commands { private_key: Option, /// Deployed verifier manager contract's address /// Use to facilitate reusable verifier and vk artifact deployment - #[arg(long, default_value = DEFAULT_CONTRACT_ADDRESS, value_hint = clap::ValueHint::Other)] - verifier_manager: Option, + #[arg(long, value_hint = clap::ValueHint::Other)] + addr_verifier_manager: Option, + /// Deployed reusable verifier contract's address + /// Use to facilitate reusable verifier and vk artifact deployment + #[arg(long, value_hint = clap::ValueHint::Other)] + addr_reusable_verifier: Option, /// Contract type to be deployed #[arg(long = "contract-type", short = 'C', default_value = DEFAULT_CONTRACT_DEPLOYMENT_TYPE, value_hint = clap::ValueHint::Other)] contract: ContractType, diff --git a/src/eth.rs b/src/eth.rs index 1ba955305..d0fe3be16 100644 --- a/src/eth.rs +++ b/src/eth.rs @@ -17,7 +17,7 @@ use alloy::prelude::Wallet; use alloy::json_abi::JsonAbi; use alloy::node_bindings::Anvil; use alloy::primitives::ruint::ParseError; -use alloy::primitives::{keccak256, ParseSignedError, B256, I256}; +use alloy::primitives::{ParseSignedError, B256, I256}; use alloy::providers::fillers::{ ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, SignerFiller, }; @@ -34,7 +34,7 @@ use alloy::transports::{RpcError, TransportErrorKind}; use foundry_compilers::artifacts::Settings as SolcSettings; use foundry_compilers::error::{SolcError, SolcIoError}; use foundry_compilers::Solc; -use halo2_solidity_verifier::encode_calldata; +use halo2_solidity_verifier::{encode_calldata, encode_deploy}; use halo2curves::bn256::{Fr, G1Affine}; use halo2curves::group::ff::PrimeField; use itertools::Itertools; @@ -216,12 +216,12 @@ abigen!( } ); -// The bytecode here was generated from running solc compiler version 0.8.20 with optimization enabled and runs param set to 200. +// The bytecode here was generated from running solc compiler version 0.8.20 with optimization enabled and runs param set to 1. abigen!( #[allow(missing_docs)] #[sol( rpc, - bytecode = "608060405234801561000f575f80fd5b5060405161093138038061093183398101604081905261002e91610130565b806001600160a01b03811661005d57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b61006681610076565b50610070816100c5565b5061015d565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6100cd610102565b6001600160a01b0381166100f657604051631e4fbdf760e01b81525f6004820152602401610054565b6100ff81610076565b50565b5f546001600160a01b0316331461012e5760405163118cdaa760e01b8152336004820152602401610054565b565b5f60208284031215610140575f80fd5b81516001600160a01b0381168114610156575f80fd5b9392505050565b6107c78061016a5f395ff3fe608060405234801561000f575f80fd5b5060043610610090575f3560e01c80638da5cb5b116100635780638da5cb5b14610128578063af83a18d14610138578063ebce97301461015b578063efe1c950146101c5578063f2fde38b146101ed575f80fd5b806326fbc2b0146100945780635d34fd56146100d9578063715018a61461010b5780637224b7e114610115575b5f80fd5b6100bc6100a2366004610494565b60026020525f90815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100ec6100e7366004610551565b610200565b604080516001600160a01b0390931683526020830191909152016100d0565b61011361028a565b005b6100bc61012336600461058b565b61029d565b5f546001600160a01b03166100bc565b61014b6101463660046105d6565b610329565b60405190151581526020016100d0565b6100bc6101693660046106a6565b8151602092830120604080516001600160f81b0319818601523060601b6bffffffffffffffffffffffff191660218201526035810193909352605580840192909252805180840390920182526075909201909152805191012090565b6100bc6101d33660046106e8565b60016020525f90815260409020546001600160a01b031681565b6101136101fb366004610494565b6103bc565b5f8061020a6103fe565b5f8351602085015ff59150813b61021f575f80fd5b50803f5f8181526001602090815260409182902080546001600160a01b0319166001600160a01b03861690811790915582519081529081018390527fc76faa42f66c1ea9e596e467b87dec63ad41cb0e6217b2e4206fc43d8464ca6a910160405180910390a1915091565b6102926103fe565b61029b5f61042a565b565b5f6102a66103fe565b5f808451602086015ff59050803b6102bc575f80fd5b6001600160a01b038181165f8181526002602090815260409182902080546001600160a01b031916948816948517905581519384528301919091527fea544395368ef77bf2480023a11e12abf361a2f69e6efc513fb281a5a10e663e910160405180910390a19392505050565b6001600160a01b038086165f9081526002602052604080822054905163af83a18d60e01b815291921690819063af83a18d906103719083908a908a908a908a906004016106ff565b6020604051808303815f875af115801561038d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103b19190610772565b979650505050505050565b6103c46103fe565b6001600160a01b0381166103f257604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6103fb8161042a565b50565b5f546001600160a01b0316331461029b5760405163118cdaa760e01b81523360048201526024016103e9565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80356001600160a01b038116811461048f575f80fd5b919050565b5f602082840312156104a4575f80fd5b6104ad82610479565b9392505050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f8301126104d7575f80fd5b813567ffffffffffffffff808211156104f2576104f26104b4565b604051601f8301601f19908116603f0116810190828211818310171561051a5761051a6104b4565b81604052838152866020858801011115610532575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f60208284031215610561575f80fd5b813567ffffffffffffffff811115610577575f80fd5b610583848285016104c8565b949350505050565b5f806040838503121561059c575f80fd5b823567ffffffffffffffff8111156105b2575f80fd5b6105be858286016104c8565b9250506105cd60208401610479565b90509250929050565b5f805f805f606086880312156105ea575f80fd5b6105f386610479565b9450602086013567ffffffffffffffff8082111561060f575f80fd5b818801915088601f830112610622575f80fd5b813581811115610630575f80fd5b896020828501011115610641575f80fd5b60208301965080955050604088013591508082111561065e575f80fd5b818801915088601f830112610671575f80fd5b81358181111561067f575f80fd5b8960208260051b8501011115610693575f80fd5b9699959850939650602001949392505050565b5f80604083850312156106b7575f80fd5b823567ffffffffffffffff8111156106cd575f80fd5b6106d9858286016104c8565b95602094909401359450505050565b5f602082840312156106f8575f80fd5b5035919050565b6001600160a01b03861681526060602082018190528101849052838560808301375f60808583018101829052601f19601f8701168301838103820160408501529081018490526001600160fb1b03841115610758575f80fd5b8360051b808660a08401370160a001979650505050505050565b5f60208284031215610782575f80fd5b815180151581146104ad575f80fdfea264697066735822122020b829266d031cbdab7a497c6c1b55609ea4792c7aba586af26a55eb3e7cf29064736f6c63430008140033" + bytecode = "60806040525f80546001600160a01b03191673f39fd6e51aad88f6f4ce6ab8827279cfffb92266179055348015610034575f80fd5b5061003e33610043565b610092565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6103dc8061009f5f395ff3fe608060405234801561000f575f80fd5b5060043610610060575f3560e01c80635717ecef146100645780635d34fd561461009b578063715018a6146100bb5780637a33ac87146100c55780638da5cb5b14610125578063f2fde38b1461012d575b5f80fd5b6100866100723660046102a7565b60016020525f908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6100ae6100a93660046102e8565b610140565b6040516100929190610392565b6100c36101bf565b005b6100ae6100d33660046102e8565b8051602091820120604080516001600160f81b0319818501523060601b6001600160601b03191660218201525f6035820152605580820193909352815180820390930183526075019052805191012090565b6100ae6101d2565b6100c361013b3660046102a7565b6101e0565b5f610149610226565b5f8251602084015ff59050803b61015e575f80fd5b6001600160a01b0381165f90815260016020819052604091829020805460ff19169091179055517f27bf8213352a1c07513a54703c920b9e437940154edead05874c43279acf166c906101b2908390610392565b60405180910390a1919050565b6101c7610226565b6101d05f610258565b565b5f546001600160a01b031690565b6101e8610226565b6001600160a01b03811661021a575f604051631e4fbdf760e01b81526004016102119190610392565b60405180910390fd5b61022381610258565b50565b3361022f6101d2565b6001600160a01b0316146101d0573360405163118cdaa760e01b81526004016102119190610392565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156102b7575f80fd5b81356001600160a01b03811681146102cd575f80fd5b9392505050565b634e487b7160e01b5f52604160045260245ffd5b5f602082840312156102f8575f80fd5b81356001600160401b038082111561030e575f80fd5b818401915084601f830112610321575f80fd5b813581811115610333576103336102d4565b604051601f8201601f19908116603f0116810190838211818310171561035b5761035b6102d4565b81604052828152876020848701011115610373575f80fd5b826020860160208301375f928101602001929092525095945050505050565b6001600160a01b039190911681526020019056fea26469706673582212201d85104628b308554b775f612650220008f8e318f66dc4ace466d82d70bae4e264736f6c63430008140033" )] EZKLVerifierManager, "./abis/EZKLVerifierManager.json" @@ -366,13 +366,14 @@ pub async fn deploy_contract_via_solidity( Ok(contract) } -pub async fn deploy_via_verifier_manager( +pub async fn deploy_vka( sol_code_path: PathBuf, rpc_url: Option<&str>, runs: usize, private_key: Option<&str>, contract_name: &str, verifier_manager: H160, + reusable_verifier: H160, ) -> Result { let (client, _) = setup_eth_backend(rpc_url, private_key).await?; @@ -380,54 +381,82 @@ pub async fn deploy_via_verifier_manager( let verifier_manager_contract = EZKLVerifierManager::new(verifier_manager, client.clone()); // Get the bytecode of the contract to be deployed - let (_, bytecode, _) = + let (_, bytecode, _run_time_bytecode) = get_contract_artifacts(sol_code_path.clone(), contract_name, runs).await?; - if contract_name == "Halo2VerifierReusable" { - // Deploy the contract using the EZKLVerifierManager - let output = verifier_manager_contract - .deployVerifier(bytecode.clone().into()) - .call() - .await?; - // Get the deployed contract address from the receipt - let contract = output.addr; - let _ = verifier_manager_contract - .deployVerifier(bytecode.into()) - .send() - .await?; - return Ok(contract); - } else { - let (_, bytecode, runtime_bytecode) = - get_contract_artifacts(sol_code_path, "Halo2VerifierReusable", runs).await?; + // Check if the reusable verifier is already deployed + let deployed_verifier: bool = verifier_manager_contract + .verifierAddresses(reusable_verifier) + .call() + .await? + ._0; - // Hash the runtime bytecode of the reusable verifier - let reusable_verifier_hash = keccak256(&runtime_bytecode); + if deployed_verifier == false { + panic!("The reusable verifier for this VKA has not been deployed yet."); + } - // Check if the reusable verifier is already deployed - let deployed_verifier = verifier_manager_contract - .verifiers(reusable_verifier_hash) - .call() - .await? - ._0; + let encoded = encode_deploy(&bytecode); - if deployed_verifier == H160::new([0_u8; 20]) { - panic!("The reusable verifier for this VKA has not been deployed yet."); - } - // Statically call the deployVKArtifact to get the receipt back. - let receipt = verifier_manager_contract - .deployVkArtifact(bytecode.clone().into(), deployed_verifier) - .call() - .await?; - - // Get the deployed VkArtifact address from the receipt - let contract = receipt._0; - // Deploy the VkArtifact using the EZKLVerifierManager - let _ = verifier_manager_contract - .deployVkArtifact(bytecode.into(), deployed_verifier) - .send() - .await?; - return Ok(contract); + debug!("encoded: {:#?}", hex::encode(&encoded)); + + let input: TransactionInput = encoded.into(); + + let tx = TransactionRequest::default() + .to(reusable_verifier) + .input(input); + debug!("transaction {:#?}", tx); + + let result = client.call(&tx).await; + + if let Err(e) = result { + return Err(EvmVerificationError::SolidityExecution(e.to_string()).into()); } + + // Now send the tx + let _ = client.send_transaction(tx).await?; + + let result = result?; + debug!("result: {:#?}", result.to_vec()); + + let contract = H160::from_slice(&result.to_vec()[12..32]); + return Ok(contract); +} + +pub async fn deploy_reusable_verifier( + sol_code_path: PathBuf, + rpc_url: Option<&str>, + runs: usize, + private_key: Option<&str>, + contract_name: &str, + verifier_manager: H160, +) -> Result { + let (client, _) = setup_eth_backend(rpc_url, private_key).await?; + + // Create an instance of the EZKLVerifierManager contract + let verifier_manager_contract = EZKLVerifierManager::new(verifier_manager, client.clone()); + + // Get the bytecode of the contract to be deployed + let (_, bytecode, _run_time_bytecode) = + get_contract_artifacts(sol_code_path.clone(), contract_name, runs).await?; + + // Deploy the contract using the EZKLVerifierManager + let output = verifier_manager_contract + .deployVerifier(bytecode.clone().into()) + .call() + .await?; + let out = verifier_manager_contract + .precomputeAddress(bytecode.clone().into()) + .call() + .await?; + // assert that out == output + assert_eq!(out._0, output.addr); + // Get the deployed contract address from the receipt + let contract = output.addr; + let _ = verifier_manager_contract + .deployVerifier(bytecode.into()) + .send() + .await?; + return Ok(contract); } /// diff --git a/src/execute.rs b/src/execute.rs index a47e57bd0..546448fc5 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -3,9 +3,7 @@ use crate::circuit::CheckMode; #[cfg(not(target_arch = "wasm32"))] use crate::commands::CalibrationTarget; #[cfg(not(target_arch = "wasm32"))] -use crate::eth::{ - deploy_contract_via_solidity, deploy_da_verifier_via_solidity, deploy_via_verifier_manager, -}; +use crate::eth::{deploy_contract_via_solidity, deploy_da_verifier_via_solidity}; #[cfg(not(target_arch = "wasm32"))] #[allow(unused_imports)] use crate::eth::{fix_da_sol, get_contract_artifacts, verify_proof_via_solidity}; @@ -442,16 +440,34 @@ pub async fn run(command: Commands) -> Result { addr_path, optimizer_runs, private_key, - verifier_manager, + addr_verifier_manager, + addr_reusable_verifier, contract, } => { + // if contract type is either verifier/reusable + match contract { + ContractType::Verifier { reusable: true } => { + if addr_verifier_manager.is_none() { + panic!("Must pass a verifier manager address for reusable verifier") + } + } + ContractType::VerifyingKeyArtifact => { + if addr_verifier_manager.is_none() || addr_reusable_verifier.is_none() { + panic!( + "Must pass a verifier manager address and reusable verifier address for verifying key artifact" + ) + } + } + _ => {} + }; deploy_evm( sol_code_path.unwrap_or(DEFAULT_SOL_CODE.into()), rpc_url, addr_path.unwrap_or(DEFAULT_CONTRACT_ADDRESS.into()), optimizer_runs, private_key, - verifier_manager.unwrap_or(DEFAULT_VERIFIER_MANAGER_ADDRESS.into()), + addr_verifier_manager.map(|s| s.into()), + addr_reusable_verifier.map(|s| s.into()), contract, ) .await @@ -1488,6 +1504,13 @@ pub(crate) async fn create_evm_vka( let (reusable_verifier, vk_solidity) = generator.render_separately()?; + // Remove the first line of vk_solidity (license identifier). Same license identifier for all contracts in this .sol + let vk_solidity = vk_solidity + .lines() + .skip(1) + .collect::>() + .join("\n"); + // We store each contracts to the same file... // We need to do this so that during the deployment transaction we make sure // verifier manager links the VKA to the correct reusable_verifier. @@ -1613,37 +1636,51 @@ pub(crate) async fn deploy_evm( addr_path: PathBuf, runs: usize, private_key: Option, - verifier_manager: H160Flag, + verifier_manager: Option, + reusable_verifier: Option, contract: ContractType, ) -> Result { + use crate::eth::{deploy_reusable_verifier, deploy_vka}; + let contract_name = match contract { ContractType::Verifier { reusable: false } => "Halo2Verifier", ContractType::Verifier { reusable: true } => "Halo2VerifierReusable", ContractType::VerifyingKeyArtifact => "Halo2VerifyingArtifact", + ContractType::VerifierManager => "EZKLVerifierManager", }; - let contract_address = - if contract_name == "Halo2VerifierReusable" || contract_name == "Halo2VerifyingArtifact" { - // Use VerifierManager to deploy the contract - deploy_via_verifier_manager( - sol_code_path, - rpc_url.as_deref(), - runs, - private_key.as_deref(), - contract_name, - verifier_manager.into(), - ) - .await? - } else { - deploy_contract_via_solidity( - sol_code_path, - rpc_url.as_deref(), - runs, - private_key.as_deref(), - contract_name, - ) - .await? - }; + let contract_address = if contract_name == "Halo2VerifierReusable" { + // Use VerifierManager to deploy the contract + deploy_reusable_verifier( + sol_code_path, + rpc_url.as_deref(), + runs, + private_key.as_deref(), + contract_name, + verifier_manager.unwrap(), + ) + .await? + } else if contract_name == "Halo2VerifyingArtifact" { + deploy_vka( + sol_code_path, + rpc_url.as_deref(), + runs, + private_key.as_deref(), + contract_name, + verifier_manager.unwrap(), + reusable_verifier.unwrap(), + ) + .await? + } else { + deploy_contract_via_solidity( + sol_code_path, + rpc_url.as_deref(), + runs, + private_key.as_deref(), + contract_name, + ) + .await? + }; info!("Contract deployed at: {:#?}", contract_address); diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 30eb731ac..29dd0c852 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1000,13 +1000,22 @@ mod native_tests { use crate::native_tests::run_js_tests; use ezkl::logger::init_logger; use crate::native_tests::lazy_static; + use std::sync::Once; // Global variables to store verifier hashes and identical verifiers lazy_static! { - // create a new variable of type static ref REUSABLE_VERIFIER_ADDR: std::sync::Mutex> = std::sync::Mutex::new(None); + static ref ANVIL_INSTANCE: std::sync::Mutex> = std::sync::Mutex::new(None); } + static INIT: Once = Once::new(); + + fn initialize() { + INIT.call_once(|| { + let anvil_child = crate::native_tests::start_anvil(false, Hardfork::Latest); + *ANVIL_INSTANCE.lock().unwrap() = Some(anvil_child); + }); + } /// Currently only on chain inputs that return a non-negative value are supported. const TESTS_ON_CHAIN_INPUT: [&str; 17] = [ @@ -1121,6 +1130,7 @@ mod native_tests { seq!(N in 0..=93 { #(#[test_case(TESTS[N])])* fn kzg_evm_prove_and_verify_reusable_verifier_(test: &str) { + initialize(); crate::native_tests::init_binary(); let test_dir = TempDir::new(test).unwrap(); let path = test_dir.path().to_str().unwrap(); crate::native_tests::mv_test_(path, test); @@ -1137,7 +1147,7 @@ mod native_tests { match REUSABLE_VERIFIER_ADDR.try_lock() { Ok(mut addr) => { *addr = Some(reusable_verifier_address.clone()); - log::error!("Reusing the same verifeir deployed at address: {}", reusable_verifier_address); + log::error!("Reusing the same verifier deployed at address: {}", reusable_verifier_address); } Err(_) => { log::error!("Failed to acquire lock on REUSABLE_VERIFIER_ADDR"); @@ -1145,11 +1155,11 @@ mod native_tests { } test_dir.close().unwrap(); - } #(#[test_case(TESTS[N])])* fn kzg_evm_prove_and_verify_reusable_verifier_with_overflow_(test: &str) { + initialize(); // verifier too big to fit on chain with overflow calibration target if test == "1l_eltwise_div" || test == "lenet_5" || test == "ltsf" || test == "lstm_large" { return; @@ -1170,7 +1180,7 @@ mod native_tests { match REUSABLE_VERIFIER_ADDR.try_lock() { Ok(mut addr) => { *addr = Some(reusable_verifier_address.clone()); - log::error!("Reusing the same verifeir deployed at address: {}", reusable_verifier_address); + log::error!("Reusing the same verifier deployed at address: {}", reusable_verifier_address); } Err(_) => { log::error!("Failed to acquire lock on REUSABLE_VERIFIER_ADDR"); @@ -1178,7 +1188,6 @@ mod native_tests { } test_dir.close().unwrap(); - } }); @@ -1192,8 +1201,8 @@ mod native_tests { let path = test_dir.path().to_str().unwrap(); crate::native_tests::mv_test_(path, test); let _anvil_child = crate::native_tests::start_anvil(false, Hardfork::Latest); kzg_evm_prove_and_verify(2, path, test.to_string(), "private", "private", "public"); - #[cfg(not(feature = "icicle"))] - run_js_tests(path, test.to_string(), "testBrowserEvmVerify", false); + // #[cfg(not(feature = "icicle"))] + // run_js_tests(path, test.to_string(), "testBrowserEvmVerify", false); test_dir.close().unwrap(); } @@ -2228,7 +2237,7 @@ mod native_tests { input_visibility: &str, param_visibility: &str, output_visibility: &str, - reusable_verifier_address: &mut Option, + _reusable_verifier_address: &mut Option, overflow: bool, ) -> String { let anvil_url = ANVIL_URL.as_str(); @@ -2253,55 +2262,77 @@ mod native_tests { let vk_arg = format!("{}/{}/key.vk", test_dir, example_name); let rpc_arg = format!("--rpc-url={}", anvil_url); + // addr path for verifier manager contract let addr_path_arg = format!("--addr-path={}/{}/addr.txt", test_dir, example_name); + let verifier_manager_arg: String; let settings_arg = format!("--settings-path={}", settings_path); + // reusable verifier sol_arg let sol_arg = format!("--sol-code-path={}/{}/kzg.sol", test_dir, example_name); - // if the reusable verifier address is not set, create the verifier - let deployed_addr_arg = match reusable_verifier_address { - Some(addr) => addr.clone(), - None => { - // create the reusable verifier - let args = vec![ - "create-evm-verifier", - "--vk-path", - &vk_arg, - &settings_arg, - &sol_arg, - "--reusable", - ]; - - let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) - .args(&args) - .status() - .expect("failed to execute process"); - assert!(status.success()); - - // deploy the verifier - let args = vec![ - "deploy-evm", - rpc_arg.as_str(), - addr_path_arg.as_str(), - sol_arg.as_str(), - "-C=verifier/reusable", - ]; - - let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) - .args(&args) - .status() - .expect("failed to execute process"); - assert!(status.success()); - - // read in the address - let addr = - std::fs::read_to_string(format!("{}/{}/addr.txt", test_dir, example_name)) - .expect("failed to read address file"); - - let deployed_addr_arg = format!("--addr-verifier={}", addr); - // set the reusable verifier address - *reusable_verifier_address = Some(addr); - deployed_addr_arg - } + // create the reusable verifier + let args = vec![ + "create-evm-verifier", + "--vk-path", + &vk_arg, + &settings_arg, + &sol_arg, + "--reusable", + ]; + + let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) + .args(&args) + .status() + .expect("failed to execute process"); + assert!(status.success()); + + // deploy the verifier manager + let args = vec![ + "deploy-evm", + rpc_arg.as_str(), + addr_path_arg.as_str(), + // set the sol code path to be contracts/VerifierManager.sol relative to root + "--sol-code-path=contracts/VerifierManager.sol", + "-C=manager", + ]; + + let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) + .args(&args) + .status() + .expect("failed to execute process"); + assert!(status.success()); + + // read in the address of the verifier manager + let addr = std::fs::read_to_string(format!("{}/{}/addr.txt", test_dir, example_name)) + .expect("failed to read address file"); + + verifier_manager_arg = format!("--addr-verifier-manager={}", addr); + + // if the reusable verifier address is not set, deploy the verifier manager and then create the verifier + let rv_addr = { + // addr path for rv contract + let addr_path_arg = format!("--addr-path={}/{}/addr_rv.txt", test_dir, example_name); + // deploy the reusable verifier via the verifier router. + let args = vec![ + "deploy-evm", + rpc_arg.as_str(), + addr_path_arg.as_str(), + sol_arg.as_str(), + verifier_manager_arg.as_str(), + "-C=verifier/reusable", + ]; + + let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR)) + .args(&args) + .status() + .expect("failed to execute process"); + assert!(status.success()); + + // read in the address of the verifier manager + let addr = + std::fs::read_to_string(format!("{}/{}/addr_rv.txt", test_dir, example_name)) + .expect("failed to read address file"); + + addr }; let addr_path_arg_vk = format!("--addr-path={}/{}/addr_vk.txt", test_dir, example_name); @@ -2321,11 +2352,15 @@ mod native_tests { .expect("failed to execute process"); assert!(status.success()); - // deploy the vka + let rv_addr_arg = format!("--addr-reusable-verifier={}", rv_addr); + + // deploy the vka via the "DeployVKA" command on the reusable verifier let args = vec![ "deploy-evm", rpc_arg.as_str(), addr_path_arg_vk.as_str(), + verifier_manager_arg.as_str(), + rv_addr_arg.as_str(), sol_arg_vk.as_str(), "-C=vka", ]; @@ -2355,6 +2390,8 @@ mod native_tests { assert!(status.success()); + let deployed_addr_arg = format!("--addr-verifier={}", rv_addr); + // now verify the proof let pf_arg = format!("{}/{}/proof.pf", test_dir, example_name); let args = vec![ diff --git a/verifier_abi.json b/verifier_abi.json index efb06340b..1cbd28cc0 100644 --- a/verifier_abi.json +++ b/verifier_abi.json @@ -1 +1 @@ -[{"type":"function","name":"verifyProof","inputs":[{"internalType":"bytes","name":"proof","type":"bytes"},{"internalType":"uint256[]","name":"instances","type":"uint256[]"}],"outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable"}] \ No newline at end of file +[{"type":"function","name":"deployVKA","inputs":[{"name":"bytecode","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"addr","type":"address","internalType":"address"}],"stateMutability":"nonpayable"},{"type":"function","name":"precomputeAddress","inputs":[{"name":"bytecode","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"verifyProof","inputs":[{"name":"vk","type":"address","internalType":"address"},{"name":"proof","type":"bytes","internalType":"bytes"},{"name":"instances","type":"uint256[]","internalType":"uint256[]"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"function","name":"vkaLog","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"event","name":"DeployedVKArtifact","inputs":[{"name":"vka","type":"address","indexed":false,"internalType":"address"}],"anonymous":false},{"type":"error","name":"UnloggedVka","inputs":[{"name":"vka","type":"address","internalType":"address"}]}] \ No newline at end of file diff --git a/vk.abi b/vk.abi index 0b6e079d5..a1d602c82 100644 --- a/vk.abi +++ b/vk.abi @@ -1 +1 @@ -[{"type":"constructor","inputs":[]}] \ No newline at end of file +[{"type":"constructor","inputs":[],"stateMutability":"nonpayable"}] \ No newline at end of file From cda4b2dacd865b83a45c0a9cb746ca2a6fdb1df0 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 28 Oct 2024 19:30:03 +0800 Subject: [PATCH 45/46] merge "main" into "verifier-router" --- .cargo/{config => config.toml} | 0 .github/workflows/rust.yml | 63 ++ .github/workflows/update-ios-package.yml | 75 ++ .gitignore | 6 +- Cargo.lock | 268 +++++- Cargo.toml | 148 ++-- build.rs | 7 + examples/conv2d_mnist/main.rs | 2 +- examples/mlp_4d_einsum.rs | 2 +- examples/notebooks/ezkl_demo_batch.ipynb | 771 +++++++++++++++++ src/bin/ezkl.rs | 20 +- src/bin/ios_gen_bindings.rs | 269 ++++++ src/bindings/mod.rs | 12 + src/{ => bindings}/python.rs | 0 src/bindings/universal.rs | 579 +++++++++++++ src/bindings/wasm.rs | 372 ++++++++ src/circuit/modules/polycommit.rs | 4 +- src/circuit/modules/poseidon.rs | 2 +- src/circuit/ops/chip.rs | 186 ++-- src/circuit/ops/layouts.rs | 58 +- src/circuit/ops/mod.rs | 2 +- src/circuit/ops/region.rs | 39 +- src/circuit/table.rs | 6 +- src/circuit/tests.rs | 32 +- src/commands.rs | 55 +- src/eth.rs | 9 - src/execute.rs | 65 +- src/graph/errors.rs | 15 +- src/graph/input.rs | 22 +- src/graph/mod.rs | 59 +- src/graph/model.rs | 58 +- src/graph/node.rs | 41 +- src/graph/postgres.rs | 4 +- src/graph/utilities.rs | 32 +- src/graph/vars.rs | 4 +- src/lib.rs | 108 ++- src/pfsys/evm/aggregation_kzg.rs | 8 +- src/pfsys/mod.rs | 51 +- src/tensor/val.rs | 2 +- src/tensor/var.rs | 47 ++ src/wasm.rs | 793 ------------------ tests/{wasm => assets}/calibration.json | 0 tests/{wasm => assets}/input.json | 0 tests/{wasm => assets}/kzg | Bin tests/{wasm => assets}/kzg1.srs | Bin tests/{wasm => assets}/model.compiled | Bin 1801 -> 1809 bytes tests/{wasm => assets}/network.onnx | Bin tests/{wasm => assets}/pk.key | Bin tests/assets/proof.json | 1 + tests/{wasm => assets}/proof_aggr.json | 0 tests/{wasm => assets}/settings.json | 1 + tests/{wasm => assets}/vk.key | Bin tests/{wasm => assets}/vk_aggr.key | Bin tests/{wasm => assets}/witness.json | 0 tests/integration_tests.rs | 48 +- tests/ios/can_verify_aggr.swift | 39 + tests/ios/gen_pk_test.swift | 42 + tests/ios/gen_vk_test.swift | 35 + tests/ios/pk_is_valid_test.swift | 69 ++ .../ios/verify_encode_verifier_calldata.swift | 71 ++ tests/ios/verify_gen_witness.swift | 45 + tests/ios/verify_kzg_commit.swift | 64 ++ tests/ios/verify_validations.swift | 103 +++ tests/ios_integration_tests.rs | 11 + tests/py_integration_tests.rs | 5 +- tests/wasm.rs | 36 +- tests/wasm/proof.json | 1 - 67 files changed, 3583 insertions(+), 1284 deletions(-) rename .cargo/{config => config.toml} (100%) create mode 100644 .github/workflows/update-ios-package.yml create mode 100644 build.rs create mode 100644 examples/notebooks/ezkl_demo_batch.ipynb create mode 100644 src/bin/ios_gen_bindings.rs create mode 100644 src/bindings/mod.rs rename src/{ => bindings}/python.rs (100%) create mode 100644 src/bindings/universal.rs create mode 100644 src/bindings/wasm.rs delete mode 100644 src/wasm.rs rename tests/{wasm => assets}/calibration.json (100%) rename tests/{wasm => assets}/input.json (100%) rename tests/{wasm => assets}/kzg (100%) rename tests/{wasm => assets}/kzg1.srs (100%) rename tests/{wasm => assets}/model.compiled (81%) rename tests/{wasm => assets}/network.onnx (100%) rename tests/{wasm => assets}/pk.key (100%) create mode 100644 tests/assets/proof.json rename tests/{wasm => assets}/proof_aggr.json (100%) rename tests/{wasm => assets}/settings.json (97%) rename tests/{wasm => assets}/vk.key (100%) rename tests/{wasm => assets}/vk_aggr.key (100%) rename tests/{wasm => assets}/witness.json (100%) create mode 100644 tests/ios/can_verify_aggr.swift create mode 100644 tests/ios/gen_pk_test.swift create mode 100644 tests/ios/gen_vk_test.swift create mode 100644 tests/ios/pk_is_valid_test.swift create mode 100644 tests/ios/verify_encode_verifier_calldata.swift create mode 100644 tests/ios/verify_gen_witness.swift create mode 100644 tests/ios/verify_kzg_commit.swift create mode 100644 tests/ios/verify_validations.swift create mode 100644 tests/ios_integration_tests.rs delete mode 100644 tests/wasm/proof.json diff --git a/.cargo/config b/.cargo/config.toml similarity index 100% rename from .cargo/config rename to .cargo/config.toml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f97f7fd65..5787c7125 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -688,3 +688,66 @@ jobs: run: source .env/bin/activate; cargo nextest run py_tests::tests::nbeats_ # - name: Reusable verifier tutorial # run: source .env/bin/activate; cargo nextest run py_tests::tests::reusable_ + + ios-integration-tests: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-2024-07-18 + override: true + components: rustfmt, clippy + - uses: baptiste0928/cargo-install@v1 + with: + crate: cargo-nextest + locked: true + - name: Run ios tests + run: CARGO_BUILD_TARGET=aarch64-apple-darwin RUSTUP_TOOLCHAIN=nightly-2024-07-18-aarch64-apple-darwin cargo test --test ios_integration_tests --features ios-bindings-test --no-default-features + + swift-package-tests: + runs-on: macos-latest + needs: [ios-integration-tests] + + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-2024-07-18 + override: true + components: rustfmt, clippy + - name: Build EzklCoreBindings + run: CONFIGURATION=debug cargo run --bin ios_gen_bindings --features "ios-bindings uuid camino uniffi_bindgen" --no-default-features + + - name: Clone ezkl-swift- repository + run: | + git clone https://github.com/zkonduit/ezkl-swift-package.git + + - name: Copy EzklCoreBindings + run: | + rm -rf ezkl-swift-package/Sources/EzklCoreBindings + cp -r build/EzklCoreBindings ezkl-swift-package/Sources/ + + - name: Set up Xcode environment + run: | + sudo xcode-select -s /Applications/Xcode.app/Contents/Developer + sudo xcodebuild -license accept + + - name: Run Package Tests + run: | + cd ezkl-swift-package + xcodebuild test \ + -scheme EzklPackage \ + -destination 'platform=iOS Simulator,name=iPhone 15 Pro,OS=17.5' \ + -resultBundlePath ../testResults + + - name: Run Example App Tests + run: | + cd ezkl-swift-package/Example + xcodebuild test \ + -project Example.xcodeproj \ + -scheme EzklApp \ + -destination 'platform=iOS Simulator,name=iPhone 15 Pro,OS=17.5' \ + -parallel-testing-enabled NO \ + -resultBundlePath ../../exampleTestResults \ + -skip-testing:EzklAppUITests/EzklAppUITests/testButtonClicksInOrder \ No newline at end of file diff --git a/.github/workflows/update-ios-package.yml b/.github/workflows/update-ios-package.yml new file mode 100644 index 000000000..8ae294221 --- /dev/null +++ b/.github/workflows/update-ios-package.yml @@ -0,0 +1,75 @@ +name: Build and Publish EZKL iOS SPM package + +on: + workflow_dispatch: + inputs: + tag: + description: "The tag to release" + required: true + push: + tags: + - "*" + +jobs: + build-and-update: + runs-on: macos-latest + + steps: + - name: Checkout EZKL + uses: actions/checkout@v3 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + + - name: Build EzklCoreBindings + run: CONFIGURATION=release cargo run --bin ios_gen_bindings --features "ios-bindings uuid camino uniffi_bindgen" --no-default-features + + - name: Clone ezkl-swift-package repository + run: | + git clone https://github.com/zkonduit/ezkl-swift-package.git + + - name: Copy EzklCoreBindings + run: | + rm -rf ezkl-swift-package/Sources/EzklCoreBindings + cp -r build/EzklCoreBindings ezkl-swift-package/Sources/ + + - name: Set up Xcode environment + run: | + sudo xcode-select -s /Applications/Xcode.app/Contents/Developer + sudo xcodebuild -license accept + + - name: Run Package Tests + run: | + cd ezkl-swift-package + xcodebuild test \ + -scheme EzklPackage \ + -destination 'platform=iOS Simulator,name=iPhone 15 Pro,OS=17.5' \ + -resultBundlePath ../testResults + + - name: Run Example App Tests + run: | + cd ezkl-swift-package/Example + xcodebuild test \ + -project Example.xcodeproj \ + -scheme EzklApp \ + -destination 'platform=iOS Simulator,name=iPhone 15 Pro,OS=17.5' \ + -parallel-testing-enabled NO \ + -resultBundlePath ../../exampleTestResults \ + -skip-testing:EzklAppUITests/EzklAppUITests/testButtonClicksInOrder + + - name: Commit and Push Changes to feat/ezkl-direct-integration + run: | + cd ezkl-swift-package + git config user.name "GitHub Action" + git config user.email "action@github.com" + git add Sources/EzklCoreBindings + git commit -m "Automatically updated EzklCoreBindings for EZKL" + git tag ${{ github.event.inputs.tag }} + git remote set-url origin https://zkonduit:${EZKL_PORTER_TOKEN}@github.com/zkonduit/ezkl-swift-package.git + git push origin + git push origin --tags + env: + EZKL_PORTER_TOKEN: ${{ secrets.EZKL_PORTER_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 61a021076..24ffd7823 100644 --- a/.gitignore +++ b/.gitignore @@ -47,7 +47,7 @@ var/ node_modules /dist timingData.json -!tests/wasm/pk.key -!tests/wasm/vk.key +!tests/assets/pk.key +!tests/assets/vk.key docs/python/build -!tests/wasm/vk_aggr.key \ No newline at end of file +!tests/assets/vk_aggr.key \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3f391473a..8c575c575 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1158,6 +1158,38 @@ dependencies = [ "serde", ] +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.22", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cast" version = "0.3.0" @@ -1861,6 +1893,7 @@ version = "0.0.0" dependencies = [ "alloy", "bincode", + "camino", "chrono", "clap", "clap_complete", @@ -1916,7 +1949,10 @@ dependencies = [ "tokio-postgres", "tosubcommand", "tract-onnx", + "uniffi", + "uniffi_bindgen", "unzip-n", + "uuid", "wasm-bindgen", "wasm-bindgen-console-logger", "wasm-bindgen-rayon", @@ -2105,6 +2141,15 @@ dependencies = [ "yansi 1.0.1", ] +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + [[package]] name = "fs4" version = "0.9.1" @@ -2268,6 +2313,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "goblin" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" +dependencies = [ + "log", + "plain", + "scroll", +] + [[package]] name = "group" version = "0.13.0" @@ -2487,6 +2543,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heck" version = "0.4.1" @@ -2755,12 +2817,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.15.0", ] [[package]] @@ -3755,6 +3817,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "plotters" version = "0.3.6" @@ -4690,6 +4758,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + [[package]] name = "sec1" version = "0.7.3" @@ -4935,6 +5023,12 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "snark-verifier" version = "0.1.1" @@ -5256,6 +5350,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", +] + [[package]] name = "thiserror" version = "1.0.58" @@ -5463,6 +5566,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "0.6.5" @@ -5522,9 +5634,9 @@ checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -5779,6 +5891,134 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "uniffi" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31bff6daf87277a9014bcdefbc2842b0553392919d1096843c5aad899ca4588" +dependencies = [ + "anyhow", + "uniffi_bindgen", + "uniffi_build", + "uniffi_core", + "uniffi_macros", +] + +[[package]] +name = "uniffi_bindgen" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96061d7e01b185aa405f7c9b134741ab3e50cc6796a47d6fd8ab9a5364b5feed" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata", + "fs-err", + "glob", + "goblin", + "heck 0.5.0", + "once_cell", + "paste", + "serde", + "textwrap", + "toml", + "uniffi_meta", + "uniffi_testing", + "uniffi_udl", +] + +[[package]] +name = "uniffi_build" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6b86f9b221046af0c533eafe09ece04e2f1ded04ccdc9bba0ec09aec1c52bd" +dependencies = [ + "anyhow", + "camino", + "uniffi_bindgen", +] + +[[package]] +name = "uniffi_checksum_derive" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22dbe67c1c957ac6e7611bdf605a6218aa86b0eebeb8be58b70ae85ad7d73dc" +dependencies = [ + "quote", + "syn 2.0.53", +] + +[[package]] +name = "uniffi_core" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3210d57d6ab6065ab47a2898dacdb7c606fd6a4156196831fa3bf82e34ac58a6" +dependencies = [ + "anyhow", + "bytes", + "camino", + "log", + "once_cell", + "paste", + "static_assertions", +] + +[[package]] +name = "uniffi_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58691741080935437dc862122e68d7414432a11824ac1137868de46181a0bd2" +dependencies = [ + "bincode", + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn 2.0.53", + "toml", + "uniffi_meta", +] + +[[package]] +name = "uniffi_meta" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7663eacdbd9fbf4a88907ddcfe2e6fa85838eb6dc2418a7d91eebb3786f8e20b" +dependencies = [ + "anyhow", + "bytes", + "siphasher", + "uniffi_checksum_derive", +] + +[[package]] +name = "uniffi_testing" +version = "0.28.0" +source = "git+https://github.com/ElusAegis/uniffi-rs?branch=feat/testing-feature-build-fix#4684b9e7da2d9c964c2b3a73883947aab7370a06" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata", + "fs-err", + "once_cell", +] + +[[package]] +name = "uniffi_udl" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef408229a3a407fafa4c36dc4f6ece78a6fb258ab28d2b64bddd49c8cb680f6" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta", + "uniffi_testing", + "weedle2", +] + [[package]] name = "unindent" version = "0.2.3" @@ -5829,6 +6069,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom", +] + [[package]] name = "valuable" version = "0.1.0" @@ -6066,6 +6315,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "weedle2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" +dependencies = [ + "nom", +] + [[package]] name = "whoami" version = "1.5.1" diff --git a/Cargo.toml b/Cargo.toml index ce229c259..94acf0a1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ cargo-features = ["profile-rustflags"] name = "ezkl" version = "0.0.0" edition = "2021" +default-run = "ezkl" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,42 +12,43 @@ edition = "2021" # Name to be imported within python # Example: import ezkl name = "ezkl" -crate-type = ["cdylib", "rlib"] +crate-type = ["cdylib", "rlib", "staticlib"] [dependencies] halo2_gadgets = { git = "https://github.com/zkonduit/halo2", branch = "ac/optional-selector-poly" } halo2curves = { git = "https://github.com/privacy-scaling-explorations/halo2curves", rev = "b753a832e92d5c86c5c997327a9cf9de86a18851", features = [ "derive_serde", + "derive_serde", ] } -halo2_proofs = { git = "https://github.com/zkonduit/halo2?branch=ac/cache-lookup-commitments#8b13a0d2a7a34d8daab010dadb2c47dfa47d37d0", package = "halo2_proofs", branch = "ac/cache-lookup-commitments" } -rand = { version = "0.8", default_features = false } -itertools = { version = "0.10.3", default_features = false } -clap = { version = "4.5.3", features = ["derive"] } -serde = { version = "1.0.126", features = ["derive"], optional = true } -serde_json = { version = "1.0.97", default_features = false, features = [ - "float_roundtrip", - "raw_value", -], optional = true } -clap_complete = "4.5.2" -log = { version = "0.4.17", default_features = false, optional = true } -thiserror = { version = "1.0.38", default_features = false } -hex = { version = "0.4.3", default_features = false } +halo2_proofs = { git = "https://github.com/zkonduit/halo2", package = "halo2_proofs", branch = "ac/cache-lookup-commitments", features = [ + "circuit-params", +] } +rand = { version = "0.8", default-features = false } +itertools = { version = "0.10.3", default-features = false } +clap = { version = "4.5.3", features = ["derive"], optional = true } +serde = { version = "1.0.126", features = ["derive"] } +clap_complete = { version = "4.5.2", optional = true } +log = { version = "0.4.17", default-features = false } +thiserror = { version = "1.0.38", default-features = false } +hex = { version = "0.4.3", default-features = false } halo2_wrong_ecc = { git = "https://github.com/zkonduit/halo2wrong", branch = "ac/chunked-mv-lookup", package = "ecc" } snark-verifier = { git = "https://github.com/zkonduit/snark-verifier", branch = "ac/chunked-mv-lookup", features = [ "derive_serde", ] } -halo2_solidity_verifier = { git = "https://github.com/alexander-camuto/halo2-solidity-verifier", branch = "vka-log" } +halo2_solidity_verifier = { git = "https://github.com/alexander-camuto/halo2-solidity-verifier", branch = "vka-log", optional = true } maybe-rayon = { version = "0.1.1", default_features = false } bincode = { version = "1.3.3", default_features = false } unzip-n = "0.1.2" num = "0.4.1" -portable-atomic = "1.6.0" -tosubcommand = { git = "https://github.com/zkonduit/enum_to_subcommand", package = "tosubcommand" } -semver = "1.0.22" +portable-atomic = { version = "1.6.0", optional = true } +tosubcommand = { git = "https://github.com/zkonduit/enum_to_subcommand", package = "tosubcommand", optional = true } +semver = { version = "1.0.22", optional = true } -# evm related deps [target.'cfg(not(target_arch = "wasm32"))'.dependencies] +serde_json = { version = "1.0.97", features = ["float_roundtrip", "raw_value"] } + +# evm related deps alloy = { git = "https://github.com/alloy-rs/alloy", version = "0.1.0", rev = "5fbf57bac99edef9d8475190109a7ea9fb7e5e83", features = [ "provider-http", "signers", @@ -54,52 +56,65 @@ alloy = { git = "https://github.com/alloy-rs/alloy", version = "0.1.0", rev = "5 "rpc-types-eth", "signer-wallet", "node-bindings", -] } -foundry-compilers = { version = "0.4.1", features = ["svm-solc"] } -ethabi = "18" -indicatif = { version = "0.17.5", features = ["rayon"] } -gag = { version = "1.0.0", default_features = false } + +], optional = true } +foundry-compilers = { version = "0.4.1", features = [ + "svm-solc", +], optional = true } +ethabi = { version = "18", optional = true } +indicatif = { version = "0.17.5", features = ["rayon"], optional = true } +gag = { version = "1.0.0", default-features = false, optional = true } instant = { version = "0.1" } reqwest = { version = "0.12.4", default-features = false, features = [ "default-tls", "multipart", "stream", -] } -openssl = { version = "0.10.55", features = ["vendored"] } -tokio-postgres = "0.7.10" -pg_bigdecimal = "0.1.5" -lazy_static = "1.4.0" -colored_json = { version = "3.0.1", default_features = false, optional = true } -regex = { version = "1", default_features = false } -tokio = { version = "1.35.0", default_features = false, features = [ +], optional = true } +openssl = { version = "0.10.55", features = ["vendored"], optional = true } +tokio-postgres = { version = "0.7.10", optional = true } +pg_bigdecimal = { version = "0.1.5", optional = true } +lazy_static = { version = "1.4.0", optional = true } +colored_json = { version = "3.0.1", default-features = false, optional = true } +regex = { version = "1", default-features = false, optional = true } +tokio = { version = "1.35.0", default-features = false, features = [ "macros", "rt-multi-thread", -] } +], optional = true } pyo3 = { version = "0.21.2", features = [ "extension-module", "abi3-py37", "macros", -], default_features = false, optional = true } +], default-features = false, optional = true } pyo3-asyncio = { git = "https://github.com/jopemachine/pyo3-asyncio/", branch = "migration-pyo3-0.21", features = [ "attributes", "tokio-runtime", -], default_features = false, optional = true } - -pyo3-log = { version = "0.10.0", default_features = false, optional = true } -tract-onnx = { git = "https://github.com/sonos/tract/", rev = "40c64319291184814d9fea5fdf4fa16f5a4f7116", default_features = false, optional = true } +], default-features = false, optional = true } +pyo3-log = { version = "0.10.0", default-features = false, optional = true } +tract-onnx = { git = "https://github.com/sonos/tract/", rev = "40c64319291184814d9fea5fdf4fa16f5a4f7116", default-features = false, optional = true } tabled = { version = "0.12.0", optional = true } metal = { git = "https://github.com/gfx-rs/metal-rs", optional = true } objc = { version = "0.2.4", optional = true } -mimalloc = "0.1" +mimalloc = { version = "0.1", optional = true } + +# universal bindings +uniffi = { version = "=0.28.0", optional = true } +getrandom = { version = "0.2.8", optional = true } +uniffi_bindgen = { version = "=0.28.0", optional = true } +camino = { version = "^1.1", optional = true } +uuid = { version = "1.10.0", features = ["v4"], optional = true } [target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dependencies] -colored = { version = "2.0.0", default_features = false, optional = true } -env_logger = { version = "0.10.0", default_features = false, optional = true } -chrono = "0.4.31" -sha256 = "1.4.0" +colored = { version = "2.0.0", default-features = false, optional = true } +env_logger = { version = "0.10.0", default-features = false, optional = true } +chrono = { version = "0.4.31", optional = true } +sha256 = { version = "1.4.0", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] +serde_json = { version = "1.0.97", default-features = false, features = [ + "float_roundtrip", + "raw_value", +] } getrandom = { version = "0.2.8", features = ["js"] } instant = { version = "0.1", features = ["wasm-bindgen", "inaccurate"] } @@ -115,6 +130,10 @@ wasm-bindgen-console-logger = "0.1.1" [target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } + +[build-dependencies] +uniffi = { version = "0.28", features = ["build"], optional = true } + [dev-dependencies] tempfile = "3.3.0" lazy_static = "1.4.0" @@ -188,6 +207,10 @@ test = false bench = false required-features = ["ezkl"] +[[bin]] +name = "ios_gen_bindings" +required-features = ["ios-bindings", "uuid", "camino", "uniffi_bindgen"] + [features] web = ["wasm-bindgen-rayon"] default = [ @@ -199,18 +222,41 @@ default = [ ] onnx = ["dep:tract-onnx"] python-bindings = ["pyo3", "pyo3-log", "pyo3-asyncio"] +ios-bindings = ["mv-lookup", "precompute-coset", "parallel-poly-read", "uniffi"] +ios-bindings-test = ["ios-bindings", "uniffi/bindgen-tests"] ezkl = [ "onnx", - "serde", - "serde_json", - "log", - "colored", - "env_logger", + "dep:colored", + "dep:env_logger", "tabled/color", + "serde_json/std", "colored_json", + "dep:alloy", + "dep:foundry-compilers", + "dep:ethabi", + "dep:indicatif", + "dep:gag", + "dep:reqwest", + "dep:openssl", + "dep:tokio-postgres", + "dep:pg_bigdecimal", + "dep:lazy_static", + "dep:regex", + "dep:tokio", + "dep:mimalloc", + "dep:chrono", + "dep:sha256", + "dep:portable-atomic", + "dep:clap_complete", + "dep:halo2_solidity_verifier", + "dep:semver", + "dep:clap", + "dep:tosubcommand", +] +parallel-poly-read = [ "halo2_proofs/circuit-params", + "halo2_proofs/parallel-poly-read", ] -parallel-poly-read = ["halo2_proofs/parallel-poly-read"] mv-lookup = [ "halo2_proofs/mv-lookup", "snark-verifier/mv-lookup", @@ -224,7 +270,6 @@ empty-cmd = [] no-banner = [] no-update = [] - # icicle patch to 0.1.0 if feature icicle is enabled [patch.'https://github.com/ingonyama-zk/icicle'] icicle = { git = "https://github.com/ingonyama-zk/icicle?rev=45b00fb", package = "icicle", branch = "fix/vhnat/ezkl-build-fix" } @@ -232,6 +277,9 @@ icicle = { git = "https://github.com/ingonyama-zk/icicle?rev=45b00fb", package = [patch.'https://github.com/zkonduit/halo2'] halo2_proofs = { git = "https://github.com/zkonduit/halo2?branch=ac/cache-lookup-commitments#8b13a0d2a7a34d8daab010dadb2c47dfa47d37d0", package = "halo2_proofs", branch = "ac/cache-lookup-commitments" } +[patch.crates-io] +uniffi_testing = { git = "https://github.com/ElusAegis/uniffi-rs", branch = "feat/testing-feature-build-fix" } + [profile.release] rustflags = ["-C", "relocation-model=pic"] lto = "fat" diff --git a/build.rs b/build.rs new file mode 100644 index 000000000..31bdb681c --- /dev/null +++ b/build.rs @@ -0,0 +1,7 @@ +fn main() { + if cfg!(feature = "ios-bindings-test") { + println!("cargo::rustc-env=UNIFFI_CARGO_BUILD_EXTRA_ARGS=--features=ios-bindings --no-default-features"); + } + + println!("cargo::rerun-if-changed=build.rs"); +} diff --git a/examples/conv2d_mnist/main.rs b/examples/conv2d_mnist/main.rs index 702770440..dd56a3c98 100644 --- a/examples/conv2d_mnist/main.rs +++ b/examples/conv2d_mnist/main.rs @@ -285,7 +285,7 @@ where } pub fn runconv() { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] env_logger::init(); const KERNEL_HEIGHT: usize = 5; diff --git a/examples/mlp_4d_einsum.rs b/examples/mlp_4d_einsum.rs index 81f310b0f..5dadfe16b 100644 --- a/examples/mlp_4d_einsum.rs +++ b/examples/mlp_4d_einsum.rs @@ -220,7 +220,7 @@ impl = Tensor::::new( diff --git a/examples/notebooks/ezkl_demo_batch.ipynb b/examples/notebooks/ezkl_demo_batch.ipynb new file mode 100644 index 000000000..1eef1d531 --- /dev/null +++ b/examples/notebooks/ezkl_demo_batch.ipynb @@ -0,0 +1,771 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "n8QlFzjPRIGN" + }, + "source": [ + "# EZKL DEMO (BATCHED)\n", + "\n", + "This is mostly similar to the original EZKL demo but includes an example of how batching is handled.\n", + "\n", + "**Learning Objectives**\n", + "1. Learn some basic AI/ML techniques by training a toy model in pytorch to perform classification\n", + "2. Convert the toy model into zk circuit with ezkl to do provable inference\n", + "3. Create a solidity verifier and deploy it on Remix (you can deploy it however you like but we will use Remix as it's quite easy to setup)\n", + "4. Learn how to use batch inputs and outputs\n", + "\n", + "**Important Note**: You might want to avoid calling \"Run All\". There's some file locking issue with Colab which can cause weird bugs. To mitigate this issue you should run cell by cell on Colab." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "dx81GOIySIpa" + }, + "source": [ + "# Step 1: Training a toy model\n", + "\n", + "For this demo we will use a toy data set called the Iris dataset to demonstrate how training can be performed. The Iris dataset is a collection of Iris flowers and is one of the earliest dataset used to validate classification methodologies.\n", + "\n", + "[More info in the dataset](https://archive.ics.uci.edu/dataset/53/iris)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "JhHE2WMvS9NP" + }, + "source": [ + "First, we will need to import all the various dependencies required to train the model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "gvQ5HL1bTDWF" + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "from sklearn.datasets import load_iris\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.metrics import accuracy_score, precision_score, recall_score\n", + "import numpy as np\n", + "import torch\n", + "import torch.nn as nn\n", + "import torch.nn.functional as F\n", + "from torch.autograd import Variable\n", + "import tqdm" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Op9SHfZHUkaR" + }, + "source": [ + "Inspect the dataset. Note that for the Iris dataset we have 3 targets.\n", + "\n", + "0 = Iris-setosa\n", + "\n", + "1 = Iris-versicolor\n", + "\n", + "2 = Iris-virginica" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 424 + }, + "id": "C4XXA1hoU30c", + "outputId": "b92f7a06-ace9-4bcc-bd6a-d8e9a5550bd2" + }, + "outputs": [], + "source": [ + "iris = load_iris()\n", + "dataset = pd.DataFrame(\n", + " data= np.c_[iris['data'], iris['target']],\n", + " columns= iris['feature_names'] + ['target'])\n", + "dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "I8RargmGTWN2" + }, + "source": [ + "Next, we can begin defining the neural net model. For this dataset we will use a small fully connected neural net.\n", + "\n", + "
\n", + "\n", + "**Note:**\n", + "For the 1st layer we use 4x20, because there are 4 features we want as inputs. After which we add a ReLU.\n", + "\n", + "For the 2nd layer we use 20x20, then add a ReLU.\n", + "\n", + "And for the last layer we use 20x3, because there are 3 classes we want to classify, then add a ReLU.\n", + "\n", + "The last ReLU function gives us an array of 3 elements where the position of the largest value gives us the target that we want to classify.\n", + "\n", + "For example, if we get [0, 0.001, 0.002] as the output of the last ReLU. As, 0.002 is the largest value, the inferred value is 2.\n", + "\n", + "\n", + "![image.png](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcMAAAENCAIAAAD8BplNAAAMPWlDQ1BJQ0MgUHJvZmlsZQAASImVVwdYU8kWnltSIbQAAlJCb4J0AkgJoQWQXgQbIQkQSoyBoGJHFxVcu1jAhq6KKHZA7IidRbD3xYKAsi4W7MqbFNB1X/ne+b6597//nPnPmXPnlgFA/RRXLM5FNQDIExVI4kIDGWNSUhmkLkAEZIAAe6DD5eWLWTExkQDa4Pnv9u4m9IR2zUGm9c/+/2qafEE+DwAkBuJ0fj4vD+JDAOCVPLGkAACijDefUiCWYdiAtgQmCPFCGc5U4EoZTlfgfXKfhDg2xM0AkFW5XEkmAGptkGcU8jKhhlofxE4ivlAEgDoDYr+8vEl8iNMgtoE+Yohl+sz0H3Qy/6aZPqTJ5WYOYcVc5EYOEuaLc7nT/s9y/G/Ly5UOxrCCTTVLEhYnmzOs2+2cSREyrApxryg9KhpiLYg/CPlyf4hRapY0LFHhjxry8tmwZkAXYic+NygCYkOIQ0S5UZFKPj1DGMKBGK4QdKqwgJMAsR7ECwX5wfFKn82SSXHKWGhdhoTNUvIXuBJ5XFmsh9KcRJZS/3WWgKPUx9SKshKSIaZCbFEoTIqCWA1ix/yc+Ailz6iiLHbUoI9EGifL3wLiOIEoNFChjxVmSELilP6lefmD88U2Zwk5UUp8oCArIUxRH6yZx5XnD+eCtQlErMRBHUH+mMjBufAFQcGKuWPdAlFivFLng7ggME4xFqeKc2OU/riZIDdUxptB7JZfGK8ciycVwAWp0MczxAUxCYo88aJsbniMIh98GYgEbBAEGEAKWzqYBLKBsLW3vhdeKXpCABdIQCYQAAclMzgiWd4jgsd4UAT+hEgA8ofGBcp7BaAQ8l+HWMXRAWTIewvlI3LAM4jzQATIhddS+SjRULQk8BQywn9E58LGg/nmwibr//f8IPudYUEmUslIByMy1Ac9icHEIGIYMYRoixvgfrgPHgmPAbC54Ezca3Ae3/0JzwjthMeEG4QOwp2JwmLJT1mOBh1QP0RZi/Qfa4FbQU13PBD3hepQGdfFDYAD7gbjsHB/GNkdsmxl3rKqMH7S/tsMfrgbSj+KEwWlDKMEUGx+Hqlmp+Y+pCKr9Y/1UeSaPlRv9lDPz/HZP1SfD88RP3tiC7GD2HnsNHYRO4bVAwZ2EmvAWrDjMjy0up7KV9dgtDh5PjlQR/iPeIN3VlbJfKcapx6nL4q+AsFU2TsasCeJp0mEmVkFDBb8IggYHBHPcQTDxcnFFQDZ90Xx+noTK/9uILot37l5fwDge3JgYODody78JAD7PeHjf+Q7Z8OEnw4VAC4c4UklhQoOlx0I8C2hDp80fWAMzIENnI8L8AA+IAAEg3AQDRJACpgAs8+C61wCpoAZYC4oAWVgGVgN1oNNYCvYCfaAA6AeHAOnwTlwGbSBG+AeXD2d4AXoA+/AZwRBSAgNoSP6iAliidgjLggT8UOCkUgkDklB0pBMRIRIkRnIPKQMWYGsR7Yg1ch+5AhyGrmItCN3kEdID/Ia+YRiqCqqjRqhVuhIlImy0Ag0AR2PZqKT0SJ0ProEXYtWobvROvQ0ehm9gXagL9B+DGAqmC5mijlgTIyNRWOpWAYmwWZhpVg5VoXVYo3wPl/DOrBe7CNOxOk4A3eAKzgMT8R5+GR8Fr4YX4/vxOvwZvwa/gjvw78RaARDgj3Bm8AhjCFkEqYQSgjlhO2Ew4Sz8FnqJLwjEom6RGuiJ3wWU4jZxOnExcQNxL3EU8R24hNiP4lE0ifZk3xJ0SQuqYBUQlpH2k06SbpK6iR9IKuQTcgu5BByKllELiaXk3eRT5CvkrvInykaFEuKNyWawqdMoyylbKM0Uq5QOimfqZpUa6ovNYGaTZ1LXUutpZ6l3qe+UVFRMVPxUolVEarMUVmrsk/lgsojlY+qWqp2qmzVcapS1SWqO1RPqd5RfUOj0axoAbRUWgFtCa2adob2kPZBja7mqMZR46vNVqtQq1O7qvZSnaJuqc5Sn6BepF6uflD9inqvBkXDSoOtwdWYpVGhcUTjlka/Jl3TWTNaM09zseYuzYua3VokLSutYC2+1nytrVpntJ7QMbo5nU3n0efRt9HP0ju1idrW2hztbO0y7T3ardp9Olo6bjpJOlN1KnSO63ToYrpWuhzdXN2lugd0b+p+GmY0jDVMMGzRsNphV4e91xuuF6An0CvV26t3Q++TPkM/WD9Hf7l+vf4DA9zAziDWYIrBRoOzBr3DtYf7DOcNLx1+YPhdQ9TQzjDOcLrhVsMWw34jY6NQI7HROqMzRr3GusYBxtnGq4xPGPeY0E38TIQmq0xOmjxn6DBYjFzGWkYzo8/U0DTMVGq6xbTV9LOZtVmiWbHZXrMH5lRzpnmG+SrzJvM+CxOL0RYzLGos7lpSLJmWWZZrLM9bvreytkq2WmBVb9VtrWfNsS6yrrG+b0Oz8beZbFNlc92WaMu0zbHdYNtmh9q522XZVdhdsUftPeyF9hvs20cQRniNEI2oGnHLQdWB5VDoUOPwyFHXMdKx2LHe8eVIi5GpI5ePPD/ym5O7U67TNqd7zlrO4c7Fzo3Or13sXHguFS7XXWmuIa6zXRtcX7nZuwncNrrddqe7j3Zf4N7k/tXD00PiUevR42nhmeZZ6XmLqc2MYS5mXvAieAV6zfY65vXR28O7wPuA918+Dj45Prt8ukdZjxKM2jbqia+ZL9d3i2+HH8MvzW+zX4e/qT/Xv8r/cYB5AD9ge0AXy5aVzdrNehnoFCgJPBz4nu3Nnsk+FYQFhQaVBrUGawUnBq8PfhhiFpIZUhPSF+oeOj30VBghLCJsedgtjhGHx6nm9IV7hs8Mb45QjYiPWB/xONIuUhLZOBodHT565ej7UZZRoqj6aBDNiV4Z/SDGOmZyzNFYYmxMbEXsszjnuBlx5+Pp8RPjd8W/SwhMWJpwL9EmUZrYlKSeNC6pOul9clDyiuSOMSPHzBxzOcUgRZjSkEpKTUrdnto/Nnjs6rGd49zHlYy7Od56/NTxFycYTMidcHyi+kTuxINphLTktF1pX7jR3CpufzonvTK9j8fmreG94AfwV/F7BL6CFYKuDN+MFRndmb6ZKzN7svyzyrN6hWzheuGr7LDsTdnvc6JzduQM5Cbn7s0j56XlHRFpiXJEzZOMJ02d1C62F5eIOyZ7T149uU8SIdmej+SPz28o0IY/8i1SG+kv0keFfoUVhR+mJE05OFVzqmhqyzS7aYumdRWFFP02HZ/Om940w3TG3BmPZrJmbpmFzEqf1TTbfPb82Z1zQufsnEudmzP392Kn4hXFb+clz2ucbzR/zvwnv4T+UlOiViIpubXAZ8GmhfhC4cLWRa6L1i36VsovvVTmVFZe9mUxb/GlX51/XfvrwJKMJa1LPZZuXEZcJlp2c7n/8p0rNFcUrXiycvTKulWMVaWr3q6euPpiuVv5pjXUNdI1HWsj1zass1i3bN2X9Vnrb1QEVuytNKxcVPl+A3/D1Y0BG2s3GW0q2/Rps3Dz7S2hW+qqrKrKtxK3Fm59ti1p2/nfmL9VbzfYXrb96w7Rjo6dcTubqz2rq3cZ7lpag9ZIa3p2j9vdtidoT0OtQ+2Wvbp7y/aBfdJ9z/en7b95IOJA00HmwdpDlocqD9MPl9YhddPq+uqz6jsaUhraj4QfaWr0aTx81PHojmOmxyqO6xxfeoJ6Yv6JgZNFJ/tPiU/1ns48/aRpYtO9M2POXG+ObW49G3H2wrmQc2fOs86fvOB74dhF74tHLjEv1V/2uFzX4t5y+Hf33w+3erTWXfG80tDm1dbYPqr9xFX/q6evBV07d51z/fKNqBvtNxNv3r417lbHbf7t7ju5d17dLbz7+d6c+4T7pQ80HpQ/NHxY9YftH3s7PDqOPwp61PI4/vG9J7wnL57mP/3SOf8Z7Vl5l0lXdbdL97GekJ6252Ofd74Qv/jcW/Kn5p+VL21eHvor4K+WvjF9na8krwZeL36j/2bHW7e3Tf0x/Q/f5b37/L70g/6HnR+ZH89/Sv7U9XnKF9KXtV9tvzZ+i/h2fyBvYEDMlXDlvwIYbGhGBgCvdwBASwGADvdn1LGK/Z/cEMWeVY7Af8KKPaLcPACohf/vsb3w7+YWAPu2we0X1FcfB0AMDYAEL4C6ug61wb2afF8pMyLcB2yO+Zqelw7+jSn2nD/k/fMZyFTdwM/nfwFGJnxZflARVQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAABw6ADAAQAAAABAAABDQAAAAAI4glaAABAAElEQVR4Aezde7hvVVX/8Z+plZmZmZWlKVpeKi+lpRLo4aAm4V3TSDBEvEtGT8bj7ZHiIcseVPTxhiBpKhKSJHnLIxKhdjNTNCsvaWVpVmRmd/P32vvNma725ex9LsD5nr3mH3OPNeeYY4451hyfNeac67v2Nb785S//vznNFrj6LPC///u/Ov+qr/qqf/u3f/u6r/s6tJL/+I//QP/Xf/3XV3/1V6O/9mu/Vvm//uu/Iq51rWtVMvhVRQ9ODbGRqepv/uZvbnKTmwyZyfn6r//6mOtC4WiL0FC/Cuc0W2CTFrjGjKSbtNTMdiVZYCDp//zP/6ChGBDU17/8y798wzd8A4iEnnLYlwL//M///I3f+I1oBIZ/+Id/+JZv+RaXmmsIbYHgChxMLB5yQKfm4DKe0FaVcrSEOQUUzmm2wCYtMCPpJg01s11ZFghJP/nJT77nPe+Bni7h4wMf+MC/+Iu/eMtb3nKb29xG1X3ucx/dP+EJT3jUox51/PHHowtCVT372c/+pm/6plNPPRUUQlsg+KEPfej0008Hl6pwKoS5oPPbvu3bXJIv6UgrhQM0B4FnTrMFdtcCS8ufOc0WuNotIBi87LLLfn45IQAlaHv/+99/zDHHnHfeeX/1V38lMj3iiCPueMc7ij3V4qez8s985jMAFw06NQGOav/4j//4oosuApda4VcVjGLWEBsYrQu0BIWnFlhxOa2a6dkCa1rgmieffPKaFXPhbIGrxgLtL93gBje47W1v+6d/+qff8R3f8bKXvex617ve9a9//dvd7nY7dux48pOf/PCHPxz23eEOdwCIl19+OWbwJ/b8whe+YJFugX/3u9/9hje8IYUVXvOa1/z7v/979F3vetcb3/jGat/3vvd99KMf/c7v/E7RrqovfvGL173udT/+8Y8D1utc5zpf8zVfc41rXAM/Tb70pS+BWhKumrHPvRwwFljakJrTbIGr3QIiRxBml9PSGzhaj1PJ4v2f/umfEC7PPPPMc845x+r+p37qp8SMT3va0z72sY/B0z/6oz+yA3CrW91K2+c85zniU3HrJZdccvOb3xx0Cktf/OIXC10RF1544bOe9ayLL774tNNOUwWm3/jGNz7oQQ867rjjHEllgel6/2q3yazAAllgRtIFulkHsqoA1GG9kBMUHnXUUWDxe77neyzSrcEVqoJx4s1M8Eu/9EsXXHABQPyBH/iBpzzlKaJOUHvGGWe86U1vskOK/2//9m9b5sPfSy+9lEAbBS984Qu/9Vu/9bu+67tAMDknnXQS7H7FK16xffv2gaSgWRVlVB3I5p7Htq8tMCPpvrboLG+PLNAiXQQKQJ/61Kfa2YSPlvPQ0xocwDmDgnr2PcHc29/+9kMOOUSJSPb7vu/7hKUKzz333Dvf+c5gES1KLby1rnf5qU99CjI+8YlP1Orbv/3bsd3jHveQC1Qhb69e4e/Aao/UnxttdQvMSLrVZ8DVO34wlwIW7HZCIaPw0PIc8H3zN38zeG3VD0kRSsSeRYveEkUDXNup4lbrd83/7M/+jDSX9kx/7/d+T1jqBSlAecoppyj/4Ac/2CaAhkCTwGQmUBeUQQepV69Z5t4XzgLzidPC3bIDSmGHPMBLDshe8pKXWLN//vOfd+xzy1veEvH85z/fOf61r31tR0Af/vCHbWuKSb0RBR8t2x1P4RGK/u7v/u4P/dAPHXbYYY6n3vve9zpKete73vUnf/Int7/97Z1NWdfbB7CiVwVV3/3ud7/uda9z9ASy7Qa84x3vIO2ggw5yxkWHjEuB+dDpgJpnV/5gZiS98m0897C+BWCopB78gTzHRAAumBNmOnA/+OCDRZ3iTblzdst2+Hizm93s3ve+9yc+8Qn7oXe60528Z3q3u93NST2gtJAXkHoBAMOPLCdxKIgUaeL57u/+bnHrLW5xixvd6EYAWvRqc+CmN72pEgGvEFVYqnxG0vXv2FyztgXmN/PXtstcetVYoAV1uf1Qh0X6tdK3rpcLV4GgzVNIqhzwKVcC8tAAtyqXdjzjwYbG0+6nS2v89kz7KZS3oBCVkKAtIWBUXzjxC3vlc5otsFsWmJF0t8w1M+9jC8DQJAIy25dQDAFD7WNWHoPCQYSnUG/wDKiNwBAaQklCgG+iICy6qrC78pGPLtasHWwzMVtgtQVmJF1tk7nkKrVAsAX+iiJBZGBXaAn7wKgEFkWaNBvEANAkFMkO1acHR0QJP0dbVfoKN0NktOYDZHHqcYiaidkCG1pgni4bmmhmuBItEJzJgZ1uECC1/lqhh56jMH7wp6TlvK3SUA8UqoWzCBKCRZxoMNqvnpIvmB3QXGDrUkfFsGgpHeZ8tsAmLTDHpJs01Mx2pVgAtIEtubdHX/SiF+nDeZES50UOl2AidJvueMI7SVCp3CtQGFq8k4AOPREkYAOjEWpdKpfXnVxfGDQZ4TDCpapRcqWMeRZ6IFpgfvYeiHd1ccYUrtEXMgpLvdv00pe+FERCT3CpFlDCwTEgUIhNIInHvmowCvgwAEFYicAjVUJCAOoyUaQpdFmJfCA1oqpRssw1Z7MFNrbAHJNubKOZ40q1QEAJwpzdn3DCCUDQb5lgZZAHYSUo6af0FukwzltNVvTekcLjEqTCXPDq3N8RvJ/qq8Xpt0xqCzO9xm91r2HH9yHvDJdX6m3dasLn3zhttTu+n44XGtIM3kHG9i6VIACrl+p9rwT8LYWa17qWT5Fi83Uo0Alz7ZaeddZZr371q32+xCulXuaHm9o+5CEPedjDHuad/Ne//vXe2/ca/yMe8Yijjz46hNUXpCazvvZTo8xqLY4F5tX94tyrA1pTiNZbUMLJotS2LAWkzujFjyeeeOKRRx7pN6Bve9vbvK7vR/TKhaKQF7D6RhS49DUThdiYymef3vCGN/hkic1QX37yRSgwCnwt9uEssaLgGUYP6Dl1lQ5ujkmvUnPPne3CAnAtgBMqgrmxNvfBpwc84AG+WqJEAoVAFrD6cMn555/fij4M/YM/+IP73ve+oPPQQw89/PDDHVv58L6g9TGPeQy29knlEjXgte6id6HVXDVbYDMWmJF0M1aaea4KC1h3Fy0Gqbq0N2pVDgSt32GlrzeJSX3dWZVQFLyKNJUISB30+/AoCXe5y10e+chHwkcYKhT1SgBmQauP6U3HAENdzjA6tclM740FZiTdG+vNbfeZBXxwxIdEnSMB0+c973niSl8n+e3f/m2bm5bkfizv+3i+VCIOFZD6binofOxjH+uQylm/2JMecFZA+oxnPMM3SpwsWfgLS7Ui03aqX/R3YIUTjNpv7Zh+nw1gFrS1LTCf3W/t+78fjL5dUZjoH9j5wojLz372s5DOKl5w+qQnPUnkKN5snW51Dxkf/OAH+4wp3dXe+ta39u9J2jBVAnwt+X3BxIdKoa2TqH/8x3/0/0U6a4LC5EvkY+590v3ABrMKC2+BGUkX/hYe2AOAeg1wwB/QtOR/7Wtfa+0PTwWhvueEB/LKg8iaiDpH8zkCzSZzfiVZYF7dX0mGncXuMwsMEASLaCGqxbv1vsN670KBUav13meKMza5RAmFEftMoVnQbIFVFphj0lUmmQv2MwvAwSASYiIke6nFnlbrlBWNdnbkPSqboamPrYYDT/ezYc3qHFAWmJH0gLqdB+RgOiAyNJgohaHQ0y4nAIWYGBDTdf0BaYd5UPuzBWYk3Z/vzqzbkgWAJpSEmGhIOoLNqXWgqvLBNq2a6dkCV4EF5n3Sq8DIcxf7zALhaUEoVLU9Cmcd8bfMb/m/zzqbBc0W2LQF5ph006aaGa8mC4zV/SCgJxgNPYdSahW2YToKZ2K2wFVjgfl391eNnede9tAC0zATnRSr+N4MLSZVCENnGN1DE8/N9oUFZiTdwIo24LzAGFOezHs3aLNOtd8sjpoBCqNkJrIAQGTzaKYe26NBanZT2KUX9aGqJkpEowgN5YNwyi+AnW07W+DKtsC8ut+shXk1v+WinBa2jrdtVrTPh1cUugwREFM5q9m2ckm4WbA5tVg2WULHZZDFJnmldJzXKweXLAxVEZ07gdqx0gfNK7YCtrKd57FfGRaYT5w2sKqghhNy3dzSG4u+LeSIYxfNuDTfXpEHvvk8RObbAwh2IWrrVLEwO/e8AYLQ0NgV+gSJ7416D1/+7//+78MgBac3uMENbn/723s532/zayKPYF48EmKG0WG3mbiSLDDHpLtnWL49HHW3WoatIqYQeY6SVluvZXj28QD74Ac/6Esll112GZsXcnqAWQoMA3qqeT4VhPrs0z3ucQ9fevY5KJIVhqHdrNnaq609l+xbC8xIuoE9OSQOzsyH5cJJJfnnBi1XVfNnMRcg4OfBwSBW8W7FAvZhWI+cD33oQ74L9dGPfhRWtopnLl8hUctiIlOcbgRjKsT/d3/3dz6A4tJy4bDDDvMr0lvc4hYsOAB0tvNWnE9X7ZhnJN3A3sWSmPx3IJ90u8lNbuKr7CKgFd+7HFJan47LKREQEyiS8nnNadVMZwG2fc973vPOd74ThjKRVfl1rnOdL33pS2p7pAFTJYCVGS+//HIQeb3rXU+sinApkiXhDne4w33uc5873vGOWrG5OzKv7ucJdmVbYEbSzVoYjL785S+/4IILrB/9j6BnPvOZXDScXZGvKXEwV8vzfaKYt693crWmkAO70CdKfd7pIx/5CFvd7GY3g482SdHQE3QixvAHzfKwUpSKEJ9CzM997nP9R7wf+ZEf8WE95gXBrC2GHc1nYrbAPrfANU8++eR9LvQAE+jtpete97oAlFu+613vetCDHuRLmsb45S9/eXXOpZWvTpxf0KT82te+Nsf2z4h+5Vd+xX8Z8nnNA8xcezac/pXIn//5n1uYOz6yWgejDpSucY1rgNGRWO+ayylLKoeeUNINYmGQKrchAF7hsi+TkgaRRbXgeM8Um1vNFtiMBebptYGVRDQ8Uy5Z0ctvdKMbWUWeccYZ3JUP284Dtb6SKcB87nOf60vvN77xjd/73vdyYP8wwyr1lFNOcb7sP1/6CLGPt/sQ3LZt2xTCDpKxOSchBwQTDgj4PGLRPd9wVljW0Cpp5c6Gxti6G+r53qhLmyf/+Z//afg3vOENQ09NlEeTkJBybHrxWHIZzYxuCn6PKIb9/d//fVWPf/zj9YKNKHI0kfDIk7NCz/lytsAeWOArK6Y9aLx1mnA8Xsf5uZ9zDMl/whCZ+ucWjoxV+U+Wzkl+53d+5wUveAGeY445xjfeH/3oR8NQ/9DtN37jNxxDW2yqetWrXkWafz0El61ABU38PCBQnkkHsbgWZpNpMhBjlxA2QO0UQzoDRzDUG9/4RsxGbYUu6kRIoad1wJpJc+UjR0iQVMpoolQljv5/67d+S7+k6YKd1RKePnHO+WyBvbfAHJNuYENeBzcHKPBeDbilf1cpqDzhhBP8x2ARaJue97vf/eCps+NDDjmESws//bdL/1FD7OlYWS7m8s8zVIFRJdr6/0IEcvW6yNU30GkBq41uCUd3/ot5GNogEI6Y/Ed7lmFqbGwbjKpCQ0OXaLmkFR550kSaLIZt3KMYXGLwr/S8iPrmN7+ZcM8zDfGPrjGkw5zPFth7C8xIuikb8lV8nDY/FFXZyxOiAlYOyT/BIgbr1gIotCaYeS9OkRdaTsJ4h9zBCH6FCQkg0obM6WWFi54bkQQZ2xUxRnjnSWMNHoyKRhmtxFwS2lNnZ9lSUDkSS5IgZ3xplGc6mMu217/+9T2oPvGJT/zmb/4mwj1SPmwbLi+6VWf99xMLzEi6wY3g9rwUE8gTeDoGsTFqX8//aFPo7XEOCRG8fCNE9W+FwCs2uS1Rh1TiU0t7G6nWmBby1vhiUv8vExZrqAlOreALaUrqC31gJLDVQMa4oF4WA5RQEsYpsRJ3KIRH1UDSMNRjZhQiSMMmkSyFpPK2RxEYmFRbuUv/Gg+kul/ulO1pEnSni+RgSGZKzvlsgT22wHx2v4Hp2rbjfqIn61BIykX9RzaHy05FODAY/cAHPgArv//7vx8cXHTRRVyX3/Lzpz3tabe61a14tQPot771rY6bHUZZZoLXm970pl73sRVw6KGHAlxQwqWx0UYerGyg2f5dbfjGS0eEEaEbnQMlGGqwDOWknhE8VDqOZ+reZMLgpdGSEsCnBKFVOc6agFREHZXrsRN8/bo7OsXzhS98Qa33TImKLQvjUbt/G3LWbjEsML9PusF94mwiF/4GQOEpt3cJPTXj3n4M7i39GHjpC1/4wvPOOw+AejO8wAeqakII5NVKWKohQlvSFMJZ23lDiaKk8lG4iIQhU3vglBEpkQxcdG9j9KlPfSoTMYio09v1mFWxhoQQpEsICYDKMSMILCVQXgKaEsNK7pQumFfukmQ/gkI4FbTG11wJZh3Rp0slc5otsDcWmFf3G1iPp/E3bhyGcmau266ociGn9rmlV0TPPffcT37yk1asjpI4P1/VCoMmAs+82iWZ/FwOheuezwcWZCrRS+WLm68GKUYwanG9PQ2/YsIA16AeYwpUeyHUwKVAM4INEeUIRisRRYKU2Aylyr0gkD3RMFo5Bg09tOzA9NqZwtriWVwLz5rvVxZYeI+9CqwJ4zi8EInjcWCXo1Meq5znc2AvPHkvCg/E5PkSAj9/VovAyYFjyMmTo9YlBpdq8USMXvYfYjO6xQO8IFePHFvDxmjHQyh6m9vcxlqbPY844gjLcE8aS3Vr7YZs+JK2EqsOern4Kxlmd0FHjAYoB5tyRrYDo1aVu4MHg3vBwvZS7nznO2dMDFqplVcy57MF9sYCM5JuYD0QwKW5Ir4gUgk/t2zntAMQ+aRF+r3vfW9VOCGFnDNjaIHvMlFyDZOGYYrLRU+qQgfE/pMCnXCHel02WHThdrEkHuOiORi1++F1UbmQUAT6Ez/xEyzjR7e+g7d9+/YdO3ZA1f/+7//GrFWJzJLLBHYZsMYzzOJS7dBtcNJwKlNbGkLYYeEkaD5EzcRsgb2xwIykG1gvDMXkZRqH8t5hgox292BBPuysSSFwLG4VB2nikuuGs7wXsqiFLGBUbQ4MQyW1dZG3AwUEtoHRG+h3VVWn8xRA69kwjWJYqUJDEIcyl9c5DUQcCjetrOHp2Wef7QUGby8Q6C0IpusQyaXEpIOY0hVOc5roS0l5zPWu0OUAUzSwxu/XupXL45zz2QL7ygLzlNqUJaGntaFfNNrmgwvewLdDCjSBY9hqlQo9Cz/hIF8NJQFoB0phjRyqYtNw4AINwiNN0mZ/g9FhIzoHYUpCsYYJtqCnEnEf9LRZjGAEQainjiZowanz+ksvvXTbtm33ve997ZYqZxBrfJukaImciBW57pTUI6LLodXqy0qGEJfUiz8h0XM+W2BfWWBG0g0sWRQJKDk/VhjhN6DHH388Gojw1WCxqJOXKoEO2CxmhasOmqADEPHSqI1Ce6kYeHVhEXp0D3rCXK1IaAdg1F7tRHaghjGiaW74RlE5wg8NBJsGjoG5HvjAB7IAswSyLKD205/+NIP4fa22Bth3DPBA0ukAM8swDmKkFHCZqVe30p1CDIFyDGj8vSEwbTLTswX2lQVmJN3AknweWGCCbtan1vjiR54JO3LmQMSHncRiaF8ahrPexvdbcuHqL//yL8NNv7UHJWeddZbmvl2CTTzroydg5bTTTgOdSnpj/6EPfaheNtDp6qg2/LDJcNCpAATRonULeUgq0PaosO+BsJDHw0SGZoCXXHIJS5IAQP2iCeGxwZJOpXxAb/WA6msA6CAGpxL0KB+XFcZW70TRE4N+B5uqIWomZgvsvQVmJN3AhlwOdnBFfAFH/unSSbSoykr8zDPPFG+KVZU4Ttm+nPpSCUzhvUBWgAZGvUTpk1HexvcFEx5+1FFH+a2UWqh6u9vdDgyBG/wgZqDVBvpdtdV0bvhMATrBpV8c+IWCJfzd7353Y2QrGtHfU8RAPITE7Bby8BSGanXb2972rne9K6PFafcDm4RBTv404ZcILMeg95JCl8rxLzHtTDV3hYgfQR+GtSGD1m/lV63l5t4OcAvMSLrBDc4z4Rr3AwrgYDg/GNWYlx577LH8U/m73/1uPgxfvJnvk3riTZEmxAETXte3SwhM/cbJ25T2CoVIciXnn3++MNaKWBe6gxT7IYyOVbwBemxAf8M0dlvGnhaCa1DFGumPVoWGmL6Nrckhhxzi97IYoK2g20jZ5KCDDhKTfvazn0WEfVkbrVbOFHKtEHKFEXISJPw6qm0EeqR4vGKlikq6rrnyCM3JJHlOswX20gLzNNrAgDwNRAJKvgcyAIodT66o3M6mNTu/FYpeeOGFzqN9TO/000+HiYQ6bIGYAlWhkCU/+PAykCVt39Pj2GFT3k4+b9dKOffOzzfQbK+rgy19DYQist6HAgN0gHunSdbynihCTpE3U6TF4A+YCDEQVa985SvR97///T1FEH4sa+2PubEffPDBfULUpYYsmTUw6M4lm6A1JGpFLIlTIR61chIkBGjWdcbEo8TPSYG+dYDbl7b6srsC5dO2wjmfLbA3FpiRdGPrcTn+zBvBAVSFj0Wg8pNOOunEE0/81Kc+5aTFovVFL3qRcxVslvC81Iv6PrDfkp/Dw1Y/f/qxH/uxF7/4xfBILAZ5//AP/xDC+gi0Ixqq5Py6kzbWbO84aCgNGTSUgnIAVC01IBTcoSEApZ6FPFUhqYYAixHi0aRHCEIhUY7pNXzyk5/sMwV+Yi/wJEpgqCEGuZ94+ViBb7yivaJkl6BgnJGVkEYH0tC60LVcGpdKllW+IizFSR8S2BObSy+rglE5OT/6oz/aYJWrdU/JwR/io+c0W2BvLDB/wWQD68GCnO1Nb3qTPcFb3vKW3M8q3t6f/0Ti26Mnn3yyUAtYQEkBFwav9YjXsPkQEYRVwo19SsMJ9eGHH2457zxKfPeEJzzBwh+O+JEPZLnTne4EGmijuz72sYFme1dNPYBSR8DFEpgaIRf0QasFnRDfMD0bbGt4PPjpgc1cbMyibapqTn8fHIFraM21dQbl57M/+ZM/qeE555wjenXu5AUy+6TYSKCAKq1YAHSCZjjrZ6OgkFhvgBLVz5/oxjJyhRjK1bKzS60Q5dWSTz3JYT37e2vV04vlQap3V6mnbajKhFTdO0POrWcLLFlg/oLJxvOg1T1n5q4t8NGgUHgVKJTbEhVk8VKwAmdFZOJNl86gRECaSDi5bpGXjnm7Kjyk2WokvyooE3xvrNzecRgarfRVkDiEQb2+REdh8AcH+8JAGBT6qNJc5NgotHUJHBHGzgKQd9u2bW95y1vAsWeMx4ZLRtOj7sB0geHLXvYy/4AAwsJBkn1UlBEgnYQNLS+plciXL1lzZ0xKExaTqEcHtfLYfOZZrbUCZehJvSTUllhsc5otsPcW+Mribu9lHagS+J7E6yAOX839QgS7cjZA+bxyMAoK1cIIUOK4CQCdeuqpwJczK8dDAmYujRMGgZKqRGRqhwF1N+griYA7VAr4dEENCb7Yi3CaJHCmql+pOx8L76i3bIYrIAxtIIagHIOGBPYYEJ5758lRvlMmiOwSbdNDX53R6UiPLjOIgB3wiUztG/gXeCwjeERgCBCXVVuKdjPLMM4USdWWqJF8bP2Gysf0HJF5HtBTrVb0VGv46CHtSrLzLHaLWGCOSTe40Zw5uAF/PJDv8UDezl0L5fJbOWSRVGHj1Za3AjHeq1Afok6Qym9VkVCvpEloPBEYiJJLG2i219U6giy6pg/1RI5gFPZ5SIjgoJ4B6oRi2NBpqCTdXK4YtSq/AfMhV48Q+wBMISDtTQYbGgJSH2bFM+Cs3ing4M6HsV2SbLdUX6yknOXphijVr1zSewqU00QTudvkWM+2rCWC59MDHvAA2xE2eUmwvUvgcrulA8M0aYzoOc0W2BsLzPukG1iPJ3PRL37xiwhJuKSB/TvbbS6BhSoboHzYZVXwyP8fhQgWqny+wMqenW8MY7bNZ7dO3EosN8aglYUtme0Atke5gVp7XU0rvdPZ+cw73vEOQbQS0POQhzzkB3/wB/1vzoE11MYpbwOUwrYsJZdDVbRtU6L8T0AjJYRBfBXUKw33vOc9na1JNivBnLE33n5uz4B0YCtd+6fKdgDI9CiCqlI2IarUNujYJ3WJpzwCzcJakUMf0HnkkUfSR+wM4hlc2Et/hLwRtVO81+acBWx1C8wx6QYzgIsWtohluB9ufghl0MK3fo+kJBjl8NjQlcjBU6t7NICoM4XFuQqV4AcxotdxOfraQLm9qKaDNa9FN0y3LyFwsxM6NGwgxljST6EcYlqCzaWciWwIOGICiN6ldYn/Na95jaG59MsFLzaIc41xjD3DYqMAE5FsV4GE97///czrZVsICGfRIzXcoUBKkhDBevBdTo6x+L+t3rKiCfmQtHcqwLqFgiYUG3c2sXM+W2BvLLDl9kl5EVdkMi7HnRA8iq+GCJVw4DBl6my1wh8POb1hrkRV0shBcOx45HwYQ1iA0EpSjqdybYvLXCpXOxRTsjphSJMlQcs7Ay4rwawkWKlwlCfHJQaQZzUNWaAeWLGWB2SQfcpM4RVdJ7AuaOiyQUFG+GhzAAgifJ2g0Vnj/+Vf/qXXaa2yBZuQtCH3CCEHxsnJYSJaIWxlWpgLG71nBuXR/kdLrdS6F42urpVXpbvM7j0q29Z0g6EQ07iUa0IrORz35NuxY4d+daTrbi4hqwc7JgC2Oc0W2IwFVjrMZtosNA8/zAMRBpIP86Ulv1yOsNBgNDpvX3O8NVeFiObDnJPfKtR8SEbENmVGS6qG0yKU6Bo07BpMl5t+pV+XS6ovwzdNNK+kfpU3CpAnWLPzAPVUWflSNQxNoEJpNaxUK08rXeDBSbISQgR9XpPSC5l61xEaFDrqwQyyEZrvQrLa7AA9vcZvm9Vug/99ba9AvEwm/JXaENAvO2vS0+5zn/ucXYUA3XD85goWg9FuCjapcTk90wswxUZtCE5OY4lHk1olebnpnM0W2JQFtuLqnitK4Qss4DzDyQeE8T10PGsaEkOJKAwkSES5rNylcst2Ry7AC0ZwYHnlhGPLb+WSy+TICYkNvTqplZTjWcG2Yjh1YdOw06TW0Y7UW+HqdAgnMAUC4lE+JQboB0C6Tn6vxwoDQVj8gBUUPv7xjzdw3x/w5qx3vPBPexyS61cV5anB5mjIKHAWz9rDRUvY6FYtmjLy1GZYYO0NAePCM3rBED0IpnAIBij9SkJht3jUIghcT8+h8EzMFlhtgS0XkzJBEJAt0AgQw+XEKfwwvHBZ1WqTVYJB4ntTx3MJZcR9AjHHx/w/5/f+OTaLXMKBqR/2iI+Ai1rStJISyJOV7LprtZImU92SkEBCqCFBCiGYNS/hUMbRuchOK+OFJgqTkKgUmMpcQeu0EvLHc8ixOLi0IwlGe2CQJpb0QRa7H/DUaZIIURPyU3KF2HHZoOKhJxPZvQXQQk4mhaoeS0ZkMxSPZCPVdqqluq6BqRLmTTG0HofCunAvyGTzbdu22YjoBQMlS4J2Am4GSdVp26HhTMwWWM8CWy4mzcHyFkbhPGh+ZTVqj49n8lWuNRwy71ptPmycbdRyci9Ocvi3v/3tiVVLCGjDI3HyUMxunaQKtH3v934vvGhHjxpktq5EhImr+01zAqtyKVF4uiCFGjTxar2dSt+sg0cSgXTAjJNumqwQPmSuKB+XtEKPUYNjRmO6Bz/4wdbgyrPJr//6rwvDjzvuOFDurXtQaJNUQ/L1jm0IjEgTVdMhE1V5N8IlBhKkWg3m2MrVIgZPnSrRVk4UlRSKdv3vQr+/cjClXFK4WrE6mvPZApuxwMppvZk2C82TM+c8cg4pF/X4RaO4xudBleBpjLGtOV48OTMM5Zlg1HvgFo+9qL/i08UkhAicHOjAa2CHGd7ZuyxaVJgz6zREWLNftVJV+EtdghuBm3CYTOXeq/dsEAJXqxUdQhmcA4nW7GXNQhLGqIFyP4Lavn27kHApAF7+9ScFPvCBD3gLSoB/ySWXeGPJUY+qgH6KcSu6oDDhS2PbiWtp6FIrdJdadXcMQZMEjibJHL0ksOaqsGmC8FxxyyC+T3A5dos/seguEzXnswU2aYEth6T5CbcBK1xLWvadr4I4DrUBIlzjb8qzoNp4VuRBA0CEXH4Z6X1J/L0UpUmvWyaBNCkkJUTSO6BJgrCxD6B4ld1Ji1oYURV6de8BSlXJh1NwTXjoeSDaNRCHNta89YhHdxJilFRYc10gyitZL9e1fiXSwKju9CKmVqIJnUWpfo9w61vfWqzXA8bHBJTXu5wd1hNOgRU64CdZk6Uul7uIZypEeYXEontCxFyufIgdgK7QI/O5z32ugNq+RA8bbBiMEbFrVdcbwly+lS2w5d7M9w65+y1W4jPezZZ4JlCwgwaPIKDY0Bmxcu4k781thFbTXLlWwi7YYTELpMShnJBYycaoEgnhUheqNJF6od3b4xX6ygkAEs/CYhKooXcoMO1rStMwiGkgdPDOuY+MwFCv08MFS2kvD+mu/UQEzfVLDQTJjV15iXCEqmpxRq/OU17vAk/fcKGnL10RCIAMU3c+F0KTH/7hHxalXnbZZR/+8If9xIhwnKQVRa4W2+jSVm0KEItIwylRczqkc8zDIEyqpFRb/ORL5FODDoavUBMnVK973evY3G2y8aIVnqyU8Pqa89kCm7HAwiDpcB6j4rrmuqlfIa/z45ZRojCUWXP8okjeoi1+nBANm69kkmBN6k1GTtWLotjIwamqH9twVD7ZcY3cp4vx6x38qSog4q4S59SkRI4SWLNc8zUBKDkUID/vFRaBQpio0Lc81JKma0mJy8IlhESs8o985CM+z+FIhxBxNFDzYSo/V1eLx6AywrKMJSFZw2XlwzhKVtBTU6vKvNSmA+HOzXz1ys+3jj76aFWYDVZOYcfihDMjGg9l7DAUkyqJbWgy7ZQOyqWhDKI02KYEzhWXK0pG7U4xS2McNOaG4+jvDW94Az0lt8koJFVNkiFkJmYLbGiBdVdbG7a8ihnMb35bpzwBLZdMfR7eYY6gcqyLB/MKPfnzKCETjROhrS0zy1VwBpgIJLkqtWieVqe2U6Gej+zZ6PROYgChClbqHTOtMEuEL+u4lNUpNqLACja5S/1CWAx6J1BMp8ryPD0xJ0Fh0qyaRcGWpSDYOtppj6U0HqKk0VHjqtPdyocNETqVyKQM4UYk+gY9njQPe9jDljtcKiTfWOw8dtCkIfsAI2ZUawgJidgtZfYh87AMtYl1SSvjspV88cUXt1vq9lHeWJpO+7D3WdQBb4GFQVJ3Yvhk3isq5LGiOW/5QDcOwHtBGzZ03hu4TPOqcIaViXVJGu+yOoYI1uyWfsFinQJrApOjCd/zITh9ScI0JXwyfrrlonKJzBIezSUKkFmMmXA5fISnVuUiTW9QgioLZLm2RmdTlSiK2ZC1glbuQNymqt6H/nrH7LKE1te0ZGfNBn91hENbeXZu7Mq95OT9IR1t37598CCgj9zrUD56IjxH2zvWtYMdrWruMsurvRoTUw+z0M1YwH2PByG28SoxGRr41ajn3PXCWWBhkJQDmPpBA3+AoUIz7y0edNBBfNV3KyARBu7aPUCveTOUE4WNzyAkMj/+8Y/7iSFabOIjb+eddx6A7sNF9SgfqOdsx4dHATEJYFQVx3NZ4IbQBVohQm2JMsu9LXUtqZKTWS0wQkNMu7SGZmnsX5BqggcDDN2xYwesVysYxAZDyScwCUrQ0/G61HBaskmaWJ1qbnSaIKiKAOgeMFAG4ugdqsqrogMN3YJDDjmkR5SDO6jaO54E1vWe6bNJtTfJlg5DEwT17CzbbvaQ8Lq+EbmDPZs3KXNmmy3AAmvDzX5oGu4NnlLMdOfGLvmts2OeYF2sEKCIL3jCcJXVA6kKQHAhEIAgBxYL9DC7FElZ4IMGr3kWFSrHDOx0AUECtf63MGkKSZAQEgDC7FJeCR6J5JKBSKp0SgFiceoIUBqOt80Vcmy7B8IlaojvyBSB2nNMyfQkLcUQmujCpVRf47LC3cqpxJKaABTGpB5VwSgjWwuzvO48aeR49E5tAem97nUv0ElVOw8KvfykFo/mxksfhPK9UWy3RrEms95ZfkWVEfmd6+tf/3qj8BzF4Pat4JkvZwvs2gIrZ9Wuua/G2uEAvNpE57EiIC4q/LESh3rpxlU4P49dT9WcWT4SD+dL+JXk+dbODk+86OPNpCEHG9qbUhbgRVsuFQad2qJ1HYbKJTqnNq0w1MUSju78iSoCJMHQRPlknxMbpx+wzPutd7nLXeyBPvKRjzS6tMVPIGkJDMsqIaGEU+3Oqz35ayBJaGg69fAQKW9b/uK9y4TqlwK6YyglnkD4EbY+xM5i0oasZErspW51vcf56J1KkkvDcac8otxrb5halHg2N5A97mVuuAUtsFcud1XaK0gy+3mvXcVAxGYi0BFCKqcMr8gHcpg183BHLnEh/LGRiVAIqYkFYTBahEusviRVYjQ7lXqBmP53EH6gsyRopyjSShgi6qJ8WoWhBJTVkk+agBSNwOmlfe9F6UvE560jsbBewjiDVS53WQkj0FPDkSP2OOmdfM0pwxp2UTw/4IsYuY50Sqvk9xKuT+cpVNIyvx1Sg1JIFDkICqfhHiu2lw2bJFMh9Bkq2eEVkHoMWBAY9ZRtpmcLbGiBhXkLykh4ppCNT3bYLRe+gRivLorawJBXBW1c8na5V140WZ0Totx7QjwcNqG95CR38i53IJ7/E+h3n9icj/NAhZhtp9pNcwn+dEEBOR6EX5f3uyYwBDiUBB8Y6CwhOC0hw3V7V0kcqmvlxNKWVtwYv5dDL7roIi9aff7zn4fgdmYN03ETxFcriCZHEwoj2IS0BDZkY9mzRAHyiZWTYFEP071g7/PPLo2id8L0yM6GCXrcAl9xzpjePbBhesQRR9CKJnL6Z9sa7plW+6RVJso+2Q1dMhYPNpEpbd1iqwED3CedzkK2iAUWBkmhBt+GaJzTvQE3BTt+fMnb4ZGIiTNbhEIZEdN6npCH8588nyiEwoAMASygFef33Xu40Ofc8XA2789DN69SKuSWOEEJAIWeukNrBUDlSmirVTxykuuID2vrUtdomhgUlPEYcCkpTx9eTQg1bF8ICe0D+EGU1zl9iN6plz0BsSq1PU4EyA2ENIRC0sIIlyMpJ1+ux4ASjU1OpbqmJNui6SwA9wa+ly7vdre7xanKMGlLQ0O2rmcivxeipF7o45UGO6TebG2wCoNRhJKhySDqut6zSVU0RNBNj3Xd0BS6pB6GBpiEakerIX/wEx5/VV2iFbqzGhqXQhvEhHuI2pXuNqllinSgzJqjmHY301vTAguDpLmKmxRGhFMKebV1GYwz3cVxyvkDr9jd20lUfiIvugTHYBFYeD0AMvIrvVjyQ1LletGXjgCKhCgFpkrogyGCcGnqhC55LJl8GGFQHgwDTJWIBJ0yOUyzRwkrxciSwNBvooTJgmKRIGWcR0nC1UJXDXWqa33pYoURlFA7zfVbwqNJVXKF6WxRb9vksMMO06nywI5YzbsLwMVLDiAebrKPS/pQWxPqreh6vcvQSk4HPIxgCLpLJTIzY4q51Du6IVTY5ZCvyhDAHyKa8KnlB2cE5m6c+66kG+3B7KHlJxIIsSqtiNLRLuSsEDtfbjULrBEm7J8myIuWvWMpfmxOI4Ca842//uu/FpzannPJN7DtwSg01Gp4i91S+6RW9PmYvoSBUAZDCqRMucKRlKBHHjG00rY0eEbDSvBTw6Bgpc9uwlMfM/ZREkfnTkU8NqQxOjy+PQr07Vfao9QLIQIrPA5PIBo5g1mtpHclPQzqy6XCatEkGDgkJaQj+J4xmCW0tngsBQTvBctagRuRsgUyxHe5+ZQ+eieTeUfDrKQ7RFV6V+tSX6lBEyVwUzJqzRHQEPzFqWFEbdErUv0qJFNOIP3RFh+Ss8fMQrIe5evJWSF2vtxqFlgkJM2j8g3z25yWm9xCJ5GLw2I3Lx6eoHx372VuST6A0NwlkBJwWVYnGZKSr19JyciXC76yB9rlqE2NLodKo3YwDyLJLqkBB71ddPbZZ9u507X/jNTnnQxQLeyg27Zt2zRhCiUQULKvaqENfLGBFTweMxJsbYxDDTIHVKnKaI5cLrjgAg8SJzAYMCtv4OggTHd+QSD8DLPgl30V/wLPj1bxDObR0S4II8XfbaVw99Rl5fT3tDAc8j0w1JYMB6F3wGdc2FJ12jsJ9YtzvfmQQbQlKn4ldorca/PKb95Ybyp8FwOZq7ayBRYGSbkWH87fzHu+kedzMwtwq+CQlCsql6/nObu42YTXMO8iFiR50zCBGmLQtZQauxCFZ7jxINbkX68WLOJfBsDv9Nl5+Cgg5dLOoPwQi4fTKh5E6CYClcSwGqqCBRIMMgqg4GVJNhEzkimXADGBUoqFUHJvgNFq+/IPmZgXTils7Di1cmnPwdOL2Y1Uodymqu1jilWSzM3k8ZOJ0G/6AE0htr0C/yDPWAyhOwvvjJcyb33rW40O4WFju8O7FuAPqmpOjuaxEUuHXU8GkgdPc4xYv8tQ/tKXvvSkk07K1ANqNzOomWerWWBhkLQbk7Pl2EoQ3mA3xbn09M4pmV5unubJoRLvPf/8833WaArQcCpRqSF3KS+FBdGVk7bicmiiPJ5pqykzwAKClIFNTsYFgBAQmJLp1XfA6nVaPANMayvHIKliBMtzOx46wgYTl+LVz3wGPHlI6NdwBFxyqIpTE5DhnUpg5Of8cpEmGB06Q67eGbDP4NMtj3jEIyCOhAFy+Syp34mR0NBGqw0J2moCtvSIGUFDGwW9q6BHhUXTHV45XsPjI9nK0cbl1Muv3TxCfKjfbgNUbQK00sdmsOmJXpF0PXA2NvrgUWhpTxP/W8XTNDX0O5hXyJkvt7gFFglJlxBieZY3+817q29LUd/7gAK5DU8OCnf3vpI55GvrGMdBhANcHlgVQi86demYSy1iJOWlStD45WRWQibiCqblP4NzdSFm2AfgIALvhQ5gxWrdpbX8xRdfzMPhjripgSdcX1O8yFYwjvNjkyzYdcpQeiQBMrIeAiek0FZfgEk0GowyJskDPrBJLm2hOtyD0UQRTpobwSAgDP/uJippEozSR/gsDhWE0geAinPJdwwVGwW87obfWRAatvqxme8VuPSQYCKGsh8ibKfYsEZt11RMFTmq9CKviTEqN3w/i3je855nvD3D8MxIuqYZ58KFObs3iccprVnubBcQiIycZR9++OFu5KiFpHsw3YEjL+Kx2nptU6grwrJJSnKHv+Qr5+fO7vWOeZosdSU+WcKQ92oroYMD8nXEUV0iqCqXxFYO7iVVcvx9rtQb79ATqioxXrWQxdudehGFeXPTepbCDVlH5Cec2njorJYyY6JXAiMEoYBVwCu4E3djFgayp3KvAXh9VXf6hZV6hFOAMmD1/6m8cenNJ8GsEorp1IcCSIOtbGI4416Mftcj6KxruVaW8166EtuiPUXIZ1I0/XXEDnLyidIprUbvJHhOeMNBE4G8p6AdWwjr8cDOhjDexFqthrZ4CCQZ3XDo79K48HvBi1YIawL6rJYwl8wWYIGFiUlzoSY9dECIFDj8iSeeyAP5gNQd5Qy7uLXYtJXnPEI2TivnqCIgvUCiSy+9FA0ayMGskDdqYg9BldN8CmiOU1XKwLLoyjEr19zl6K5LJaUBecLA+EVkeAAEIABk/ZIds61ASZiGFpBaktsTVOIASqFvmqShsI4cElzipLMSvQxNVMWgFl0VaXp0ngNDn/Oc5xCra4GqxwbD4tEEg0N8wASq7Id6w7Td2ARi0xcYxVYInD4aIuQSzpH0rkQTtXRLTxsO4NhwPBqVe7SoQpAZ27KYr8hJiEKExMhylxb4XmDyH569ePu4xz3OcLq53Uc6DGLoQ/6gRy8ZR5VxMYX77qlj+BgoyRSIWjGXyyFhJramBb4yh/bz8fMHuDAmvUuJzs61V2ueU60uV8IBgjY5L+XGcoHMUUcdtW3bNnjKpf2y3j8WRuPXY77HscVoEFYtISLHqkhwSQhmTiXnhJoEBHjUSnVKMbUlDSWjAOV2Hi1OBVbYwCg2De0tJI0mYE6opXds9klt4enRbqYY+XWve53QlZOTptXAJuYivFEQSDJpNJG7xJbOlAGFO3bs8CUkvSgnqqeIKooxDjnMIhcpe93qHve4Bx2AFOH4Le01DE10Sr40iC71GNEjJwbyleM0ClBFf0IIzLaph1OJZGhJaAhoI5IaGoI0VXKLfXI8lp7//Of7t3egkOZD2pCTtPVyPQ6I3L59O8meW0xUsFwr84Hy+qL5ivGuJ3YuP1AtsDBIGiLkMJzB9PU+kB//5FfcSdrMTdKWkFyOd3E8l8JbaGJzzSVM8Uo2mCCNzMHpUltbk5afIrhb3vKWFp4YclFepy0oDM6WtFlGK4VaJUSuUEkJpkhKxDhK4sGsI+Al7hMEKW9QlpZgFL+VOFCzqEewCYygueV2v3wnBD+v1pDkAaMpk6hyDdUq19yxPmgWnIIPtbU1Lpc6kmoCMR2/eB8rWL/ooos0Zyi/COpf4GGjQPdI3mXDTzF5iGMgasnXF8hjc//MivXaA20nQW1tEYMecnStCzkJ9YgZAd0w+20FyBNZ++0AbT0bdIdTFSLdMrXL9VKqEkgxc8PvDiD+vZffoGA9tcoNhBxqrCdkLt8iFliYfdLuB0eyV9XGHwIW8BnJtEbLuSLCFFe4Zm7LDAM2DDErEWdxGG9H2igU9B177LHCIptlOuJ1OsqBEeDMNiIwJdylRDEuaqcvrRAS15JrK7V3SaCEk+9JCJiLUGgdCpSJkhPOS61trUyd6ihpjw8EYLBotUmqoV9bCbX0gkf8aMX9vve9z46nQanVIzCiMx3Q8oEaKWZo1FYu/vXqqJW7Z5IutE0NY5EwUw+zhO6XVMcccwx+q3td++GAPUTWs0cJ39Gf/vSnDRmzfUw6kEYOTYYQA9S1Epu8GORnnHGG9bh7AcpVad59wUNtI2IByc2VNJFCeQThcpyIujBerdxN+5sEOrxi3kMOOUStsaSJXvDLd5GCXQORGMqcse3AzraMPVrIwWB0JLiJlNyFqLlqK1hgYZDUfOVjAxHMY3CjxGw217tVvGW4Clrh6pxX4MemFo2QnPnyQHGfj1R6t1ycxdP4PAeeOky9wwgAJJQLqWEBvONpXJoDk6kJaeGmKprLS6AKP80ltCQg1ZZ/oskXRqEFPhI50I0oEugjyAKXCv0KHpQbu5+xGgUGL1QKirV1KQSTG6MmqoxuoIYm1FCrRBL5ejuVEG/g650lM6CqRqq5pJACIkf/7UokLlhWa7D0sTKgkqM5iVmYy3MIyluqY/Yf8WwBd9cSSCtoiNYFCcYisLXzC5SJgke6o7AzvfgRjAM3l6B0+X8LauVSwkBhqQFSkuXlJJOjUwaByB5LNijYh7Y4jR2PhJDoM+hKRk4OIeaAu0wrd42SSjzMyLeBoGtGo49+MY+GM7E1LbAwSMpzeAJ4MnHdKrRJbDab3LlQXhGNwSV6dVJFCGmqtOUDfMl5NH9zsCC28iYAuAFhXBeDjiSEhphhpUhHZGqpy804GG+HDuAPOGpCOMcjnAIIYKEkGEW45H4VgiepKsIV+s0rgaJO/5KTu1KSbvgHxEAQ7046VIE7VppAEFjoVw4sLD8hLGbq0ZlwowgpKKN3WikhVncGS4JyG8RyheQwqSpEmhsUIDNqdhZv2iSF73QgnB0wi1Id+oFRyKJTGw7iYtu7DsRYCQOrglqbqjiNDs7qiFg5maDWOxKUJ5OF6SYxphJqay6uVLIEostJSUnbEjndwYZJ+QZCILsZBWlU1bUHABobO2uCU48MYizoNROGBpsFSBOWul8eEuAVNGuuSmLAocCaoubCA94CC4Ok7oTJataa+gjTOhhqriupfExoxHqJh/A3tcORwKKPGJEAVmAH4RxVjzj1iA2zJkqqEn9xS8giOA1JRUD0QYMh/FxLWynQRChH40GEnl3qiBz8PN86FIzacxRX4kyHYI5uSgTL3lUCDdiAlL1FS3sKa0tD+AUurUDpSQKFlac8ITQHQ9goA9EgsnjtuOOOc5k1dIcNgwS/XMoNH4PDLmgIIi3qlZBPFP2Fk+xgse8u6EgXmpCmVoBPN2+YSgBIObXtL9sE8PIDwnCgMIyDm6owEAUwtSUNYVySB0OXVaUVHgRNJN0xDpWklDcuBLH0UUh/0To12AQn42urnALkLDVbK1WLnw3xE4igiaeXB4DtCIPyPNOU/PpdS8xctlUssDAnTpzcPRk+w3+k1XcJW5xr1sZv6o+GMXMbYHq/+92Ps6lyWc5zMIxOYQdv5/ZCkm3btrnkXXYJhWN9Vg5CQQTlcomQ0TY5crXKEdTQET2Bhe1FbUGhoA8GYdCRWgpIEakNEWws6JcCr371q+1LADjMiXUwokdoiwGtHKELhRKxknU6OFPutfOsRHLEUBJbGioHTAJYhB4VqvLYACKgXCxvMwTeJZxlAhfMiVoGw6/3AHAp6Yg0vYNmP8SkszBWK+9+qspi2HTBwg2KQNKWzXnF10PqC5tyrab21JCt3A66oTHIHTcBbkazs0ysJgo3TMweJzUw60UJQr9embC5LKL3k5AVd3lDsTPDgWqBhYlJN/nYx7aEGTtRY83bZvbzB+FbsYbFpvjF6s/5CU8riCvY4T95Y3IUIkYgY9MQ6vlQkyArT9OvkJNwl/xQTDRyvbgkXJWwSE4BnGRCQ7hmV+HHf/zHydSFcpyq5BI9lehXDnr8Lp5X+9id7QUrTa8TkKxrtYZPJcNx0kIra21CVGFovEDQiwqQUTRqaFl1mAtBMfahtqpiNwGd36rSTaeZLn0uueQSg/VR50ykC7WJorNRkAA3EXRAk3n55Zfr2qsCO3bs8NAi0FMEzmJDYwNbboHwUxfGC4jlxE6T7lzqqKHVVzqgEYyJoA9U1bvcoDzw7EKQpqMM1egwrJd0IVVbX2gltLWbwYwIt0OJoUl4dCTHpoqS60meyw88CywMku4r05vxpn4IIifW1Od+AEgsUzknjOCQw5dWKMBn8NgsE0jyfCEPXwWRQ4iOgBe21vIAwqVtUCXkK0QLx5xg2OL0G3Z7ozYH9MLzMfDzepdLysvVggNAabdUtCX4gpieAdAhlzYcfUEoMCdu4uqqeDWBVAJ/DpqOPPJIPDilxoVnuZ8rkEgXtFWrO//gXiBsCU9meKFQLTS8+c1vTnmSmYIEtXLKN3a7FnZLCQErAmFybBGg7T/AevpDVYtlogAo5cnRr5y28E5CsG2XypfUXb5Z7pce5bqjv7aIUk+pdJArlLM5nf2wwrYMfm0b9R7kHkWEEGtoHmO08kggn+YKqUSm4VMegTliDzqamyyWBbYikvJGNwlw8DFOKHHXMeN5hRLOJiHWu52E5K5QwI9KAxRH1d7bt93Jn7XN+UNAgZKUVzuvhzIuCYGhcA0U6o63S7wxt0xJckiY5g6RHXnDNWCEX3CkxPbC4EFYFEugij7AFFrBI0Bme9RJPW0Jx6bTBpiSSlxSHrOcerYynWJZxjqLU8s41FNuXW8/5F73uhdASYJaSS2ZpLEnsGYQnXojAvSL+g899FC4b3PZnoB9RkI8itgBM1tJBo4uRwwMJTYGhF4oUKf6CkAR8It5qY2gQGgem3IPLe/n2kbAXyt5tbuVa54mDO5ZyJ5kZgQRt4lEmpuCB2eXuyV/Zl5QC6yx1bigI9mk2vxwcOaElcAIvqeK93LXwbMewTMhhVpBFjB1FAOwbFDCNZEIsBCiqsIQIuiLg7lsAQt8fZdEoGdtWxeY9Ws96xKznGJpOEpcUo+qUAlCeU2SBGGpE3CREeis71XA/AAAQABJREFUFzzaAimg6VDI552szemGFvkKorFhkOp69FVHNEQQAgdFteCPcMaRhmUMk+YC+R48iRq5tgJPu5OiNojT5i9zaWU7GEFt+uvIBoUh40+TJZ2W4VhHUgNJPeWDZ00ihtG8yzFSyhtODfcmp7M75Z6SbFwCfJstdpyNokL94lHLMkr2pq+57QJZYGPIWKDBbEbVHCxOvoow6cMIVaVR6DKe1ZI5eV7KhXIbJQ5/JNLAqHiwE2ptCeFX2AQvMNeimLMlU7laHYWhFeJUmPwVOQZNYKhoETA5Vtq2bZudVpC3ffv2RJGWHNiqVvD4kpe8xEH/Yx7zGBAGUKhBSTyDc9B17ZIoWwEuvbSf2DRRpTuH10BZuUvlHhKNyFPEgRIGcTf89S6EviRsVvdgVHQMmu2BaKI5NYRv8gGaOEtDN0R0udrlgqXHTEQl5amaBAwIwiPKq9rjnBAjdQt0jfb70TPPPNNGh4eW8p6dbmW1us5Ee9zd3HBRLLAVkTSPMtfHTcohpyWqpj45OAch3OBOUm6DGS23VgUKNhalmHWnqvAkHuVwhJvpMQCKs0LNpZRcnePUIwaLSphl789uqR/OCktFiOLNRBFOGk5galV72mmnwTWIplCeWJwIOnTZ8A0hxQCuV0HtPBRq4VEFKAmE4De+8Y0boBFRxig8OcShYjS0vhxwU0wTvRAlItYWgGIWyslb11MGthKuJM3TqjzFolOvXMkKtZWUNCmNy3ZUFNJnJ9cVjxCFQ+Co2pDQqrvJUAZ7z3veszM0YMrgDEKCXFX0hgL3hoEyazbfg3GtKWcu3KQFthySsstwwmGjHN6leSlhkEbtmkQIwv+5k8S1wgK+tEIOUWrtBoSAScOcN+ZseOhQyehOYcpMc33VkWj0bW97G4Tyco9IE4qhQRVsCs0bFGgTDD7lKU+BI34176Af+GLg5zqqixTuUq4LWll9g0tbEI1UTqAkrrSfINQ1HEG33CtNMF04pnfBstdI6WC8mLWSBMVOnATj0JYaOgXoOoKw0N+mZ2rod8n6ywk9LhFEybGVsLgcqnY5zWuLWUPyVaGpF6HhHidyWK+7TLjHg3GxAwvY7nAv3J02anTRndrjvjbZkEqNa5pvsu3Mtq8ssBWRlO1MPnneiO6y8qmzQYH1nGGUcydJWw2l6ARWokpqU1UJmfKaDwKDcq1qDmgwqFW+IsdAbblI07mTdTQY1VZA9PKXv1ygB+MgMlGEgDOnPQJVew4KlUA9+CWGXdJpZ9JFvVSga9Bgi9O5UICrPIUhCLwmCgICFL+qItAlEAEocnRCjFdHIBUPJIXgJEDS448/nsxGqlNIZCNVWFrDZUW+MmojlQgsN+oMmxEqX2ZZyhhBTkIlCMzxpxJlVDWQSuKJ3mTOhoygL/ykidARbG6wNlhUeZDotOFgHgbZpPw9YDMKrVbk04Hvgcy5ye5aYMud3U8NZLZJK85wlQyecUY8SnZBkDNt2+VSB8uphgqhgJ1BZSv4C81iw6N2zcR71fJkiMBLea+De8l7RV57cs7uVUdHxuQ7Aff2uyN+m5WYoaqXVaGYNbu3C8SbYYpeMMMyMjWpUw1t5gp7lSinm/J0u/DCC70bawiWtN6lhdGHHXYYsIZTGPQSdtBTX34k6qujXuSEL695zWu8ouCszPG6IVASsDpxsvr2+pdcX1SCCGpBkk4Lrh3E612Oh56t1l1SDK0jhcYOtpQ4QCfHqxHdO68KKFHldQXxMt0oj4GqWbJB6XQMEE0sm1S1Ileu626QHF3y/TCD9QQSdEtaiUxZIGn6wraezBVd7PqSwJ19Lv01NEbzFkGTim27gw2BARuXnA7sQ2f3SEOEklRq+EQpNARzZqoDIYTjGaYjYTpdY8bQnZ223Tr0lkbSBb3NnMTMpjxP8OMoE50bcxLYBzh8QMRL+zzEKROEAqP4zXLwoVCJMEoAhRO/VmCIVxDIIR0TATgQySXgju1OvWDgYCTAX6f/b3zjG4nyTiVs8pFpPBhABlH8kxMiODY5XsByZuXAypqXTNsCj3rUo+iQtMJbi2Kf6QN8ngTUkDAYF6/WNSEgYAkwlt+opUM0QqdBA1UlSBqYEm5Dw1iMCIYqhNd6cbzu5QrlwWgARIJeEp5kOQUk5WumqT7Y4k8CANURa3uYUYkpyG9E5QQ2hNBtTfkbFqbAYDMivXhCGKl3k1/4whe+4AUvsJFtgNQofGYKl7TFTAHMrEQCrFdFYW0bMqv2UrNa95HaZpeb6xKMkqZ3eztF92miOQXQJCR26LaliBlJF+x2m7KcoXnPMTiDONSLpUGY9bXfI5noYlXBheW590A5gynOZ4QSvMJqmhDvaV166aWizhAHZoFRWMDl/AhVCCl+DHQ0h6F2Eizk/UrSsv0XfuEXgLWoNoAoCOWNhLCmQBXO9m4WZ/bbLT9b8K9Ajz32WF5Kc/rzPSoh7Mbq1KVyXVPMQIjl0kTRDQ9fRcvRASha0gpgKdFQ7xLnj6a5ZwxA99gIT4kSktMQP1rv6aDHaQpPlaw3LehA1QAUjS0YVQhoXHpJlg7T/d8hDb9Ud+vJ37CchCmP3hmBNX5lOXmmenQZo+9/2zq3zd2PEQxWQ6NmJcMnwctbNl5MFTfURKI/s2NQZctFzoYmjEG5VGvm0Nyt9FD0LjBLYlaSNfZyUNMRLSg9I+mC3Tj+YMantKks2YXkJwIuK2XLfND5cz/3c2DOl1DMeG4AmzgPnlzIpOddjl+cAlnpAzI0gTyHB77zne8EBP03TW0/9rGP+baLbznrNP/xSWlbtKTxXr1zNg01KXKB7ATKX/va1wpaITU/B+vc1T9q5fbkcGauiyaQzl5u9wAgxNDIxJBjY9CLXLlcVYmSyuU6JZw0BLiXoz0POL8c+gNuQwCv/jkrQNdRvwUoKAMBq1epjFNfatecGRiUjxxRUkhPAOpBYpnPwuygis6q5HTu3q0nec3uVheSScNRTibT+RcP3hp+1rOe9TM/8zN+IgxM3dOzzz6beRnT1rZHGuyzM2PIwn/b6zitGEwbqwprAqYzkSQPYC87Q0z/KMxccuO8byfMx+bnFVp5mppmnrXuflMxMKWVRL2h25YiZiRdsNsNQUxfbmn6ynkF3xB9CCG5LjThFW9+85tFqX6AxCu4GeDgb0AHbaKDOd7Fr/DwMc7DYXJ7KCCk9b6nS0J6N4DPiG05p/UdJPJ11KCZI1GATP4TRsOv1oY2FkCJ/QEhj8U1LAbNdUoTFtfWQLQSIPNnEjRUSL3wMdBxiZAgkVwTCTLKQWdJ4CkBAuOSK8TJCLom1scAQcDjH/94WxnAxbaGQEwXupYMxBCwUSnI06Pksnzzk0O/TIG/XtqwdnfcJqJSfkBPt2/zwqecRE0vKa9rSOrx5r+t6Mulcbn08VlLAbQ3Xg3Zg+2cc86x9geONHnlK19p5niRw7vPz3zmM1/2spfhgZ4+vA0oGfOEE06wcPFdBXJ++qd/WkcCfFPCwxXUOufUFxsaIJUa5u4abTqQRadnJF2wO2hCh6FmLec3lQGl0BJyCfHs09nK9IsmMQWQAiIQs4mOkwMbLYdHhCbwV9gCTwEZadbvmmNwcA+VfBxr+/btPFAtfm5JiLdBi0SIyn/gmhIAJxjkgd61Ip8Och4LkTmz3QC1NJEI0bYm1KYh3KEGaTh7TmCImVbkEyW1eC93qZxWkhLPCUmhy6pcMoKx2Cm25lVOuB1bKtnWAKlgTi+0ahS601dDY+TdBQWDIkGnYnODsg3txwti7UJ1XSQToVMa1hF6dxPFiBqtyLGR4p+sWMi7WewP7wzWMN1NmycCVZrARJvUlHnFK17hHWEvbwhL3Vmvx7GAFzzsgQBiuOmbgabT05/+dAxuiuNEQa5g1lRhSesYZjz99NPlkl6M2nBoJQ2ttiCxRd+CWtw7be5SvhksykMLvhyIC0sVwkQeZU0tMIFo/AHKSLwFZw7W2jY5Srjfjh077Jp5gwrqkQZ/vbQkiBO8DEPxK+gDRpUQKOdCcIEcOVoJjxJgWgked9xxdOO3LqHkKaec4lItZnkSKkHzarGzaKj9ODBEQ2yGUBOcUgrjD4xc5sN4pOVRLr0FhVAFZMXCWvmPeAaoFZkGiwCmAM6ZjNdsrW2N1JCpmm4NCtseJCEeFCOhd9FY1U0hvxiQMtRLbEPYgy40ydTTtoYmibWJtZhgAZfYBI9upTHSx3JBE4TbisCjxJBNDO9dbNu2DTOzUNWK5NRTT3UTiYLLRBkUyfad8XumOjk0UszpoEqK3hvrJWFx87U3gxZ3PFtBcw5p7uZRXALtVSRxh7Wb3x1Zhpv9Vta8AljkLdgkbqMqf9ZccinK4DOW7VZzvP3nf/7nQQ+40ZzPZE8exdPU6ogcDdHaLvnQzsWdEstA/vzQhz40f9ZR/yxPv2ppMu6OKnLgV+jmHQAPA5cg1cIchiqX8MhdlqIpI4HdaeLbJbrZIaW5tvZGg36FaulAAaJAg9dvITg5YnD7G2rxGMvQcHcJ3RErJ8TobJjq2t4CTfSSNF1k/L3paHVb+wk2gj05LMwboI6EmYbmnqrtKct6NGFhd5wa7q87SGHMUJUFXApvzQSzyL1gqNBWk+yP0NB9dH8ZX6FLiUppRdTu2u2A4d/Sq3uToHtvYYJoeaLQ3a3ECrel6Hr3e2keTZY26KaUCWqp2Ep8tNVFK1A89WVyo7FJlUyZpyXa4iGTZIigYbtygi8OwA0svW13Oh83vzFYMlu92vTkSHwGs7EQTkIjsulpKwD2OU/QxH6ZYJAEQQci0IGAuqOG/VMntl4LJdNyUqIPzdXyK4Mi07GSE4k++E9VWsFxoEYlEjihxaBW4Sl+QujTKHQnMuKcngdkUl6VMbInYsnEk6jTECrEoOvW/nLCyTQWoE/s0UcfDSspqa3yRk2xFEDY/QDf7OCAyD6AsUvKG44uCNQXCegup3ekwpHjVNvd1J2kBJ7+2q/9mp0NwEQsrSgT22i4uwStpk1IYwddwHF7ndCTGc866yyLDItxB0Rsa6ViQ8PrcTZtjNTdtGb33wrsX0NVuGkvAr+bZTJYl9h19Y+4LfNJ82jUys0l1pmhLWkHd6DZEZZ51Q6JW2bgezmu6aAWkd6iSGo6duObAe5c/ilvplae+615X3ksCdySzyBMJm3xcxVtl13pih9BciFz3YSOX5P66pLr6rFcW1VoEuQYRtcu0XJyzGkYh1mnAMuJgaUxAr/VHOhUngKYoZvzAdrytArx8w0vzNsF4+pOk0SagkfbrGjvJGKjEh302Fhas3M/IKsXiSbxyAEEUIabPudhLZlWdgOs6w8++GCAxSxglA70GSNaQaiyE2ftyeE1pIOGJBuskTZ8fRFOjthKzqvZlj0lPDjFUDwfrPgHqJ4NDSSTognJsCMn0KgdsFi62hwkxDsPOqWbW2aYODXXqaouV6g9Lslfssvy5rVWyqGnR4JxeZ75SII4jnpu0zDdaLtbhC6yRq3QzEtn/5DGk4OqLG/p7WuNPlhDK09Zh4RutK6tVHwPQWJqt9Jiv/+7BT01tK7HbOfU45BwL405k2Rh951kApm0SNYOEglGxPL0wawjOSs19nTbUvn/uStbZOT8x0int9wMMCfG8DFI05JRtYIw/2JLApka4pErqdAMVmKOcq0hExCoVW5qDplLve6EMIVTDZUrkU8lEMg5xZWqrCVtzBFrj5JWqhRarDmiBWcW7No6grCSDYZsAvAuXbT6c2RhJcgDBSk+hqI5f6MbgXgsG2G0EjSIMSLSGjt9wKhtR9Lo4JJ8QK9fA6QMCS41RKQVxdZMJGtiRM5JvIxlXYzfqQhmIaccZJBPFDpQpgaxgiytKhQ4ezAAdJeq8A9OJasTaQaiHAR7YAQ3xtKzinwMhGBoyN3N1XLiVJ60OnXJGo7OPcb8NICojJMNVwvZTImOpmyZl0D9ul9qpXEX2FOVsaj1pAGdahUOmyi3LeAwikznSHZ1EQolrdDdNU1o7nZkfPLJiZ4qgy1bTQu3CL3lYtJmgAcpwvO8JO7wUFXSA7ac9yKiV88GE1Qh345H84IXz3Al+bz5amKZXiQLT8w8rVwiVIkjJJfk1ItcVQIpVmFdu0TI+QNR5MjNcsdKuvbCEy/StXjEMhldL3hEeaJFWGPhJtyjD2D1TpLVmaUZb3GOZE9NiCHiIFygAbwsxuGj1a7TGLhGrKjEwl+PdDA6gzUE2lotiogFOGTqVK0uOKf1oBWiiA8zNp4Jg9KqEa3IVWnIIHrx3hUopJsdiX6C6TmEIQsLpelsKSqQ1JGzEVrBPi+6emFA2GUI3Updd1tZBk03mqzI3WUDwWakBx10EEOJiEW7sE+5RE9q6JoEGq5Qe1ySQ4jLcdcQ1DAizyo2hEQGRSBTKBwNd5cg1hBGq0RRjHp0MMCGrFyPypkdoYkRpZ4NDYXUUIvfj9bcX6q6FJYqZ2priJ5kGKrShGSXkudWD0VVJDdSRBYYum0p4v/cla0w8jzWSE2CaDPDJVoJIueJoSr06gR0mprNvBXRlqlWAgHmMeDDYC5yV3EfVydZQz0qJFzvkqlc72ky7b0SOR4NseEHkSAPfIg1CDH1RYK+JAJTtMWsRJwlvsP8uMc9ThUFijJopWvlwkn7aM58OLwqDZUjvDmouWAWJurRdmeOh0EruQTFvKcNxXxZroAFpwSaCXH0RKt6UYhBPsy7wqRMhFPhNHgHlGJkyQ9P0UEDOYTQENR6HhiUnT7Ku0xDoupXrkd2WK9TDEZBLIbBoyObwm6fZ0O/QcDQvaabp5RWm0wMRSU5C1900UWC5W3btpFG1U1KWM3WTBjlVBrz0BDUGnKW1G9sdEB0c40XjXPcxKEPu0mZd8iPIBO/RBThhOjIJSLh0Qlf0XaLXG45JB3ObCpIzYPpzVbocpNzwgQi0OTTBM3nLRIBkC18tFmu3KzVC7Zo895mnLDLZhNHVVjS75iOCE2mOqRVCpvuqni7oxVn0HCEBFUKd+zYoV/uqmuBhkMDASYG5wmO40EDNgl/Ayfh7LPP9hahUA52gHhVIj6Ai+D8hiMqfNKTnqS5kmnSEbE4dachh2QHOoi/bMJaMBqjvnTEh1UNy0+FDNqQG1fGjA4aVGkrJ23QrWQJx49tyMFDBzkJ9R6sYBhVMZQPJEKEkgaCWUjuXIVwRmtRPDhHX6sJXUjZtlqXdKah0xtPNaG9+75rU6wWu6KEzFFiFJlXCc11JDXZlNBk6FOnLjUxlqoQ7h2akPGQyMjGbiaoHeYiUHOGVYVHuZQmKy4r3FL5uquVA9UK494bYDPeJDCfTJHgA8OY6E27NU1hPik3a80qhOiM48EdL5GQoNy85IE9wK169GWRqJUkwnIQ5CBCj748bwEuUNWqyU3aVMnVvZMAJQEZ+IOSuY0eG8L555/v5RsnCYDs4Q9/OOE05CdCSxDPMXAmk5yW8Nb1SgRKmYJWCG4pIBWgib7p4xKDvijpUrnBamVvVLmu5XhY0m6skworWfINX1V2RqweyyjBQ4JLXeAc1ohYbRkajoFopS+cSipHd1+Sb/jhaTpMcw+2huZ+EaJ5arCtWNuRtzDfhoknFk61jWWoPSWInUqmQ7XpaWPatrXHjCB6hfJTIRvSdTFl+9Vf/VXTSXeG6XMw1ijNZPfCoJQj0kG/mhvCsE+DbYEV26h1I5KjSXOsybwaRpNJJcQY9VTDrUBvuX3S7rRbbla1rcORrJGhm50mm3RVYbP7Y3dsvZlhwkl4hHLeC3FiYzbbqDI1bS/aN7QLOVy9jS2SlcAyp9J2ME0v/HzVLp4Zj79O5VLbT2MKJkGuylLRpqfQxvmAUVCbniJH5+9iTLuHYl7nG17xcSqtFg+VuLEdQE10Ryw5DvHFsM7rsfElW2MKKcaj5NwGYe0PlBmH5oVmBEIca387ldbvOPXOFGyoFyBu5xSC52/K22HkinbuaKKLMagpgaFtviWz7ny9oRvEyJKGUhZwqYo9NVmuvKK2cr1IaMwxUL7LISE5cuUK9S43NJxowo3RTfRIoIwReUBmganOU5oyJJTqy8AVEpVAhe47mzMRVJ223S064aOJIZhR7qx1gBvkxVLPMLeY2mN32K10SRkDpI+fM5nntPJopzBRbqLxyjEoySw6krBp6N6RYD5ojoF5XWqoqruG06UhJ3Cot3WIxUNSt9YNc0dX3yT3tTu6umpaYtI0pRRqIreT6HcdDhzECyRgMHU84eVqYaVJhoC53AmBgRqEgCeY4r08c8vpNjDCIJlPktmGhxBTmVi5qjREYCYWIcAUG+K36+eSZKNzroJfXySr0opjqHX47p0VO26CJrXCTK80CW9FxPQXOolK7C148wmmUyA9idKXaJE0VQRqe+6554qztm3bhg3DGB2F0RggCOHW6ZzTGP0nKG2NyDYo44hGe8OJ4/ExuANMvdbqJSQApHnOVl/dr2hVqxMG/BlHrcs1mQcDHjS2SuQRyjWsXMlgiH/NHP+KrrEpMShDa8XgEWWrBAaJ9OvIHGAE1nDJqgCl8WprUpEpISQ87IlgbVPXktnTjiXdUDy6GM+Dbj0Ju0jkTGt17S54bnmFw9sXtsgJ1CMe52YmjKegjSC33uxSZWkCys3bzhu9pyECMCjKm0K+IWtQbjdNrDlAreZufTeawiatqmGuFRY2nKluW4peGCQ1Y9yY7hz3QCvppnbDmmHmitRc38WN7JZrgjAtvBBufpNmV9FcIUFhDoABUY9oSSslktnpzMeBuHmslSo91tAMhkdoaRBdyqmXhsvylv6jsjkqrOBgVusQEHaL74I2PLk0mbAMcHtB2huCfNtCXnyqLZT0Gjb84h4U89J1v1TRteb6ItCZjFFARh4lJOlE3qG/ADZEIJ9JBafhO/fWl1ZiWyiJINYmhpytFLZKxaYhINbWA0lHnJk1dmH8hahik/EoYlWDdZvME6jkdov+2Jl5IWPPXbfVxAA9rM3OaET3t/EqdMmM+L27SqYtHc0ZkBwNzTqW3NA4xOIcbPpFuyl2IYS63iolkDSTR6zaS756MVc9gK0VQOfP/uzPmmnerDDHfvEXfxGP3VvlPt9laD7Hl1ZeR7WHblECYantkyUmhr4c/UcMHWaCBRZmn7QZE542QZVUaBgm7vR2rricVmnbJVdBm7tJ8xqQJbYH+3HHHTf41YJX00uJrnFyg6a72NABAhjVCnDo0fwLg1JMXl9DycSSM9LQ06TXEQfTl2ktvMWsOU7NdeFSHEE9W1c4TXfRKFfs2IrPE5V6aEt4v3LJlzQExCSQ49QI8tpPAHZy510AApsm2Ig1NHhhjJCRfMDhFXdVkr60es5zniNUecYzniEoZkDaYlard+qBab4KZRK13G5Rs+6ycUkM65K5mFGc7uzIMkJI7q51l5nCOFlDQnTXIrRlDfYkpxthVxpBCGmCQbXjFmzGWORM2TR3C7oL6UylCE81/T72sY89/PDDYaLP7lHer57EmJ6FJoOcNF9FsOfu8yXeF/bIhJsKzR+LFSGqTQNC3NOGo7tm41SHmWaBhYlJu1vmpeSx3OwxZYUGqpRsMmEmQe7RDfi0IoRjCBkc/nhucw9HDSaT9TWIVI6nmZoOZqoZ6WV1D2czD/TgAaMmHEeSEyuXxBrT3CVtNZcaBckIfqiJVy/1AkwFFCK+BKqlCTYnPCJQkSaBmivhHsIKmItHEpni1EoX0FbISY71vsuCVj5APSMyWAmzI3uX3El8ZGj4eSDJqjSxFcvhdbGs79IT10KPc3IqyEuUVgauNme2rtfWrmu2VZ65FjRnUuNiCncN0UpZLO82+cGS7U4/BjNGs4UR2M10aioar7byzJicDGtzwCWB7ospZLvAOhowmQBuK34G3NBceFbEpAS6id4s9jaogIAmROnLpCLc7jmgdL/0ImilvOjSO6R6NwoTxicHXXpAqjVSmzneOPaEtuo3ATw+LdQMrccDggIWLhTeUNUtxXBFgLb/j9nkSEmE5Ebmz12uzjc/IviluUnDK8xFS1oxgsKevcMres7LTSMHNXh0odYM0xwzSDVfS3STFE7TKNzJtfTXQGxCQTEHOCTgN8UttZKvi4BedGBm21yzPWpyezWHE+qXMnTIFelGYKDPK8ClQTUQRNCmleeEHVLRaIEGLFZLFPUw65ccuaDbmTXh+YzloTBWtCuo4W9eI7XSH0YWwFKbVql9ALgZsxu7hGAQ9wXNPmhLXeGb0NKipFd62YHl2dDw5ZkFoZDZWaNCDTVnPaIc1iHsFWB2393Bbtww6XrEkD8YyHEH9UXVunPp/3K7RyJNqxC//jSx4alReBaaAJYaNLe97oRKQ3tKVLLs8B1Ss8uUUNjDo2mj01L3l8Kj95nIAgsTk3oO98T2FEVIpovtIYGh4FHuHiPKVaF3nfAQ5WEOwixjPcM9e33I0hJVYR+yNbnNJLlHsakmmfHCRu9RmnYsSIInv2nHQ4QG0Qgl0rjkSwrxSASSU6grN1/NS0hKMkIrvYtAfRvUoGyevupVr3LCIxp69KMf3buNpDW/2UQXWYMyghECWQCD43hBJWnCnxhqgsHyzaaEsFd4UhU5BoJBCpTBoq7vf//7e7rggapCG5uz8FcYqyHduCjLeAwYGqDnftu3b+fMIzRbaB8DHKxhCJmosTCOSwNkRtvNfplu51r4z/LuqXvk/mLA1oQhRHRfYXMJW6sHt4bprPFZ3j1qIuHB4HasTsSmA0LtsK2Z47Vf/zDGtDSlPfCs4q3l3WX7D6aHp76Pk3rswX1tITjdbKlb41tVPPGJT6SeDVM7pz62Al7NbQP3aPS5bi+EuNcSCDbkdEA0lqHDTLDAwizBAITbuaTx8vz2aPU8txgXWJl8kiq5udt9HUSXIx8ewudFYWI6M8nj2gcaEBI48F1boZ81ssvBnwQYZ0ZGqwUitEIooRj3kFzCMrmSZdWWsvTRqaTKXJSjEWIKgAVG0QQiHIL712Z2slTxUhDmN0hhN5kKE5hMuU7HADUnlgICDY4qrlRFvkIED+FUQhKEBR1kJBCzKp0ypubke5cATABH5WJbYEGa1C3QnR81KeSuRoHHjyyhPEIVfSRiXS5uoj+LdfuMAjgqafjhiCrmAkweM6aiiWHv2F6qsY9RkyB1ieimd7MItD7QRHP2R2BjeTQJ3d9pPmRO5SskjRqeiw4PdQFMFQoILM8t0klw6x1OQlVnkm49WokTfAGyOa87TdCiVzGsWlPCDimtnv3sZ1MYQ7dyaSTL84r8bvpQaSZY4P883/Z/i5hq7m4TGkzYDj/xxBNNF4ny5Sum2upBmRk4m7WQtDMchWGQKe6pbtL4slEdkeBSuUtPbEGiN2NMJg98uYZmsyq4TKxcofnXFFRSolWJKEOQKIBWyAl14VLsIK4hUCyglY9xqNUv2AKjmiQKA34OkHqGoFOFODHoF0El/A4TfCBdfO1SX3i8xe2Z5FTNBgIwtZojKv6UoTzodIzL8TgVeGVnzy1NNCc/tQlEcDxLfvuntpg9ezCkFbVxkry4yRBSvrEYDqLERKqY13glhUzhMJA9QZKbZSKplRBxal4rzEMUQhIGatj3YtxHDGsaDecon/IUYWR291EVId1QtLkBNxGaNwGsb0xgkOps8OlPfzr96WnOJNxAEFStC3kMSlIAM4bBP1SaiYVZ3btVJg3wErWZN3IIiLDA90A2XVSZQ3LJDLZyWTNZnEIBPGCoANAsWW609HIyWqHZA8sQ8MuKxnyyIiMcrNhhBHY4XZpbFkrYakUI4WjzTBfJIUpJ68FWfAZCpnJrNJJbkstdaqIjC2e6WV9DWHhHlBWcsyb7D447NDHd6xdhVahTMknQL7G8CKELuwSqHIz4KhpOJdbjokhBt50y1rMStLNBJjaWpL+FG3+zU4zZ9gKDO4sgUPCilmI01CP5LIC2PqWnAMfGiBf4tW1oeBhHQ3rSTXmjltPfSLGh10wxDJ7V/KOEWOPS0S6k6QJ/+qzZ3XqFZJaGfJfRdJNa7SJIQBs+s3iTVzSH09o/K7kjFNBQQkj40XL6a+ipbIug9UHm1VzVcoullf7gR5RSrJyF8Zhjcn11iTAfMLtrSWABXcu18sax7VExrDmsEGf2wTntVPO6IA1R12gpes6nFlgkJHXXu4tuvBlj9phwJq5y0zEsMBWaENNBTukmlpJBVEsmgTm/TUbS2iQCx6rMNrW2U51Qm/1gSEfmPTU4jPkqB3nykhK1EoU1xKYVWqtmZ3rqekxlhOFICLXQDXKRCc5AITDlbyJiYaBjX1EMLCOTMxBos1UXWhHoUjlCla5BJ/0tJPHbtXBmJUSlpFpKXnLJJVaCPVSMXS86hbCg1kJVnCWo8etDaqSwIRCennoU2njf0HYqNjfChhqxFNYvNTAYC5nZlkq6yAjoacKmX5L1kn3cHSlajhnPtMRwFLJwtVRS4jlEPjY94pfwkClNu9sbmvA1mysXkIribXTYajdVmMKTBtazBk3oQFUEgyihqktDZiXPZk9KSUwQm3L3FI0wFj02zNVdr6dPdh615JTMbfGBjVETm0wMlMloq4Xvot81mbdy4SIhaZPM3TK33HuECeGfVfDhXkpvppoxIxpafWu1NXtGqklTinzzWy6RbN1q3oMeAjmqXPwFemAcDyFByTJaLmVQiedEmJqgSs5JJKpKZOKXNESnGPkl8pWjEarQ3lABcNbUAN27Vk5U/XYI6pGMDZjaprSbaSUOVbVSbhR1NEZNB1GnNz29HOMASkxqv8yr+PjpQCw5EtzUFlzS39v1TOq3Us4xSLYGNIQ6RegaJ8zCLPczWZGyX6baqiPf7oRnjPUBM0IQ/JilCJ0i6KZkaIggkzK6RmuYbkpic6mcQeQKMaMlohhToY485wzcYKWl/paDx8GMwLavkq7XFKUXSxbTwD0SoXuAtUUDuRgqAzoCZTdKek7TH0GUWjdCuWkMPb1h2qNIrWmMAadJu94o1tOHEbRVy4DTlFmYDgO7qcKmcNdy8Mxp1xZYJCRtcuTMZoBLk4wPW8CaqcJShVxRoSkiX2/kGo6EB11uMvFn8skpFAU9UEbIYOaZ8WDL8s3WgSZcVyF+M17vpf/P3r3sSHZUCx/XEd+D2MA7ICEPyuUpI4YgwBhs7hiBjcHiIm4WFwuBuIMNGJAQggdAqGm1jMU7nAHywLzDmZzB98v8Vy/2ycwqZ3VVV3faFYPotSNWrFusWLEi9s5qj/hqCeCgSpiGgNXoKzjiAsDUOhEOPCo5t3YEvQfzX2hYY8I3IpIdOYuEwlr1U5beb1hsrilJ5VWsz6S8lLeeZT0oJAaDiKSWqLDrGyZJE1J6W960M4pUAO3WsEjKktY8mt3PEpu0hgCIamAaMQ7LOydiJHrazGTNIogNQPhIXxoZpRhOEpjaUVsWFsgIupiUAcMxFi+PhmupMSB87egYsmJwmwXZhvi0syq0ab8IgPXO4XJqRsBFlOQ8vpHiJ3Ya78S5qNmkjr1WbcogwMw4bTn2ZpqaaM7mZkBjehV8M/5OvqfJw9qNYpBlMYMeWYYlY4FCkuykn9l3dl03Li1wMJGUW5hUNel5QNGH8wk0woQDqdXLd3MO7Usll3BLcWfN+TgZFiIRCrID0URu6JzrERcfBolT0i6Ysg/IIqZFIkB4LKrCVLTkr9UclwzJj76S+9JCQW0ZSS37EBz3vIH1JaDViAvVvPmx0jACo0YS6Y8fjyoAK5mQTtwivgRHsumMySbuW/0xZrHSKyx0cCQVIYmEFNbSTxFWBHRbR0FHdQII1miKrVQrEuFIEcMb5exviGgrdtBXo5AhRcUXmswXfcSJhKmxVCb2ci6CiwX1ZqKiwNqQJ1E1OkgpIhEEhVQr863/CIOBCr0EX8LXXqMa8aZgm/t5WyK1XVMfKXKSodlxrLHzmQKHfWYUH2lEDLZisWzYRJDZWJcDwrHNyZ7Eq8210w9AryGnybktSS0YIU73BmYTtXaNQieymS5pte8kNRROE+C6PQscTCQ1zcslAeYWfEXtYx2xgM9Z+RyCC1r8dzDBUeNeSBU7hFQ5mnAmieB5PtmT34lllgT/U1sPikihLpi2NiaSkhMmqciT/ARWqMOb8cJIQVxdgLAa9VpsCHqP4XqBRoIp7ZygieQwiKMhcFot4ot1aylahBJPXz4JoBod7YVFf7HCizLRFl+NJDEcdxzJaZRQ60Rv53AglUbhe3R0hC9M9MlPTgDBwAB0vGUS6H0YIJhKtTI7uxEYR4airMiOERb0is527oMULmoIAEoZaAhGkAWg1fXhf/+321hT7JLaTbEvW70Tc9Mi7hObFoYoZFBiZLh2ZgQgq0AIuGAdne2a5MMa3yzGQ2zwNjzSsgnDmpccjGrMlUmJzURcyFbkhsQsM74JTVSYzHia2NuSLFuMYlvyrG2zquoFkI3APUIABG/Xp7G+bl9a4D9HoWXrfQibez6hHtmsNy08WHTjr7ywriXOIO8DoAatJY247Mzrb59V8mNd/F6j5S2WCaxLgg1U64Wjq5ZwwNvCzxBAZQiGTAzJlyAFcFR00hc3fZjlR4HwvTUSAfXGa1aaLsNFNwiKXsYR2gwXVR3w9bbmRVujpKLWubxSbHIvLCGi9TPPPEOLaBoLJkl6JSRqcl6puhdZWix1koibuHtE01hnf9ZwBUF+YzHVmG0jslFjQbaY6iK2i2DJtTCaffSiYxbwEpLEUBuAOw00sTZNPsOyi2AKBwWkwBtc7t5j7ETG5CQV7grZFJu9ew+fndKInHw1U0BmYeE1mU2ci2w/RLZRAUgLTeMdiM3gSaKe4U1Q9kyAYAjL+R38a2B/CxxMJF2qNF7CD6xAwcXtocyR4+ril0vkDThn2mjsUUTIa0UTvm4xyw58VmklFAgEHX6Jo0i6XiYnFZrzGFydsyKuN77TC6iMJB6XTk8YTF1ZGksAy09eKXIJpr5AdLL2Usj6hGaUdWj4rI3YaVcQEXp870lsr4a0OP5rFOmkujJum5BVZMjPfvYz1L74xS+6B4BGMDQZBBoBIEATKQBCGExhCwI09BEXSjwCmI6hsPCZgdr34QbqMvaM5aor+b3mEnRc10jDDUGNMLrUJZiSTZuZi0UTnZzmy0zJ+yStfhnJGYwi2Nh/jHz3gEwxWsSdAMylMKDIKOu/deuWD+BtcnZHWyPzjrsWUj0+8cQT/rAT3fuxAx3PNt1OpWZPMiMSApufYiqRIg+aWJsmhmIuu/JOIteN+1vg/5zueUN5/vZ41ter5LvbCHfQgtqwW8KnkVoiE0NxEpFMObH6+0k8g9NY1VavLsg7C+L1Qmt9Om1Zokg57zhk6UVEsJDBOcj7Ex5a+J9ed6YOv3otYwaxbDoiGY5UBR2AIRpHAI0r260LdsYqeCl5tlXk3YIu4tXC753v/Hze0jKcdsj6obQszw2mKCNtcQaUneEFwUDU4LhrE1hpp8vKEX/R8eG3QIOLhFH8dYnpbb6fTvluiQrOlUb539JFVYu8OwTCY0pkt3WooZ9qWPtBhOP/gw8+yAKxRgRAEi2GQMYCLAWjCC6EEQGJpxemmTIEmrnD3UDFo+DrZxHiO5gw8FkbZgWOFmQNyeZaUBM17G3axXehSoSyGRRK4qULwY1CEjpC2Gi/s0d0VjosEsChQ3gwdiaC0Uje1YRHEVY7IxjYrPGBdghHBBZwk0PIsVuegwKC4GEH5jbQtOfYAKmAnN0nyQrAZYi5kMt7dal4B8DD27ScRTgAKxHJQKRiMdOkURmOg1D7RepmgY45//aMQJg52oAhVwbhIpIsx2JEHrXGgDH1oOnFfVi/5Utf+lIDNFWGCkAxUjtClSF0cSB2TU/wOME2cWjhJI+ZNs1eFnuXIiK4ntOehPyJhuDwN+qU18i/1UbRETLWWtRWqUYhwPWib4CEALyMUgt2IqyAxedwibKx4IoWaLWr1/ZbGRCAsikRTVbhc10kVgqn9KQLCwCH1uLRYdyJ1eUmqWKhy9pgB3HcnSlAdiNgSSvg40sGoURMEUAT28qxWkQ9rztEQCdlAVSIwZeaRLJWLR4/SxW82BNg85ChGCVpFax9DODmDjX4CqbeLxOVYGIcwbTQTp0KWlKfMAQzxN2rLJgAIrIYx3TURBA7Q4SPAFa6ceOGrQIOFQzMpJk6mCmoOUUjWCMc+PRSALR2r8p6DIUaOUnYvOMyhZzbjdN7WQAd2SRPIC3FGZxlBDLe6wWdxJC+cMw74eGYHbsdQGLOsWlnQqlGYGjak43NwVkvK1E2u5k+vuFOmUlNn3gtHcYUEQ7jkQyYmhHWQwdHn3B4oYoRND5AYL4KPyBb5Wa4jwwXt1LekmrBTRZeiNcSF+1E1dKs1QXWrrDMxYVBgT3RzM5qXNQKFuAli5GkxlVOGsbgBTCW8SFN15LQBWFGIZya3ApqaoyG6U76cEpt9Jp+HwBZNv6SG4dAjcyigwUGB6aW7RKv0Yj7DlOseSSREJfKcfGHH34YvnY4inZpqQWgC5fpwreihSTw9cL3qM4n1IRRk00BYK2YOcWjIlaS3yiw6IO7l7+YZhOLRK8XwXJSa8BKsEjEVimMhYE4XggWVggA2af4FoY01lhr2FiSly0WYrBGR2Lib174ab8lhBR7Sp3go0lZO4ocijzExlGQQlBuuxZ/tfiJl/pEJXk21+hQKTd0eLTbQXY1ARkRChIvTOYyVi0Fk+8zr8BXFqxdRoaO3im0C9arS6F13MEAi59SWL/22mt+vqWYULz0Nh2EnLLdMl2XCJBQYRY0AVRgZDPLe80IwViJ5Pw5pmCAmbKvcG+Y5iIVmmW9AHoxI2QGAWuBpsv+JwNV62VzxLVzBhTCBFCcGEzt2GEzAxvuXsX1t3TVDqSgzEkmgpNcywiQqBevEdyYBY/I4oXjkn4CqJVpBzOgMi0XBJgIazKwUh4OICQWG3JitGx5y9e+9rWVaOuZng6PCbTu+Y/cF5RyOZxk5EtusDwFd49LnCWcJGqKUVUXR+Rqrp8eeOABiwcRJfeCBqeW7VovtJCHqZYoA2zp9me3ityIO2qJO4+USjgliQ4GkkF73qxGkEbJph0CgpHVDtAijpgesJpzq0mugB3xAIZDlvxyYlcWlgEuRR8eL+rhbsnJJS0DCaackajkIRsBiCHNgWmUtFqm6YbUusVRwJVQS3U7a5MQLwaXYzr9WVEuml0meCuCiz9WYi0J2X27Km4SCWWhWWC18BAkBgCdxKPd0uZ0ERDpAoe0lqiTu+sIEmIqd9YusEIgNsFcdmPR2kZQIy1gQlMrkE1HsFp77DI1OhWN+EqrJeDScFk2mrwL3+ZLPcXYge8SwAfoEnESArSYd/I4WNjnTCjj0E7QJzwcXQQ2C6ZAQLSdMCAiuvIiMCJIwU8F9kQB7HLDfZeZMpbiYearyCqsapSxGuEr9UpUJcKI2DI5ufZ5pQnGWm2UmvCGjFIaL1IoglqWGZHihWx81Uszxk5j+BfhftrYBGAobsZoALwgj2ANXD6u7q0yito8KUaCm+yx16CdxvsO2uOInbExOoPLdNENLNBYmYKI6Rf4rNt0Do0WI/mGYNjBEbm4FB1hCjEONQ899JBUS5fLRMci+SBP4qDcMfGyjJXpUskStQasZ/jaFaRwJANJPGoHa1kWjQpMBQ4ZKmCMDAxZsOPNftHkRAZfI5kNUWNq53jppZe8NxdkdXntTtqbN2+Sv7BrSRSnZHk+igrNWLzERxmlXnEzM0p5rOQSN+wgO7aLrXRXKI6jAnB/KnWF/8gjjxSUnT3FPoscRwMVmIIdZGIXzSmFL2mxI48wTU7DM1GGJcAf//hHUhmrGMuwcMhsLtCkpuEes48ajkc14hlzZdO1VTUK8WrnYu7hTxz5C/+E0YLdPSlYj+QEWEriCwp5AMOyjO9wj46OKE4pAvNJvcxifqnPhhFJhYFZkuImFKYd0SEdMqdlQ3bQ6NFRIzeGnCkACkkUpBgTEZTNJlJm2Yx4j+cvXWlEIWSzE3f4ARevIzWSJA+yADXZhkVd0z5d0zKYFwEYn0jK0E93LWeTfcuXv/zlGQMo+grARi4j7hI+m+KevdmFlOZ7Jfg68IN5wE4KBDCEYAqE5OQftm7L1bKHALCqC3BmvS1lo6ZjuzePiakW6RsLOkZBlnZJD3tt2nEp+3BKAwVuJ0enXV044mXICDzi6QLbSKVXzYQswyMFFbASjFftMJESm/r15xNPPPHggw/CwQI1ouKClEOfE71VJ6Z7ZIG3ve1tnF4Khqb8BSbXl42++uqrzulWZpLTAnJfeqKMlxtMmaCXyDji4gepMJ39ZZ0sKQpbXdCEs5aQUEgeL6+kqBJbGWtnfI3k8dGSPUZ6ZZvphCgzwrHM1LzIkhxmIdO3/Iv8dkQGF0rMIMkTnqgWvMLg5lfN2tNCOy0K2YhtCKBiIJqkJb8W3G23hKEjpk2KeubrCgAi4UiqHD6OWjwSXq/CMvzKDurbA8ITm7564TCLDNEdi40ql0bBcIVGxhZ2TVyvlQwx43pZACBMC45MQQB11yZGsSFqCjNiBMCRALzRKEvDvIinTjwOJSwfBQN5NRYeL9d0BI44slh45MwEG3Pp1Yj1NnftDbkUkbgTXs0XgglG39f1n5XvGqYYz74GA5KJ0NaSGk5d4Om9oNzRQVZBlitoMWdnkG3bTFWjhBKbtlHaPRrIe6Jwhpx1zRCjtPAemREj8B6XBl408S2PCdMQLLTg7ggs9IhTejliUumCtizw0zGBdcWL60NWDARTHAJAr0aUjfJ3euQgcZ86RnplCv4iusglvzOEyp3KhTO9rsawlqczjvUZU9x1icIadUkwPcpPcdFooX7sYx+DSWvCSHuFNmjJoFGXR8aRtvcoMmpsrQKIYYMhvCivdloXH71lgiapjAtk/9GFV/OmCVNdlistRAq3E/gSEnHqoAamBTTEdYEVgJIlw1EzSwXZekUi2lHKhqHLl/zsw1uQuleFPMQmbQJ4JK0WAtOXaoKXmr6O85yBCuYIMpzj42NRkqG0mNBI6QowHL4Tvctoo9zkFIMEUDh2QWisyvgxgqwlMcjjEQtWIon5mhb4Zsde67qAACaLeHGE3/BLqXFEVkGtGv0ao09Cj4RfY62qEBKD2EoDL0UeRKIcu2jWcjb91Rsnk+ooIRsyoH3SGC0yHcvDNqWRHYVn5Wxy5+o1zVmB03MFR1oplW3zNCLEE7loSIwcIgvaMbiCRkIyuoLsaUTavmRGkBFcqfRf/+UYaM/xDcAf/vAHRyH/wZEuCCxDdy+C8EVT1APwKkK+vP4tfPIYq6BDHkMIYDiR+HQ1QMoJxleBo4VhwQAiYUFgAxnBGnjsscfkCKhRDVl8CQOt2cEUwGIyDmvPHJFZtEKNVHxObNJ4dHSkHU0txEPEKLnkjRs3tHi949bST1G9nbAChUiSoIwIsn2mwyBjB/GIwL5mZQeNCFKHzGA1ORGRQAlejtXWvHTVAjZNNLIahebXXnvNRLthIJ59CwIikmI5I5WRpaCabApqJbNgupeTAqhQIapHfD3mEiNJ/iwu4O6jH1m2PUY4YEmiVgOuprA/RiSsgJtTwlMBzDHyHMFLqi73dCawT5svjwyinQrO+MyrQDYEteT36PtiQ8RiVvKoZhOUsSi2oqARO6OgZUC97VvVZtMQU0BggKOAIQAehbutLiHVqRP3i9fiDHWiGXG1qYydGbQ3C03sQM7YcVGjuI1VQ2zq6L24JFHIqxEkAO5yAnubxeItwoi3k9cqkhrmf1vzR38dLvg3wxljXfmPJD//+c/b68yoJH/GY8DcRjnKmRu+XldOY2znQTKBFb32NxM5QJimSovl5MbNmZHJvB5p8Wtv4JKIedVeSdXbT/+5eifPEm0QBsAOTsVM4AJf4GhKJKTCB32pQDy9MJMchQGYyDq30wgNGsVWGplgOPCx4Bxqw/kEyixmeUNgVS1wNFoqzEIGXXydc7jk8hbiQx/6kOWEFLeu5itg1NTJLzcRmBQ5Jg9DEKZ8xF2nRSXIehGPFL4WTNZudeElSfz2t7/tN/iuYmkBWfhzlkc8HMJ7l+XmzvEcfZQd2+VEH/jAB8hJcsbXSJ4AGmHhUSGJRkwJY/kh6wN+kmDKzuK7s79ahmuJFlJtBkYpLWn2rLC/UoAgp8YWv1qBP0sIjLiaVCShCJ8kJ2uLCDYAp1TB2jVI1oMTMvy7XTBiluGyhDV6JHmNzKiFEaTPFOQM3JLBWZL65PcZr0fBlF4Q1CxgDdrkjE1lFkMQbBaQUrRABjAgWG1UFgZgHXcwUZVEzWeM4rS+L7YoOHk7Ohx+1dRDBqs3GiOyT/3rX//adzL8jZrwKUIeNEmYDH7x9a1vfYvukgAehZEPSJzJfvzjH7vMZRa3TOaa1okXU8GE5IggmMeqzT40wFrpk0mBwOG1W/IMZYGwEiIBbrS8ebax+XO92mmtHTKnAmc6Laz3lq985Ssgvs7nXnzxRUmZ+zKRAlHTxullKwKcXjAGpGFfVGD6dMYiZGLSaKF5JsYplwUIN2qugAvdLAxokMEKUqKJwCEo+ALGnz5qmnkJUmQYUhno4jXuaGZchsACTSvNqpaZylw4Tb2EDNjJ1CGakLas0skiHa0NwYJB1KlMTbroUgDwbae05hM8ngUgOzizgGzuE5/4xAMPPOCAzGI7+Zp1ghku3Lv09Ch6IguZ38hBNJLKfMlStXAvLPBSoCHLd0Xtd77zndi5uGR2UdVAlk/flqjZF+y4OKexJ0MWFuHQFCm6qPNIAJOCqayEgFQ20W7SSWuW3UTzFurLUt3KMQUJjS2YktNjNa8FKAQ2RwB1yGRgAcVA9XDBiCUJk/GZnXHIqWYEfC05VoIGBwBNFyI77Xy3G3En+bLUknZ2SuaydkyQ/cwqE0Dl147b3EPQYWdaqGU/NidKmcG1Vf4f01GKawFYTx08NmTJCl4wjTUws5CB4oxGMLACR4jxyBNQMK2GYA0HZmPHnroMWSo1MPw1vc3KK4qbN286jHbvD42+0ilTRirc+YxzjKxOwHVY5Ja8RfEmwM/8Pv3pT8OkY9kbAUjl/zD/4Q9/yFAib/YUu3SRHzIvSmCLCIwjhycWA3J1C4eOGiGjye2FUY2u2k2BLou6jUov9YVswlhZq6+gmNWUEFGGBe/Pf/6zTMQwpG13zoBeEBPF+eKll17ywTaikhQ/K7RZZVk7xu9//3uYMuFf/OIXQhJbMOIHP/hBisl9/Lrx+9//vlnHksmctmBqZEe1NW+f0Qhf+3PPPSexIgAiZKM2/QFIXbxka2IzMQkRB3NQ93es5kWTdguMHRk399rJ1CjBlMcLgqIw8ZjblJgbo5oeOEgZjqau1dbxP//D1BA0mjbRhMGZS69vniSSZkUvUmTbyZf8aFohcIx1YmBe0hqlWHjWGw9DWYiUCZpWNqcmkQjpJEivsjPym2sr1vt6vXAikrROx4K74wJeJPTnnMsaSAWNFhw9E4FpWotGdODEjqjiJvG0mGviybOsfNHfpoU+fQ2EQDZjLQlwIYC/MuYykoZDPJhk8JiJEpswjKbGVAGQIUyPNg8HNASbdAMBenGHefWFzGRgq5noAPJwBjDnN0HWoFVpJ4Nvw7NuzbgAYS7YnCXNYAZhLgbhHhxDi+GZEQWAkmEBjAABsho+I6hJosa3wnQAZjHKhufFnRsb8Y4l4Y9hyZDx+RgAhR63a/R3FvHEFFv+/I27WiA8tmklHgFYAxf+LAj4T3Fs58SghexHwid0YC2l80GkjV/aZy0/++yztmoyELWET8yhwtNPPy3TZ1VB0BFciwt0P2F4/vnnxWVq2i2sKVf8DmQOYf6bOI7qJsoRSsgWqb7xjW9IUwhm1Vi//isXeaDHBc0AAEAASURBVLGlgdFqA29GbYDJKltxwHRq0G56TC1aX/3qVwkhVhLaG2E6+8OUrOzdiN1elyWKBwb0EVgBEivrkPnk4d/73vekvUIVUb75zW9S3jcD9hkJlBjNy1lK+IYs2goB3ma6Z+AEMImBUca9rJrmkaK+eaImjWTE2tvctFN80Hby1WvmZJFc3Cjhhi7udBTzgWyFVSmo1kgdZJfU2Jmmft3vPY+4jC+0JcIGzETIakTn6OjI6YYreIyXaIWCGRSXGc0tpLXXELXdgoT2RXcpjMxdmJ0zGaIXQTVSHmOKiLXqPyBBkH00ki2cLGNIwuCl1KXFbm+hAtR2RC7705/+1GsT6ssUOAymfJQMaEpX42i4gvIAhnvcWU/jAGFGQWMiaSxYUJzw2ke7dIRwT0o6kiEgsYOLiWxFWvHLGdHScN1sNi0rsGxDLwXFC64lqsI0FxRBwfAoZIGpNQ48QI2rAeve6npRQxZB88V7vZ/EQheZcW/e1RC0hAyAs7Mw+85ioMt6WztvNJAA0cfamgLjKLBwv1/96ld2X8tNjBNtOY8uIeI73/mOVIYXSfUef/xxByChCT6yYGPZTboDWTgSuAH808U9Y/p/J/25CWmv4b6W84NpQVNoEnPRtCcZSCROS2VpJWvzZ2QJRnELNmnVJ29CYYukZgsbiadvBknjMkKQxg9jelrwSCAkeUTFSqCP9c/QKWYZP/XUU0I+KRGk6kc+8hGbBuVlSbqMkoZIvL19/tSnPiWcC6w4IiulNUqM9ofaZLtEbKr05jRkvcRi/lAjj2IyeKetwlZPU7rosg2QwdSGuc3aTBPMcGhuUmjNv22tbGgI9SllJnJxunRw8GjrE91MCcfy6OaEfzDy6JvDJR4Yi426DYZl4NgPHQL4hyV348YNHi9Q0sKkAPiWa1Bx0CxYdfLB973vfURF0Dza8GSjaU1ajFITEY9gRGx+6JvB6U2YMNWUrUt7XZRSwFYCH+C+oph80FEGMrfGEUxmCamxLEN3FlvShDkFDrgaDkCJlzq0AO05TPgalYQURtmc09aCowLZkOF7lQDXWrJOKjXJEwNMYI+sZJXZjWxFRrEtWOndHQRaSBJTU2+6r+bgdoEA1F4XzAyIBSN4VIOV6Ku1GKIFJkBgkijYWblEw0PgiuFA02Lgch5hvm7hq4qt/YUXXnDMpymt45IAasS5segmdPgLNf5WmfLe974Xax6uCE3dePA3+AKfBSXmcGOkBFPxUaJKfosUgvQFRwvEX9Xwn+t89rOfJafFKyhLaZ2MLY0nn3zSGqGgq1KppIUDWdiFSVOFVAja51iVCivj6mMCXM1NK9yVBEICPI+XTqKlV9RjJvpYIcKNaE0moqNgGZNbZHSUcxsFgTSGCJd6xWgxpfUpNFtLppZPi1YIkpUAapIRgz4lZYmrHTIYL2gXL1ggktZDTTSMS36AXSmYqRqcDYDtyAaBXgBJlvzUbmnrdtEjdiiGUEqNKfnVKKtZhpXYlgdgPQgABKmPIFhp+LKG4DHBUBOV7L1iqPXGP8gvaOpt+xFnuQ43skuZFEcNaMiSwZSZZTPuMUWQbW8gnkZi0JEWR0dHL7/8MqeBhr6xCaBmRrUWhYJZlUEMFECtdr0YKWhyaHSgUVxNKjbH0VjxmuNFMJoDe5yisUI8jdWGg6msRpZNtGcfyBpx4a6ckAV0eVTDVAD3qpzGneQ0IhVTK4Bm0/5nwZtiDuaIbV+0Z8PMhs6blAUjC3/glZLroospKhknyjABaqQgVrMhWA1fryIeWa25uscEA5g18nAVCGrDh/hq2KKc1u4W0i2w5cYrJH1cBZz34mIUmgAzCCCP2wyZFk/m2/5fYdkbl/7c5z7Hkz/84Q9zUZ7mlEZ+TmiUc73bVaPcEjjAaRHZWMMpkD/AcbdAciy8VBeaqGlNERwF1ACWM1NA9sk2LyKe4dozXSYy/CQnJSUS5oYOqBNOQoqWbdACeOihhwRjctsTTCHp3Rokq17aej8rbXHR+7vf/c4Vp5jiwOsmghqsT0kpJxhZh3fIRIFGFIFYu0UlLjMiGwnfSAnE9griNql4gS+l0LxJVQ+gkQXiInYwt5am8zSmhRsqZG7Ihgs6CgMKIlRDyiRRIUbMbRrslvZMQOzyj9hpQRBaWu9kHWY4wcfHx/ZM26n/M5JtcScbC0eEgzrUyyttgVwE/fyGwKbDSuAi9E1CBAOw1i5thGZGvKQSGe0W2hMYHbAavlFKXbS22nkFszCFfSJhMDLLDPLoo49adbyCGACphBsGd17uc6IZ2YF73GjUG0LAwMmwRMZFzou7RpMiHpGfcZruM+wM/64WAmzQz/J04QZgsoFbtGZBC5jbyNpsnK4FvavQwoZihEwlI5gIlNkcgM6UHuE0U1gjqCQD+jHKLHBgVuCINeaIJYUIPoNmo7SYUPe2XM52joiuobmtHYJ6N2qXfsILZK7L36SlFj7KWgjDi9hBcKCR6ZN+aefJukQbaam80mlS8b6HACg470LmrrIHvHigH7l4l/71r3/dmcz57OGHH4ZsIMoeXVdaj3IacVy6YI2IdTjyYUsV4AgOzSrARfwVtU0BTcmAi/RRqGSxk81cMiVDYRGxUrFQZbaCN7mZGBuXFNg7faPuQGqgEzHFyCHvZQUSe2EiuQDo/fjHP+5oz3BYYkZ5w9lFuPQROAmk6+yFrOhJXDsbxbSj9qMf/Ugjuzg1G65dTZLLKuyLlDogeIjzFTCm417TtQTYMevBRGccEQ41HWaV8CFAhjDstI/DTWMUcrWz9WU3yHCS0BAz7TKe8csx0cc0FnZRYYtVOT0jC6wm10QztZkSgrXAJwayuhqLOJktElsa3zK//JIbME5k4Scq/IYIoGKlmMVDrA2pMYfWhQ453Q5Zh76TFbU1kp/z/OMf/wB7b4BUxeNtcPXv9mN8a59eLGaURoX8LEAj97y8y3UKmBi2B7cr8BXvZMQgQEOuuG7eq9OUAICmFcxEHskMR6AktkeuRWC5lXaZlxUnk5I0cUVDqNwMqqOcUuAeA2JXyxIBESWc6WU3wbqTIhciHhyMALjwN6FQiw+Na4zguWoaUYdqzrXiiYWDrEYWUL/yyiteAcneZAOCkr1QkWw1xXxY4PKZlBYUBCgt3BWOdoLxXn4oDqqR5cACq4hJQvT9mFh8VKPGaaE5wzk5ISX5g8/tRXDLxNFeGIWJF4/iyfxZDCWeUzs5V2+Z/cNeVhd98ObruAK0K+yOJca8kCmZ2FJRQ5DDaiccBC1WbFOIToKqK+hjrDYWccsbKcjosw7KpoEArEAlw8F6WQF3XQlwm9ib/V/2YRDGZCK2AJj+n/zkJ04xvj1gXjY0HexmRlw/2XItOXusXIa1+att1jp0xaPF3mZ/NhFINelqLH7wgx9ARpA/mCCvKSHwQjVkLEwNLriLoXZiU8bzxGWzBkcZf+jK382UrhFeWOeU9gB0ulCmDg+uBFNTIY86pXI8xPNMtULTfEaNKUnQ5O4e4Ts/9uhyjUZRE/HRNBDZtbA7KmPTcaPegbpuujP8qDUWnLcTEkA1gC4AOdVJYhf0Wln6ZvF7lw3TQPPOdJTKgB6D1YrhSFVGndgZrpjHrMeAU7Qo3qCq+YzXJ5/85CeNikLi8SvhT6A5Pj6uHcK5CuIkrJZpilw+zUSBVMS2Cwod/AFxuy/MjtgQTOLo1bW7WATTQDjE88gaVAsNC0TiBY2aYOYVxwo+8A1ElqHa8lemWU8ETAWMGsDugj5kMFMrq3mKDdL6yJe5A5pgtUekYZIMjKLawjBEYy1xMhxAYizBelPAekYHvnYSO6cYBU0LOjQpddeo4GUsINbrtuvqxAIsw/7mi2E1CXPl8hyiHDOj6fUJiETSZQs0QcRezU3d2PAAU+BUwWlEYYBeOLyWi6AvhVRbGyYCL5NurNhn9Qq7yWEJ2bEtML24QIDc/JLNzKJAEi8E7JputXI+vmE4r4AMJrxzvQM4ZGPzBxwheKwFvnYFkAupaxmcMDXqskJQwA5xRc7rcMrf7NM2cjiIe4SZIvdhnTrkTDZakNZylZqZa29FZIJeOdgkmE5XHwxRLRNpASvoaAnuMa3By/a4DNoSQAHxlrApaxQiSejR4nVe5kXSQOaN1P41XtwAfQ5g1uysfNI2bwv3qNGsKRHEFGahQ4sprt0Qmaxe+FoEE10T0ABIKRgF5J+FMgIgKDoZqBdsaWj0qG4KhCxDtCgE0JgZsYNT14nFdejWpDQYUUgNGBhvQqcPf9Wb6Fqiro46lpHCmAJggEa9YAPR1GJgLKIT8dB0EUBJHo3XJQuwFeOAM6Nwxko8jxe6Puska+35/K0IKEq2kbqSFjQla75PtG+hI4v0BZ8932yaDi5bdBMfdeWy/BImT/Uo/RQWxVAX4kIksg5KEt5iLpFgjr8CRF6khHKe2rzDMfXQSIVdpxzy00jRpQzQ49TaCTmPwTXOEAAWuhhHTTt87RyEXyUO65WGHRxlgB7vk5pU5OT2dGlFtEU5Fsi89DrdO1iYTbApCJ/w8MEKE6l7nMZ1z44qO+gIyAjziBRhIlhEqwsaCyOu1ybKtjZaBm/4/vWSGpif0IubuXDnvVrMHbLURJMYnArAvePFPsVNmIThTlqEQpPu0Si1UYypGKhGAbJRurTQggowI6hRr1IvBAWCFoDGtAZkE0Jq0bX6Mr8Dvtq3uLWuSa3CqBbtHgF9beubWO0+7iUTWniQIFnd11AAUYAhEHQNgs/6NPZpul3OIzpawOzifGcg4lq0Kx4xhZYAtWh8k5fmklmcubwK8FVZP/aQeZkUGYqERRB0DS04inE2ZyZtgpjOV80iqcxUKmG4JAId7y6czvhW0ydEmkEtXgcxu0YUzAJSkiB5rmk1a3IiOCj0uoMvllqSzctl/irEe6/oQyu/HdBrCBciQ193k9YlpndN7iu5Y3NtrLKa/tu/m4Kf75GB7sRQUFAoRRIFwK8AaveJKOilF5j7kcqPgCXFlqiuZEAK7J4UI/Tvn0IkhqIssdnQijAvgpQ7axPB2oIFi7E87ZiXfeAUX2hhbMNbp2AWyKQpTl8GRF9Z2/LEmPgyozp7ZtKMjILtGcyGDhCZbjVD6/ULEy++4RxjdxTlibR/IQYK5CdMBLkor7C7c2nyU5YAADQpYloBHMYoj2yi1sLb+bNHfkVT/saHkULTkBpL7eGzrdCva1YT+nEHoEwYSsEEU7zhREWZMGqNrC3EGYW7rlUyrM8AQK0ABZLuAHWwmohWiE1S7KeP7ahZJFMAZGZVK/CTlYYecTEkQK2r9vjSE1n0yZOItXs0u2pDrksTwTJtVNYYa7vrZBn25+ves7sblYJ5w8hdmoLMbk1CY2HHMV0SRjhHR0ciqTRTlsrOgqyMQI7pEbI5Ynl5Ac92deAnGFaykCT/bS7QJIx5rCCeJAiKyF73uzlFhLRmP6dS1yLP9asnxK0WciaqLphNOlKI12UUWJlGmCGrSa4YaEXlKm0MxtpsJFN4odBYdfB4rJb7pBApQzVlzCgPdW/j7Oxe27GXhc0OO9gLTSI1KV7cpMLaJCd5Ezv0qB67LdVkHLwqgzmzAIBsIPqsip1cbzk8OMrg8LcR9mwZOvDBTU21UMO7SNiFJsvYUeiuhfCQFY8sAx9MYPPbhRVqoopG4uVCWlAIh4lQABuoV9EbU4BiIC4DDwBTl8fpPQlPkQgvU9YCbkA1BEvXBbPXW17O+kqWg2okUKV57VRIPvrrJetQjmDEE8LLiuxCAZgsAhmaOm2xXuoWqTdtzc7Zh21dmcm2vF7Xwtf5R+HDa0dfcjgo5WdZGwyBtS1OH5rwMzuiYCcm+lMDJlQLBKcq0yGM8kssJLBWstqPUP2ezYtOTF2KuRjloNjNfDWzhhgo2nrLJEb3YYCuxE5ycPOLi+8NfJsiaygWcAmTXm/IavJr0WXg+KF2LZSqBhio1m5LBhgCmdge7RwCAcwa0QEr/Gp4GXiV5TS+ZG6+iO29sF2NlcyXKObRPal7aroQlfB+c2groqxcUt6kEVl6UZA1PAIUNLUHqEdNjcZW4E/RsjbPqoIjg1N7tdVcN1yL3qSF7yrJVwTC/RDfE1jJt5a2uUCQv9lc7fHCCMocW65AcS7NGuwgsELz/tCOQmZOixd52A0+OgoPbKCuxNZVOGJYQ+CM/HD0Npx7u4wiEjfGBRrKekcdXbHTvhL9tjOv/oKJNLXMP2yPkGS2ZbAlz1pCM68yCL8g9KbYpwBOdpYTflQy3HqQYw9yXSiQPp0RsWzgQ5ZI+42pHIoOfi5mLIfogKaLiHCk2QmQbNe1mTKvjMOfbqzfxQsTzMtLTLyc0c+0+TQcWx1rs54hkA0BOyQCHnroIdb2RbQTIu80HWKlw5Q5kvX4mZkudND3oZJJsV/6jopn4yLgcmtHJ3+bx1wbgqBpst7MGl5+ZmcgXjZaNdYcFxHiKWYQToB5R8EXNuIdQGOeg1oAX4fPDz2CKYWdGjWNYN7C4wMI4BEar9OCOCFpYUdxiUxs9MkT66kJc08KAXYWumt3MnBYNq1O8eaXcWyBXhX62EhEEyNop0AWdyiliKRqhjLcjLBbNqTd2JPRwOoKSyqMpmZGQIV5GVAdYMEahTUPgYk+4mpEyAAWze3oAsJ5j/bGVpAlJ/n54a1bt7ifG6GVVuv/Q1CY9hsfvHzs4eMBJzC7r6+k+RXPNJZ46BAph2cfn1f36TqR8iL0KRXB3InDGEJlgLQAHbf/IhtX8eNRBNuc1lKc/J0XMBZGVRI+//nPJo960kDKQNVhN0Ng/BzuCOcvDgj2QqG/YyKzaG/0rah96be//S1lCORjV6vUV1c8o0/H/badT/j40ZdfXJxpuAKOchx2YTJL11nMrNh7RwDsco6EeZPXpoBlZJRilp9LsEY7k885hTB3o+KdD3XZWb7ZtLKtIWbB7zT8aAKCdnMkbTRNFq2IA58/icLa/QBZPBI6XReYJvStNHUOJEbLj7y8khzF2uxwBoI5f3nFxGV5PAoaJRR4QdMIDYXkJzaR7P8cwJDOMc1sMsMMgJYDaIEwtUak1AriikfCG6WFjiIRwEfjvAtgoAIn1md7VKyTZ1mjsHwc+LLwiSdi2qvILOfKqpSSK/ldnBtwHM0mdQDiiI+1lzIYXjKlkUh0ZJZgQzwu5QTDr2S9208nVh0E0xdrRHKDYeraR+xz+JBjwT/NPoO/DdCORsQmnuOO30n62M6k124nFjqI5zs/CSmnEn/EDSckX77zHBHD1/KSAz/C1OuI5tTl7zzQhcXcL7EnM+LrRasAbVt1T2Wsj9yNksmJXb4kJbkjlxthroiRk5kh9CWY4ammzoypGaz2+B+3WD0sHjcUpmS9NMRML+VJ7FPV7373u5SxebKCBWwtiev+HAA0Tizl8Yk+4Qjh1JkhNPp1v1XUQrW2xV8fuArKNh8WtOYxwgWsxn1DnvvtkTUpOFKBNx5P69Ie8rIe5HrNJbcAtypMBPcVgHzfJw5q59wCqzDKnjzeVuQ9jz1ZKGnWEobbOUPweEMILNYAPDo8OhOYO0NQRsrkavR7JOt5ViZSxDDQCufNPv0bkQiWBQRlru/TURwRSSloABOaGGazFatdsWX6Iw9kM9e60JEph6CFw6gDwIShlNOf45tHfBW92gHGAhBhEGmLdv/9Afq4jB2Ioaw5n1XB31lOG7MTWSN8uhBVvTG2IWQOILl1URZitZtKC1ijXlclNDo6OkKBUsxIx0b5o7EUl8pRCgswAA4YmkfTYU7VZKgGsKECgKmEFoAjQE0w7AxByq+Jmpp8JuJaIJhxG6rDeJOrZWfRW6n39tNqyfATNXXcLNlCHImogLt2dXf08lAWgBZTbu+jEUtAvNPuzyvD7JfobGiPN+nCi5tlzuynooImRfyNUT92EpGlAgQQjqmDMhUEWQuHY+sSytAnA7Nghz7i8SWkdnVa1KjWcu4IxYgyF6TZ2iK0BqhKRBMvDkqLMPYo6mux3qxqiarwKs2kkuGCph3MKJgWMHtJnajkEzm/bhJzTVtJO6H1YgRzpE+H+6FmhJla1jQ3HquX0uY0vJM6G4rogqk0yvCB66ol99XSzAHkj0h53ceeyPK/7nfYtlnnH2KN04pDgKwQspXDqziKGUE2yuo8Q6N90eZnt+NGRVstNmeFSyUD5ETFyAz+9a9/NcsWJMGUvt4Xf5ENDbBRCEzH6CSqxYA41k6Iwh/WvhloMXMGs28IIggaBZMueBmrXS9Al1KLXmjoEM+PUvx20FjUDBzbboh0Vx+ZLpHikpxEJSR5NAI8ktacupxxljd3llj4xLZjGSW3MhEajWog9VEWNax8X785VTgmRsoMmn1jFStIRI4LcyEVZTVkJQsjRQaGQpx5ie1oD0cUdktjhcYaPgqoeSSAWeMkskiMdIWj3i5GoVxd70gi5PEBTF9eX9bzaqQ88qtMhzhkhQ/TxXCkhBSjREw/UmAZFiMkfJKL7AoKGnmjGy3trjEFU4WHC6zsaTnAsSIUl12GCEHI2rFwxCULUxbrbY02Ws4dSZtFkmGDh4nE1cyxeJqnlVoYhUNWLJPGPGkHQ7ZI9BpLcwVZx1JDBFN/bU+WWoavC6+Yboh+bx/XM3sSOkc8HgCex5GQEfIALQZSHMAOYS7naQmHPNRyL2O5vk2LiSwSlGVzXJAf8Ei2iqnoyZguPRmQtbmUaKvXfEEwEcaqZQFch3sJPbpwdxpo2RgovBIygmp88zAwZNN98+ZNfm/2tdjYZUYCd1M2ozYAAqeRGkH0FV7hGk5O7XCDiNso5iKe64turCAbSDyNpG3RGqgRfTbBFAAN4Nsg6rv3sFoMgUxZgLIhzBU8kpAAWKuJt1Z3VWEtLpgRCGLorVu3zKAVXoteQ+CzsGglt2pvS029WQCAFDXFTUdUpART1vB5mfvBWDNXjMydFpjIVpIKPmrcgNHgYCeGiix4mQvHSt+u4gJNbeCYEUxaSQ8WLNwsTC/ZlmXaB9AL9nc1ATbmV155xTd2UuwZhSYWmHIPjsoz8WqUjI3Athy+l/xqwpNHnfNQikba+Qzb4qVdHb5G1kBfo+IuGGVX9rTGkW9rhKARnYARbCdw7kjK+8VytOyB3uNzAqaXZlPSkialRzj2N04AzZnRZYS7No+kN8SqZhQAc1CMMggylutUl3TuL9yAuGD1o3vDmzzqsdFOBe5hI/uSnwDqgG2Ydk0DFYLV6dIoj8oMHxiQak3kILMwSzqks6ouC0MA4nOWk5qF1QbqsmNrlJZ6MYgjI7O807pe3mbieKcpc6jhZ9aMCxnr2YK0ydmlTRnMXMpwAqCpjGBwDHdR7krU8ZNn2wuRivugeVyWFKw3O0QWU0daw10NkYE/OK94xUEAw3ulIMLyh1yCVSnLGkhRR7EYhAzuR0dHpVYdBPhq+LFeCnMFML64Y0RCNTdgyVQwHczeh01iZQLDaYMBOKjKpxzm2v8YKnV0ZcCQRRMnA2R9zab2JhAax8AFU3Uy6AIgYpQSBY3ZP2G8RPINJiEFNbXpsDWaGrMAEyl1A5NEl1O2RgVNQ3DMztv1MF2xX68atUBhrFgskvqGxIYxPswOnJkMri/dY9pmXP3hSIz+LofNkkbQBBOuKzvmkH6kz5KUYluNlgl8yYRsAyP3AA7ElgYupLV2OMw///lPa8Hfl5KTGuJtDQRE6IIOTMZZi3xWtfp/nM7q3+ozW3zdz+84t5TBiYx8FKaD7cVrLLcVPN5xw1zyA/PqDtgLR2vD9PjshoYWIaFtKZarL8mJzpPMor+r3y/hLM5SJGp4g8ZeW4Lc4wbq97YRoHij580171kWdiClXiYynR6hedQI0AjWCBhltINRrgUwvTA9ujaxzYgUXIpx+A3KsgZTAIGhhh27ySwkmy0Vy9KxxTrhUu5D0ZHuORxJOnwIzaHdTwnQXqMbpUbWlo4mLqnWq0wKEokkGsnAfRGUxYjaptUCMwQORXbW9DLRhitjqwjiKKXiMCIs5/EdaMdGQ7RwaxzBVo5ibWNEVGOhcSoyE5U6/nqOl79gyGiqiccyG7bVfgWFeFgTFS8wgEgUJ621YDXJHsRBAjMdCellFiBDsC78EWKzZgazlSkAZD3UzDVqaFrq4qlvFSxJdHw/r5GdvZhWw1QYwaN2CEpmVPfNAzRc2FnN8sTwusZBQSDDjmDt0JBxhNNcoIMgBPSpqQumQv7tmuRLg0PQIsahYJs3lS610aGjGk3UiGFyRQ/bCeFdIttl/fFQW6x3SmIINKaTFnBv1AQZgVJg4S2Co+jkkTocnj1dRvFS8Zdjc35MxVDx1Atz0cbLKOogJazrdcVERxZmkw3Jl1oMvLLRPOwJkIDEJDANJJYGg6ltOi0ABnVf5pWic7ppILQWpjcTajuA5cdShCOigZgaC1AMZ1lZEvq0am4ghL+neFeDlnjJRjyiZm4LGOBxWvRS3OMosj0xcBJb1ygboFYazj6cScCSCIDtn2owOxvOgEwKIBuTRlCq6HsRMAr2LYEPYNWJoeYOzOBoisV6UZNjggHOGVa4HCGyZFjKNrwQN9G+9eOL+YOxJn1bx+RZa3OizhLWi6ZRSY676wLbKrfWkpsBRgZjWZW17QEAwrsudL/BeeqKzrAAzNgkuZq6uaCaYgkwOJnNgrmzcKTPlr1GwoSZq4ggclUt3vsZZWy6Q4OgjCm0pJdGo5xXvDzBwhRzhr4tM1xEEKGQCpk1DJwa4C2fWCavJxtre5nZF50jWABRUUAwTzPjAMNr0aXA3L/wH1c6QofP7Jo71DLIcEGNRhjxVTryLoUFINAaRwA3sApInnGYTqgRRqgsk5M3cFGZpoGRMlzyhxfPARvOYhjxN7AlBtarRt8QdMBnlHPnegS1ZrIjThmuJaSLoISWtHoBIuC6qoM5jkIgJtMCs7sbOhiLCM2NZQjWZBcSg9Uw9aphnqHG1XelFAsouRch6WJ5AKRIiikRVmjKTW1xFDTZNhJGMDHsQCljURj5lzCbwEl9CB5ZlXG4uPm2nXIsARFNHMfIaBoCLTvzHl2+lLBiOZY7UNOXw2k3IwIWgOuoCUZ+UcyLGsOd2Z1xjo+PcU8wjNBXY6GFL7qo8VcWTTqPpBS+uOQPo9QSMHAeadQjgmCU0SQSSXiOwnQOfZaEqyS1ElrWg0xaqQQhyU/4KI/fJwYWI/CwvjIgBRlTITxzyZ5EK0FfGOUSJMkOMBOYMQsZ9kijqNlYWrAMfC3gJjqDRIHlTRzj+AExFgDRwdffDTfK7MBEIamyTNko/3R84VfM7nTCpOhHPHY4AjQarvYIaJGig5degFr7donjdjuODOLYbuGbbpOIaWqG7BEv9D2abggpLqR61JgkHABAwURCoVt+P252ZtLoIO+wQowZheOwKIziZUaYK7KoxbeWkE+rzx1JiYIuZ0UxNoJFi0c7Kenj8y7fdrERyejAuGmY3+TohgD0NgehzeTNlGTT5u80He5Je56R9wRbBkxh5tSdjwhGL2aBIApkB7VGBxPvaky/+R5l4S9h1msKmZQF+JkYZxQf8vqI/1k2eQMrhQwT/fBRg2aIe3Rz4XzkHlOj+E4A+EaBHe19LBEjvRJSfwhStBVzoYndzkTiNWRk07Sx5HE+hSZYi9R80dEJGiJDDXxGGTQEKdjYfAAvj3xDNNHLfxQWxtQjmrq4Ey+CRmWNDQFwwoxAfoBR+djStmdIdbldxCOSmnhyC4dHMncjrBGvrBqcec0vs1Pc5pQwKDALXSDAVIxqoHaT28oCCDRmkJO4OvRaEh1j7bVmx1hnc4mn/BTZTGQNso8idhvla4d23CRBPAFi2ihTEDsCaEFfDdMjNLCxibpR16Wu6AWYVq+b+BuY8IiryYNU3NUK4jDNrC6PkOOlEWCiiyqJrZdUvEvNht5g48Xs3GBkNgQaagCNaMZUo5baB3/I6j2t3Mnp/jRad7U9u++j0p5iIKg0Hwwn0vEPJSNGJNh84KsA4CuNrREmNDFLkuj/ujG1ZsVkmEgAZNdAMN3yqA1EBL5i/jx6veYQjW+ugFqbrd4YBWj3gbGrHzfIt9aX67IGeY12BDGq9qigIM5KbSwtbmqRCKZe5mLEn8iW/5HHHyGVGYmApPVIKr1uq9Uf/ehHkRKIrUnuKBEmiRY4HJq5ZKzaXXCTU0i1aP33GIgUH0dsOmpciXXbcYMPt6ZR2tErm7AAdbSrU3bZrlEqKidiOi9AhCrzpXFj4gqIJo5h7U92rxiFDP+8xTbJBxB0eQo26UmoJrASIMi6nj5a/0ajgIuRrpm18/I9Fz7npCCfUYbjlXE/l6hnIx9MJE0NJh4rszv4NPVmVrYRGrUidPu0IvRIog3xawr4BRqu1lgeD46dIeBlmHCKF2u8bZtQCEFxXQ15uC/lcfePJiIQrC5h1B0fV+ZM4ReOddkVC6wita8gfCNioG8ehLDj42MU9NrDEVeM8ij2WT8ow5FlaJRj+ovihOS1vi2DSTy1ZBOyEGltw9eCO0BMFKzJI1hrRM1Y0VMm5VGROLjeIq2k2DJAzVnSGcrYPiYd+yAIVjcw7Q69ptFSnTFdytY74Y+tGFMktaUJo3ay1G9aTXEGrJEvCaMwTS7zmmvtTVYI+9fjtOTBSyRVCIMF4e2UJOF4nMd1E4CfjPvRCCNoS9X2Z30uTHwxWuo43M9F554jnwSLey7HPgIwsbJ0YvA+A7dxNgbyNnGwkOFln8e8KnbL4Q2s1muIXMMBmWvmjgBJaJ4xXACQh47Fg4UWV1T4yu+8gufo0hCHfWjwFWhwWlFCnte4Hh0PneitNAjWYSuTO7pSIInaKAi0sEiQ8uLCFVi3pX4YJ941Fl8R2fWCnBQaedTGImu40CmXcWWmBQJ1hAOL3P2A1eiNIr4yWcqml6it3Y+A3Zwajkjt6QJGf7latB9ooQvtlOQfNac9G+o1F8IWgwtVTtzZWXsxzs5k1thEMRdmGWAGmVRC6nEZZLPeuSxGjPCJyhM4p7keCkOQ2Ir2HB6ga63fSsFRcwZeOjB8UU6SS2dxNQTP/RXU1Yi1zWX2Z9891Mvu3fj4/OBcZSbMcFEPZa6scD7vLn1Uwcv5EJcKQbyDhq9HjHQlg08rnLh5PDcNwZppCFIWhpqjKIXXHBRmyInhsyRHKl+VeU9dZOwPMupFATW5rZ+++L28VEVmCt+FgC6RC2WAkOdwbcXqctbup7e6WEmM9l7enzjofRdkVxC+IIFp0fpoxuVpf9HOqnaDxhRkS01krT3thIfftybuf72XIJtMlrJMBL+1x2hybfksTKl07zEYCkIWMwqwPbOH1UIjZTyQ7sFUa0LTUfrPFC5GfeTkM0k7jV4vHlmjb2ugZXPD+Qn3404O+GbEPYxHxvTmfYgH7F8TDEeFxxJ4FojJxXfcWFcCw9RllBZCxkjjKLs/6zvAjHuiGp5Id0DnHg45mEi6nGDzrWRxRtd1rjJD8hg13+L3sgbRxPdrYoGgI655jJFH7MCQ8QLI0QyxYHxcAk3g0M71wRWxTJdFolg8YAWgoIYOMXIda0Yw1eiKQEj1QaUkAjXLCRFHM18dCvF+uCLd817VKLIJcLh71SM4aveTPnkoXgYawjUxQlCq61ROMDILmui7NRNYfSsj5fSlHnxLSAwlHkBBxAeJXpoTEg4xUPORo6TY+zS/IPJrYAECQe1w1Irhrtt8QexG2F97w33szHS4qGepgA+9pDX1lbSjuEkEcwzfPLJPL8EZ33yxMMcYRxJV7T2IAJjOTNkLfQXBhggyjt1IbSr13oGtcCcJdjMLjK/RvCjJiTu/VWqHibU6dnoBqXYHAuw5BBeCQU7rRh2inxxSJM3KTL90EQ7a+t+oTQbMnSUng28svxHIBAunZutfMPIlrAxCKDE2v6/GHV/4kL358UdaubiQJAqjI0ETa6wK0Uq7ItIJScGWUB6sRcm/kdJITiuQGzkvE8xFASIO45YZTEy1OKGj4I+SEE+L1SUx9EcMRHMvPf2ESUA0Fq8sI/6iLGl1EyrkwdEOwZJG1kWBgX5B5JtBkqQmIxhOGAMhq0uTfehOQQMxlRfDl5/Sl2yWepkvkciPDgpqGTQcBkFHwVcB0FcN+Q1QqKmwxmjU3ma3c4PMUOzmfMA9tMNhGRZjgdwVbDgjc0IzK/23P/mcy702ZI1qyNmtORpGe1qvmIidmVWMQkej2YwCgooW7cqSLNmEVwIoy/a7ASeGGvHgVu4VsL5cdf6PBS+X9KVTWznv7eiGOC9R2rrvgJepyoGEAwVlRKx/h185hbgGXjM8OZNy6/C9XXE0BrsIw13MNVyv4d1yWida1Epc1CEgCODNWhSPcEQ3p2a6+DRKLT3BvRf6sk6piigmjHpT796NbO4ihemjo6Pe3eMLAREAgi1pMDTnd+tZI46GJCHiPviVLhmSVNpTgUgRUbsGdZlw8+ZNjDzKbW0e/qyXb8UFaOdW6rvR05UiSNFFrHelQEhZtkuPlZK3v3mA+QYojEmjDUWyuZ8k+fDID2a8iOuSGlpmB9iAe2x47bzIFDtVcDa35GyYjwHMqUlRjNrmqHGfsnLB9X4MGU3CR9AjWMsQwbfHBMDxjpkOzTsD7hXfO5N2Rh1SJCX0WFnC5dc7Fq3QM8rsA6DQYlAr/Eztfko2gRRn8lLF3aJXSZx76WrQ0FcLZ47b8lZOKa3QYqCF4RFx4QlNsJpTJvDUIc8jguKRscIxGRQ/UzPQn7fpWxktYFefYpb3PJAtUX+RQD2yTRhtPWOhCwsRH1l3nRNbA0jrA0MGhJlG4iyBG6X2aIcQcN/61rd69XR8fOykL6D7rQitDbHmZbUScxS8j9aCMgoANfxf/vKX3la7c5hgqktBvIXa4yHWzV2mS34hkjF9T8Y4fsVLazPIyHAA8MHqtnyNHhlBzVZq25JeGx5qtRvVwGxl0s3FeW21lNBYLDYsrwWXJdmNx8TeGLXEvyx4KSqpkvayiF8ZnYM53bOItF/dQcDStcj9gSxBgSv75Fj+Ze618zznI2dkjdsl5GrI0MDwRS4HYWfzsglZp4XBgzFtdp13TLnw5GdnxTtO5tQmmKqd0SwVBcAjLRJAZzp1AEykyG8gBAcr6tQixHQEg6kRL4I5lT///PPa3ZwKW4ZIVB0bBevowyRSvo6sUVhotLBpJPEU8qTJGouSAJcYcl7voKSNeFHEcBZAEA5lo4YUgAXkpARwYPedVq/yMSKzbUby7rIVL7CbDcZXsCCSvUGode/MIGga4gajhQom4WnF2PAHQUtDiKd08tWCl1qBuT1qht8xEF+1icA0wxJAi53GY9NnW+WHvMVrQNmovYfd4OCbJROyWmPuFAIK/qCG34O7HmVDvdobBc7rAGWvgHOVJd+sdN7hxBsZzjX2vMhLUc879v7BP6SclJ/N1Ip3vNbVknDjdqmuya3GHfc3tNMrb0ZHETJku2KBMG39C7ilnACXjGSwolwd4gJWw5kCM1hsGoQRw8DwBQKNhmOn0aOBAI0QBCB/qUHI86GSU7ZTPGXldygvSRkLWakx43i0AfgNqF2BRig3SsiD5sBOOzTB0szIMmZo6pQFQCCPG1ifo/pjPMIiOjjWRUKPMinpqsOpzFfYbQgxnPH7osAdrkYisQbABJ0dF1IBpoKXelpGzXXn6n13wNRjvWm5CNC84NK1CclRS4skYSg60h2Cr2jVo5qxiR0wjyjUTlSU3QYwpt2OMQFaRtmLSH499p5Y4JByUh5p+ypnAYhlPFu48X4GrCUEeym/DBPynoU3mwC5mLEyLI/+sIJa1iYp4+KK1/p/+tOfcFklmeu/UMX7S0KtIoU8hqiD4YDhhFxNMGU2/MRDM4AAYL1+laSW9xmFpsdXX33VdSrK5Eye6vzG4jTQggeg772ZFKk3GB5h0out7ATveMc7kBU93WDYP4RICLgbLtVSXBfEBeAX3PJ6kVduizKRJGhCSUO8WRK1fb+FDkNRFhc1NPRtBiQXzT2KO+wAMHCno+O+7PKokApTHAFGjYlYyWM4agPHIDuJ30EjgqRFvNlnFlpL3mnByFJRXylQ3B9Msp3o4oGJRDZyZiItZEMEKbCB2gEQ3E3Z7RjfrqNRC0xF73U5RAscUiRlX/5tXXFNgBWr+MNZvhDy2l0vj3QWE3p4M9fnndD2rKEZK4Kgb/UKEOKmX3+69hIUEOfl3pi71ZJ9eCeLskWlYKeIp0X2AqsWXauwtF6BBINfQQevVilFwKJPyw9ri01trEXr9bp7NxRQ1uKA7G7BFa1oKKoSz6goZBA4CnyZrNdN3hSJdOgjiLVaEiR57A0SkQRHeTeVARCwNpbuhMcLvrOnLiFSoHz729+ud7XW12GLQYiNuExZYuvyAVPXsh6NhQmggjcwpkY8hUw2Yw3cWZCaXurA8chKFeJ5rGhZ2rOu7LAkspPLduMMNHZZqM8UiNOFnSmFKcs4rHA5YVSXN0tuhNDkh5CVJKdpjCJCbMhaWLgu9vQzB5axq3EYvZSCHNq2kNct978FDimScnROyaY8m8+BeTnvdOUnyRLguKMuzsqnc0pD4O9TQzMqR7ceLBsBVCSVkgim8XXJ6EWK0KMrHPiKxaBYTmA1qQDVBQJ1y0ltwagNV8BkE2XAAUVSY4V1i9ZLHrHMwhOY/E5GHPQLTimzS2H5qaO6qOozKSkSOSWPtCa5b7wffPBB6RI4vrJLZ3kEJVAMRVNdjv+4SL3dkJAfU+0KACn/6QVq/riJawHHWIm/uExOCEQlpyHkV4rF7vsIAzYXjAkzA8LU0kWqgc0LhI1CzloMBECrhXGwACtgagL0SnIDIBMJjkZlg+zrPibSdo2XuIkF+dsXBVMxVCKvuPRwuUlZaMyVwDPXMaWI4epmHwBTi9n0zRP5fYTPpChoVAhPkTtQ4XV1vEa4AgscXiTlc7PY+Ki4IFOz2sU7Xsjpx2XX/rlvZS0Zy+LFCP4tk9LiROx8KihA8G4BI0yFS5iCEYQCKEBkEUCtLu0ALYBZXQHNKFLEsvgBautnCr71qn1O4D2GECYO+kTRJuHetvOyu053xAIrwVCwLKWEvkwSXgVWibOrVWITiTrEEOa8FTk+PpZDaVGIh5cAjbJewZRsWqhjqdNadPb30i1+GbEMVxwxFi+SIwhQEwkd+CICYQwRa+iOLARTA/BWCgUfnGsZ1QAbBVkTNzges1V26xFsFJwxbDjmXdcMqXHPOjEgA5b1EEwq5x4btl2HSR0U+l4NR5IwGuQlu4ZEQY1yGwBYnu71nVjslxTOPcYyII20AxC5My2W3K/he2KBQ4qkHK4dO6cPttQ5qIv/jpa6oHFHNYfev8BHR0hCtkjh0UeRnF5Ec+eo3dsVnyWJNWB+D63oqUXRQpiJpK32aqQMUcwxRuoEE0CtJS3qihaPair4Nt724IwvDCErVkr6Hlj/GVC9Co4kFMKI5zMpMde1g9yTwCSRUBMYawFOkKWaSIoOMUiLhS7iCXbO4AYqHmWjHjHy6agIKOYK1sKoxn58ZRT5ESEALdSCuw3GWKGWfQiAlCRUSBUsJML/+te/+rwUsoHmaLvWlWUQjwWD4IsIyeXg0mfERW2FZVDQDicZjGrqtYP3L/nSNj46NjBSEcB9qN8sYedzNFfGbKudkMIrrT0yJgr2FSroAqdjZLVAgEkdYdRuZ1r91xIaoWkPDdDYHq/rw7LAgb27H+NOiOR/PmDi6M7dMjXeLxWCdt4VFWUhpiXUERgXTu9/+2ipWEtyB13ahwV8pUdAXVGrPVj7oNXSkGlf0rQmazcEU4u59NO1ptcU3lGIp4aLIwQGEA+mq0wAHP+ZK5sIgl5r2AnkifIpIcDP8w0U1BCUsYqqAC3ee/gDAl/4wheQcoMhjLpJQBkFOBpRZgSfl0HWyOYKweot2mKtxZtoFnvxxRd9aAVZkHWZ+Nxzz9kGBHqkUnO71lUvysG0Y23yOHP4S89g9PHSTlmzAFMuTDby41VLYy9eY4SgbYA9xW72lOZTJyHJwHT2GFrDbBa0NGsjf4B2hczosC2ytrSxHr2QRSHg4pJfU7gnFjiYnJSfSQDZiBOXEfBaa8kj7/TG4+GHH7auZApcNhwb/nYx1kDts/97lEq0BuptYfB1qZCvMt2IScd0OQVb0ohbDB7xUiwStfRQCSaAFrLFwiN2lly1KKngGCAH1I4mdnAM1KXRlgDhkUceoRTKEGR5xJBvOr+zg79zEWVcYELAXW7uJN7vlyTp8OU+bjmFUb+ax4gK3paIrSwGEyPsxDh64VhCap077EszqYmRSCFvxcuPVv3hEnqBtWMqHdNbi0aP2nFx+LW3ibOyWmGCCs7F3lwViegIB2byo0ByuqOjizrmgoR+kuCXl3JhSZxsDr55cbcr4UXQWC5BHVrQ2sWLyUJfb9ENC9SMqjTpGpuU282r03SFHRRkFWh0cR/iagJHsyBYmwWY6KijHLXUCQ4BHcDSATwKyu6vBV8/EoNcL2rIRlNLREa2a+CALHAwOWmL0CLJ16sZ2o/tpGkihTfIglFBkFNC4KDb9czN9GqJWmkCWBCxtnH0XbqrSXGkdQ7T6tUCMFwB6AKoK+vmVXtAAnisZep1wwqHqPgCYGpEX2SxZ4hr2skAQTt51GKTbNFrXymeMJck2o0NTQanUb6pBSnWcMD36HMoqWKMnJEN8bZKMAVDs7x9DOA/MRRS/T8QmNKF1qIzwRAhEvMKzU7uTJ20hMliqMEvpEJGTfE/PfhvTSGIy9JGOfVvfvObZ555hiIzCmuP6BhrILJa5NHCoqsJaJHKMtV4QVPE6Gyiha3ky7JmHzbYOfx+Qd4dplFkUyszdmAtShPHgBEkg6jnOwdECG9TCZ81FMbBXctGHSm1LmUekTVKLdwbgqBeAISNetpn7DVwQBY4mJyUTS0t+z+ntOd7tIFLQ6w6fv+e97xHuyxJbf9XIJxRr1FOcAb2WqC1lOuLNc688jsZlsWGtWOmVzFyw8Tg+jIXHHsEIxUFLWAIRLVgiBqgRhw1NXbBMkFFl8zLbSB8wcUOAc3/tiiqwsSFRgiqrW2ZqaghXRLdmAWOUd4d+YRT2igEGAtTUPAbU+/xHU5LmdXCkyjsExzvrHQprmJFIn8Sxf0jySV6IjVFRChKIY67UawtwCGFeNqpBUE1BPgJSRICG4gseVySim4SUqYjEr66yKxOI0ohYjgL+M7MFYRDBjndsaLMFBGHYIgCUHSBySakUkeA80gw7BjHKJahixbDIeOlmIhsCNaoaIFgLGTTwQLklLaT2Qt6+wd8gqkh4Eu76GzUEHShhqYuxaPSQPm1fUtu6xZCV2Js1B51zXDwdTkgCxxMJLUkOLQFxrg8nuvzPPmLtNFfjfOdDQS9BR0rE1quvFFDa/2gBh73RRNsuNWCjtXlaClH8+tMcDHaEKFEHifLg6xdsVRah1Yayh6bfvCwxggFj7gouHhUC51EFVYU7aGhDNMRXsxy7YialoIC4mCUhT/DBVOjxCY4EIRCAcUth0e9oomvbbwVoUI5Gl7wSatXlBSG4Mg9hW+PfkEvfCAuUvdnNSx+t8+iqr3KWNI6SttXBKnUJGe6A2ikJAmzkEeMEzhQkCm7wjZKlPRRlxAZctkuTAZ3DnB7AIFsLi40ksRRncBg7NBcRdDb8ZTkGtGBjztSWkIQTG0qJOkuGKCQH74C2TxqYUkA4qXGWrxZcpmDoz8b6DIEd73Rxx2CLgDBtksEEQ9AHGC42iW1mfI3aHzBZorJgK8aEb1TR1PLdTlECxxMJOV5/JK3tWgtYOmDE5O8zKdCHFQ7hDzeTAAM2S4RQUeXukf4ADRbCbrckQkfYrR8B1rr2dJyn2i9uX+0VDaID2X4CgTUqq0cElaQUgoBhScwQDCFgAiYai4H3//+94tEegUIQxIPUy8u5MV1EUaAk/FBcDwX5rxAh2mIbcaf4j86OhLFSFL4yDJqYQ4jVoJmzxBV5fXCGRm8WhF8fQ9g5QtP2EmZXbaSykYCMJaCGomB1Frdk6vDVMMLcTIAxGV7D3sKJcI6gQlpoFGkol3D/SgLcQFd8oup4QkJFiUVEgq4VGMiYmtB3yODaIFMclHPfInU0mrpLXxbDmRdEABqIhESoBiLOzpOG84fQrA83QG8TB9xOEY1cHxjPXRHhZRWaIAoe+RF/jqEl4Qmoi4qR3kHieumg7XAyrcOouTHROWjVpe14cseGcSTTz5pGQhDlpbaOuyWE5oh23UU1K0uqwiadWu4DFStS3BB2f+z2JsWmNaSGrK44OWJ3xdZsYhrVAwHK0i16hABw9er3WMtGi1jpVEGKhp7BEATGakgUHrLsR53MjZYAiXVMgpZWSRTsAPYinVD6jJ0BPBVqRRVi4EsAydlPcYIU5HLRao80V/AgiCG+iBf8SUpLjCpqRHg+J95/V7273//u5O7FvYhJxzICvrD3RCidqvowoFhpZxiOjkJLNeGGYLaFYQDNVLOFhJkMVpaaoguCia5OuIJr9aSJ1DEI5tgpxav5emCKVGZ2hsemAqRKks5KeXyV67NeoLdJO8oGwu/sejjviRym9jJv2QIIUCrIbYQu5To7HgRKeJtDLx+fGNY4GAiKQfNj6stAJ4q9jl7uoSSLWpvZbYATpueloQ6j7eoOLfVK88SmKwlRBzHZHbyiJYuUgN4eeKvIjk1t8JnaQEGFgIQR1mZdhzRURNPgYAvLcBqJZFqBDtgilOGRHaIk0RvLIgqbRQC/vKXv7iR9LWjYGQImswi0nk9FZGk3aCGqaRe7SUSspY9ZEYQWXw7pRE+XmpSSfQAAi7JURMa4AvEbKWFeBoNhyYYVRMjjgC7gk0OO1HbYR+OPckQ9DXKpg0pI3bIIAyaRmlE2WPGzJ5oRjYcsiECWQ0ZDjqICKb//ve/vS7H0Qs37UaFkzEJL93uzZK5diWK3Zr2idaGYIH7kh2ZwzmtxqIue4YLX4J5A0ZIUrHAEDxt+HX7gVrgYCLprASuCRZKfIuucE2Ff5e/cFkpErgFsD0rkC0k7g5ARxFKBIWXXnrJWPgyXFHJJ5ke5Tha4oggTLFbhiWSRse5EjUIaghDWS8Ja9eohK/WWI2+4tGSBoQjHdNCHVlbyx5ZCKiRJLIe6TjsxC92cJC3dOG0kq1hEVb8MkQLNXXh0ii86CIbFTGfffZZj5jqginAiS99AyAuN7DAjRTBBEEmwrSUWbBmPTELdwOd/cVWBKWoMNUREaSMZU/32i4xvc2zJyEr4nvnrt2HTSQ0MPkpGOs0hUk8ZRDA1IFjLMGyobEClv83CQuZqStjBwgftxK1KBlxjKAxkeKjAqUdCB1dKKvZh8BgjQpGYEXX2WVYsIns+7HHHiuGGosmaXOAfUidzei6976ywMHck2Y1N1BdiqmtLqX16UbM1ZtHnuoSSoLpWu2MAi38KCBuUcliXID+/Oc/93clvHsxnNO7VoNTrAHAdBnn9s2XhoTBV1f3Yq5ERXCPcCwnh3TFcOtQfOx6zuI33KNFBdAItqhgGuIGzYsmsj366KPWdlEDWcsYDGj5YVpMLI6j75cz7CBeCI4CKJpu/d797nd330cRIol3VEZHyBOe3EuiJhdzjs6q6GdbP6nyFSeySOGVOuGohSQbiSzeRSocBOEImiKjl93ZEAXauSQRVZHycSiYjubFJamxvjYlrbO8w6+oBz9STQeBAWr4RFVPO1PUi69Gc01sBUA2ABXMGpmZCEw2gcwNL03NjiEGuqt19cEg8m6bkFQRvqJLASA1V5laGErLGuWsqimGCd91gSsLvytlEEwjHs1m7SxC130HaIHX32MPUKl9RRa8WjnlEWBnPdmWI/O0BFRH12nR2vBu11+6sxpFFivHclWDLWDojeZLAAA1dElEQVTI4oJHyx5gYSuAhuNSo4UNGY4XHW4nhAyAR9/KdDW5ZGrUUpIWJ7IAuY8UT1BwhvVNmNhEMB9vOa5CEJcNBNCLtOiQX5zVQk0ZKKmIoY4mgHjvete7XGsKjuKdXl0aaaS3kI2pDFTKic7IlgXK/lDWrheasOIzAMVw3CPoPYxzvSBrzxDvRF7DK4ygYFqtEaxGsJKQU2vEiIRZ3i7FmHpjhIidhsA2htJtewkxuhiBdikFd2KQkyRydtaTEXdJfSn0r4nc5xZ400VS7r4MSabHo0ZBQd7k6x9RzDIT5nRpVwaYaCIqPfXUUy+88IL3JBaqLKmk0kKCPGu+uGCU1dVRETWwpa4GQ/DySppmhXuNg+zjjz8uJiJSgbMhrZYCBASU1cKok6xFq8stp/O1D4/8gYzWNkYoEExNBtI6UBd5hdq41AWGXPBCzT2Gk7iakLoSA4AUEwmObgYYDX69pDJcLzEARjWEMRGJuP1GPBXQPQZAlikzgr3E2LgDiJq01VoQhKwYq8YCkI66phfgpK9GygewyOLuwCFqS5xZRvvR0ZHwDTAcJjoXL6glEq2FURuYW6Am6OLErync/xY4sNP9pRjUKcxSdArj6GpHQgnRrVu3HFrlET4AsiQcxJzCdBUOoK0W8XoBG96jw6l1KJEUB52j5WKO1boMqRjuEQ5Sgl2xxhGeFohgKv5q9GrIi2ZfMvmtOu64aDTKcCWVNYJR89j6x0ILTCHYpYQbCQTFaEdXwQvgEO0WAuuCBV4ivtsD95KyV98koUZTdMSpuCCIUVwQYRDSukbUqyulIMj4fA3mA3hJq0csDGExpbG0w1RX0kaTAESSHsJx1lYorrFrloZ3cldXnOsVQaogK3UFw8ykgFijr5BQQZzuHslAO0OIzbw+sRJSKWWzcZkAkxkv8YMk1MhDI8cCX7A517MSATJ+5r2u38AWuJwN+UANZL2RXG19CohyJWdSi3ZClUUIoceQYWoEC2GWpS9snBm9ehahZE+CqcVpeUMzEKaxsZAAoq9Y4aKDBaZXHJEFK167+/DI2ovjLD+MaqlGStGbSET1TkNC57tX7R4d28UR16yyTgXa8fGxLkuaeFIz94PCqMvBFaHbugOokx2CkZIgy6p8Ki8/lcHFNCPAkZa6RsBCQMRFe13pC1n6SR1AsAxUssxKbkUIIAR35O+OEo7hUwMYkAzq4Oo1k5MpC24nII+CteIwgWZXolQQQx01mB2C43y7AjR5q15k10MvoWJAeSilbD+PP/64/JcMWi6B9DWJQ7DApXnSISi7W0Zr0op1O+msKixaEtaweNQyNkYUWwYy+BrhqC1FR0XJjgglGXQBJ2BpVAuXrfZqL15Elhrlp5IXsQYvkcVJ3IlerxXe2h52AyASLHYQDGbKCE+++On4bBkLbX35JMAh+PLLL1MELMQ72Mq7hTBJGS7aqRC71EE/4tqTROhBXzAV5YWnFIFjlPAqEhVJPRprSBKGVoiUrcvRRHA4qPkgwfaDHSIuB4QzG4ytxfZDC+2NhTylWeix3kEDKPHFeoaQH+XkFM4AZlaj4fApDpPwYL2stKJy4SJoUpOFnSryIj5wYarXBA7GAm/G071V16IySwFq2ajoACiIWN4tTjgOjNpb8GDtirVqcQKcWCUgvr6W72gUTNVeFqsd9sVKxbkbWUtaAJX8vvrqqwj6TF0UdqIXYhxC9ToeQpNAOfDi6wQKTR2cJMSQ8wZ7i/LKK6/46MfpXotfEzgU+6v4uFvGXsr7tMhpmpDIiqriqagNU+RSY6fGdwgSGIwpmfVSVsRxLkZBBNSlxdgMohZnsWAH8hsFQQGIUML6jXWB786Emt5iQ/OIoyHeNUnJtZChb0gFOPILnRorZACgqVENAdMVj/XFC5hxyNZk0VFBTTsbYhQvxAnJVqwkcEcQpoFgyIZcvJhZ1ywI+uSW9SiIMjuQ/OLErync/xa4zklXc2Txt/6tVWuglaDWJYUpudAVWpNazgUuqYHsCPyZz3zGipL9CTEuy6xn8dRysm6hWVRqMVdiaEnLXDwia0krANTU4p1HvDwCYucxODSPBBMdxGgHbV2O+XgdHx+DpWCroPK//4uLPw/qgwRfy0q6BVm9aREdxD0KbWot5IGgUFnKTEeRVL7sY1U7Dbi4ANlAO8Hf/vY3aalRMjLsouMgTxg7ikzZtYP9yShdSsPRN1yoNSTz1hJZ9RIAE6yy7lmxrtGoWpY1mtoxUli+a1YXIKYVL2OVJIlpLRevvWVC8IknnkCKtGrcr0/3FzfsoVB4M0bSHN0MWYHLeRq/t+q0Vy/PaBv4SwohaxEHFVeB69Bx8pre8oZgbauVEaDFXCBYShKjJbsw1Yp2q1QtIfUJJwnRd1hGuV8lCut4aUwLARHHhhhFMLWCVJiQYcKvHawrxdlExPd1gT/l5w/lFV4hGIidwO0HZt26Osi7kXTCtZeInr5Ih2A4mvBHZbAWMkhyfUkq6At2iVGXGnf1FI+VSGn3iEKNHrWDya9OUxrZV5D16NAQgp3AY2pGZCnVsNsHQBkaBWPqAkSY9lsmjLREVu8+pK5x3hgWeDNG0rs6cxYz+laUhaRMlqddY2UE8Djw6wKQixRClWDtSk40EcusamHUayuf4pd2CYIwcYfgAlfX008/bZQ/GApfmMMrIoXLiUFYGCIQJKfgiwhe7jd98oWUrBYyjtoRcQMrdMp5hUWpqFHyUCwk3UUxLdAIo4AD0BfpxDXBV5jW6HEw4QcDpsABV9e7jQNBo0JCdkAzC7gicDjQmMzhQCYGgh6Hyz6AIbYTwkPOhvYzp5D/394d81aWFH8fl553wRMxAQExIhkhYdBCiogQLLDAIoFAEPMukJCAAO0mLCAECCESBMOuWEYQbkRMwOtA+n98v7M1h2v7zrXHnh3b3UG7TnV1dXXdW79Tp7vvsd05DyWYBkoPD3DvZfXXd9W3zgMLSa/5IzsbnBO6jRQcRF8qzKZjCwsgDAwJackXjPMgH0QK4HmutJXvKdsjNoAjQNLzuEEt7MJHl4CeJQU/viEAUGAKaAYIZI6ybCNaC4YXJe8ARQFSdqv9m0xn+BkAUimkIdiaKeeWhsB0yQDdo12yIWhDKHGiyeiobLGPQDLVdUEbOg4DMJuLWnd+0zQm4URfqtYrp9FpCnxiJcfSR6cCqDJEmll7tSEuZc8Sfkk8cJUv00ti+stshnCqFMyFN84EPPpS9heTahqEqOi19udZHhgJZjtF0sZ0QpAGchZdnihDbGcfU0ZpURW8WqM0+uTLuig4aTgb/9BZ+gklPcY2F4NKRR2EoiQQPzk5IdB8wfF2dhQOrCA0wXpoDqABscvZkaO8YsQ9wmXMadoKjJ9lo4ZTtBrFplZ0w41V5Ic+l+CKi4opsEGr2p3JzYxjjUKPQZupy2Z6rvLFvHseuI979zf6KQowG8rtCAsnZfaajYuOqb7UrjG1utOsoD3aC2MHnjxgQjQoJiXUBF4tO9pBfvvtt72HRYT7dXwDwRQAJ4116fCAIwRWP9GOZAWjPcsbJdtCouxvD51mCG6tQBJqT8lPCcC3fXmt3jPvib5VUbCIE1QxyRCVrdvttjuf4FeVNqwyKbfoGAGG+EpHBCWYTTz3otuXZySCKyouIaleasUl2sQNZDHXjxFm01+vNBPYGjb0DNrQU+tIieEk7G4kDHaqrKcEk9Kru0jI3hRG5yLusAdWTnrNH674VIKDreqzHK3h41bsAE2DVrUwllQ6OwUBndYU0jJNOKgpTPTeECunnsd7x7NeHuTBnPA2ouxSFikZlJyCxVAA7vTMDn0CQYigKVygnADUA8FerSQP/f73v29ZVrZLIRA3hGUEdGVmkc0u49NMj0t2KnJqc3FmixgB/CxUMyNwVEerK/G39JZDCWu1YjaKmVJuLJ+L1soY9j7jnL+ZtFfnEJptwVnndS9peTSF1exX+hqco3ex7qIHnn637uLsPuA5CcLikB3bSHM5/ONNrIva4SfpD2jw8O5Upv/1NBgB48CohNGSpUQ1NGk9VGzrS9KCAOS1xudRHUfemmaWkIcUatZmMGEQCXCpBayvvvqqF+7B3GCXJBzH94SLb9CaZgWW5vSoZ5SmzB7Li737mRJ5NPhjnjqBuqhxWKXMNNNJg6YKSxS0ms00aFXbuGeMn9Jax8ShQV8EbVrHvEY8vnZTkYbzfPeS7Nlqoz/m8TqX5K32wHq6v+aPTzh5xKtQ7flORCmeIpVao6uPHJ5CStR6/eIXv3DI3PknJ99194xvFI+ZUiTA4Wf1nrv9/LTRyfc02il0j97ADuL0ag+5rYP3fp7v+LpskRLCYBrBVL/a8n5PZ5VgEzDydOx0vSZP957NvWqATqgkQXP0XXYcjLokY1wa2JDNCBxT0IT2jKzQwBIYTdupj3ZeSl5NTKGkYiCFfITu5uJSrTDeUzwYVbOWDTiKX4vi+FEvy10GoDSYJuW6DzrvRntajQFPWTuKczhNOu+3TLJ7PIvULKQnvyVvLgj6Gyjmqu+wBxaSXvOHK5yCDLVCuzDboknMs6NuxbatRaNeAYH1TYuVElIo9u6773q1PtQI0Qxt5Q5EOo1Pg6daK5sIUR1+MUNgU6WGLyS9hEXKSSEQ9JuohiAv4fKLKQkvjPNTAmANnSlhjNd/eMeStFeWZ2jaFDgCx/2sHuCicYy7azmtttOJZkDG2AhiNvSHxXXBh3RqZiAw1WTUZZ1sUNwz1DA0SA1GXZIh6QGfbez0mhUrpN4nwr3hO4WDbhfBaJbQVhfKQbCJGMu4Xu3qn694byExCS8XUU7nqJ0pD4fkKnfbAwtJr/nzFf8TSKl2qQQKAAJ97pD421bBiQMsSqN0CQ2hp20ly3O/+93vQKFfLpGECELakU9ZnuVLHMN50gco0Y0usNMpjQKddIIbEAZAAbRLmz82o72i2Bl777r26yaZF4Slp8dV2igB3FYYvAXV4zmbFRw7V1ZRGaCAs4tAiiVcRI/hECYVYNEmLwb9tOHrbu4tceqCqQsgCz3pNxEdzZqMAuBckoGeYNdygTuE1Q/IDkaBneEUvSKeWRvIpIjN3cVYDHODsTDibQkWNBjGfsym8EydS+AOe2Ah6TV/uKJLPE+hvegVb4johiQj5vFdooEFQp1MxIRoACTv84+JABzscBpcQgp6YJxLaR0ZyaNVS738gJ1OqBcCprOxgkIAAU+BBZSByJ6Cbfc7ug8grKJCapv+PfLDr3LbYN0oZPT1nlOJKppAA4FjT9aWHbLWFJpF862mJDNOfbErEB+CW2aFxVQBd93dLei0iGGg/IOpLwwt/VQbSyvMdVcwTUhnFkw1d5v1/GMR1nP9gwcPjMMSehBbYw7QRtfKS0DZuEan+U9/+pO7Bf9bEe4jMzqCZPIHFK6mu+2BhaRX/HwLpB1W/E8loiDFlL3Q3fZKpuEn1F3WJQwCtcWqS309dANTe0T+tb180HO3UFesZgIR785wQhOggBVmSNNgCggYk2gAsjgIgKUYyy6/PBT0EMMBo471BB84ci42wC86pXv4zKZB4gn4wJ91BjL06Eu5c6ZWDJjBYHMh3JSbVBzMWmNCc3kxRDY18EdGFmwg83LGq9EZoEC03KIjJFVTJV/2igPTZ5W7glk7DKCjO4FXWDmblQHUKmNGl4drmsmXdSIsj1qY9t8ELLnyzHw09JM5rGq13nkPLCS94kcspEOEvTp1Ra9gQ2wLDgF9heIMTCBkiRNYVONoFcbkJZ4O20vfENKxj33sY+DM6HY/4IgIB2HhJtTTC7SBHkGOicAh7BI004YDf/WFoTagPcVbT4TOTqFCTKNQAo/gF3hKIZMiZIJlspAUojlTRbNRpMOQVBfnosyU2c1L60wWEx1frcSx/CrDhcWSPtNxDwCmLs0i/zQLQwM4II6JhpsmYjoMo4oxnugJ+O3ma6+9xiFazTdjjNtYY8xhwohsoA3WWyt46623bDHxkuGMrtadZvoP61mt98EDC0mv+CnvQODJ8zgVLqsFvzBDC1pFvMGRKXM5oyY8lxFp013HCVoY0Tl2YOeEk3VMQe6IPr6HccmXvjiCHwFTgCY9M7SApxDMgU56HIf0BP3hD38YhgJlWaGOHpP1otDekcTQgiBEoxOmBKmMQUA3askr8lkn/K0DtAggH/Q6fdAMagkzwKDqiJmmSxo0KYnxmxFBMBfJMS3UNhBJAjpmP8JACj6UZFhKzNdRLUP799qf+cxnIKlFAx2VfE5eX8Jjw2HC6OauOxiVLHsI8AsuyynM1sRLuhu9WYTmhxWu1rvtgYWkV/x8i8yzncVYoVuEC8UiX9QV1dMFRxGKcbTSOaFexIIPfLRHXY/28EIXTPAHzmygY6I9YnuoF/OKONeFjF51zB7RDqEcOQLEljgdk3KqyfF+iSS15VaUSwzhoy1v8AEQ6cHEYRvaXFgrAaQNAVzgshxWdkzGQNDH2SmzgNHNqxmxpMstc2ijG4Xr2O+pXKHKphZAN6LEUzGv/EOVVoMSpryzBzasLCnYCPrc5z7nha2apO2MIRnM9XmNe7fGnEv3YRmXVR7qpcmvv/462gehnulEsN9A5+pZzHvigfVgcv0fNFBQhBx0U+R0UABgwRqJEqKcLpCa4cWk6J1LBA1Joj3RS9PkjAACblICEyWkklO/ZSJJMxmrjboYDtixQUeDuoR30FOyqaPnUwCafjLM0MWlglZLb3HagPKmOAeVzKIUj0DYMfot2r755ptQzOqqvsBXk+G6AeBEzCVOxdBUpU3NTjJQT225QDE1Nju7anZmLeWsi+71SifDbNDziRVepmo1X3aaKQGX7MmTjXtkrRc9lFsAsXpr5yrnsJMGmhlDBs3miCM1L7E76YEnK1l3cm4XTWpieIhiUrwVzwWqVkECU2qNOTqnbxwyCtqDM8izOyGto1CvmiLUItyWtMOYAAsA6SJEyTSWS5qrDT2j/Pa3vwVP+FYwLYmCFY/nlu3sI0PqjjRRQn7ALj2PHz/WUYIpfyTfIgCMGPSkc1soYTaF7HE6lXJPynANXwlN0mwugYiDWU6kOn1lahwol/zpT3/qQdhwDUSsjrpsxzqGBuLBKK9at22pQUeuc9swKcUqxEXTOWaIZJrLVt58QbnZufHws6bcu5VZ9PJAHrinSFpgCxVeEN4TIYJ2cpm9sBdpODrqhZ7QrS/08aztdz6eoLWGNWQgi4RFLwI6QhbQoAZVIMCWSEkfGV3wyeiSeQORciJQZTOHmDco40sYYaI00Fjk9c0kFtKgGEUXQIAPtaVshtvO9KIAaDq6E2CktNfoBurH9ZiU08mS8Q9hR1nB2Ve/+lXdCThNRfJLX/pStjVWk4p+/tooY0DaWIU5n8vxQ7CZcOZlP5pytx+7TO4iXkuYzIgdr3xJ3hMP3Md10gnCWd4SJzYrLKuJfEtpLUfaA4EX4rMlsIS1Kn05cApmP7h0Tt7Guj1lTfSAYw+GCDGpu2LlTpDDJns7mojZnLFP7SEdMtpsaYucmCa70oZGNJx0zDFSymWjclJHR/ElSs6BWrNjoVrJMGrloV5lj4Ch1g39IMeOEBsoZLDJ6nIRrjUjq4qOT1JoCGuFfjAK5Q2dbSZCbDzDZjP1L//wbcFzi+GkcpZcZY68rZc6A9CXKkbZLnHS07ozbfjGSj9TcS6a1OER8wYNKedJs5P/+kxNxG8ffCtwGij/HFa4Wu+hB+41koqNAgN0/uQnP7EV88lPflLyCMVCNMEJT0UsQgmqfEv0gi/gA77ANbDlcRuO6AgQOxyOJia2RaYmwsUqPQSEqEKYhn/+858gVX4KZNvSIQ9edTE6JZDaap0NIqnlz3/+c3zLdoTZT6cuRkE7GO+/CSmw0quXndK37UM4aNh+ubdz2fKjJeYQHy3ZZKTkF6xYtQBbdpaaCPQJWYxFmwVKBruX2Md3nzBB6xt6OVaVbbmOJ8eHZ8c9l0M5D0wvBA6d87ngEDi37zFMs2gIM/LRmx1taLcutyLZqJuBqRnRQJVj1C6Z++aBhaSnuQYktRDpWdjehViSg/geCBuRDxDVZASYWrYIO2ATqPXYC7acqQQxoAR8EBBy4YXuaNoUyAgxg1R60IoA1gRAdbc1TBvlsC8YLTumQTz/+Mc/hpui2k69VkStMFQXeiS2v/zlL+102/BxPv/Tn/40jGaGEcGiEcmYEfOMhWbbgS+6QcNHc0ToBUyNJeuEXyzEdMl4iGMU8ja7yNjRgubgXhd9paWSYlhsLIPqy54D457bZAg2V4xlLokZtInMXFilNNNzVZ3L7D6kqecAFsJN9zZfhq9//etScgp9B9w4uzWeq2QxlwfuI5L61As/AS8yxaco6ueJ4hYiwEThJKjgnZqYUhe1iFJbRPP0B0aladAQlAg2eApfwJyaEpkdQKGBvMgHgvgKAXw6hbFk1ogSHzBkhQECeihmIflGFNJe/iQhhSa6fOpTnyKsiW1uAJ67//a3v1lCdQ9w+kcOmHlqENCyL4KwqYU+lAA19LnffpJpMB1G8k9JumVQySa476hp+Kg1/zQ7E7SuqteDBw+4xbY7jjUBMo01OHju0OcyGT9Fd8bzA8T0qaGH0KSQPFfJASZtelGl+GhIWu/2sdpichvQJDE3I8WHtYXyAzpX0z30wL1DUgHjY4ZEiAJDLMFN7z2ytwBioKrFPtCgBBPCiSQxHDQC/NljIQlcBDCsUcNNGJROgbfDzNNsFK1uxdBAEI1aOEuAPBpmYdqJprBfDfkZqLgtjCWbzk5a62SwNTvA5KkZRL6zKxAcLtvb8VjNAF0MxxiAaJqaykkxDRQ0q03kou+6USgho0BqFipGYa37hLn/61//Mh0TZy399LAfAdTcBkgS8NN78rpbtQCppkaMVRcNeoDPM1v8YhU9LFRviQMaDjelnLdNiqR7knUMM3W8v1kT6O7iu9G4hxWu1vvpgXuHpALDJ70DilMwFY1qAOfRFX5Jpqx7ej4FDWKYpBiDO8QicJzysUfv/DkBoSUCCQOO1gdBJEABajhhcQKiEcfTomwOX1gqgIkkk6gCWPCOJXATSAEjTMfOLeDKBz22e1cewo68Z/y//OUvhB1Edyyf2aZDFaxkJwuBmiFiso2R5khA3dzVFxXCxmUSbUyiik6Wq90w3GNYYj3UySpnuUyWHpZI3MjoYnb8o0B2GCpFtZmml+kziQYyFw19Lj/wYgbjt/bH0cU066jVHPPAuaouYuYoA7nr/PWvf3UDsAzNYB+ous/FBNmPvkjJ4t9zD9waJC2k59NyKeB9ubccdHElqGDBuUWTmCGZQvJFICSViXhG9kYPT9kek3UXOYYQYNAQAeZwSNqOgIBBJD6F+AoarCBqCmHRmIAGQdJwon0nfvoAzgwDZTa+LlI8W0wsAbsSUqDppJTtIzv4HuQlrVI8z/hOJnUM3lyMSwM9weWgScPhx2mU6vHbWUKv0TAEMR0ZD1+sPzgt60mftYzkFl0UAzm0YLLemQRA3ZbcFezjPXz40DRh6GVhdGxjRmWM37vMPMzpcoDwzaFHAZRsznjytvXkoX7LxFpTw8mxCJNCH9C5mu65B27Nl2PveyxmrFTKfXzpg1RffSGNFuqYRVqwta01papeEA0h44CefiyEBltvvPGGxTIHy/tyQMAhJIkd0qSfnmoEGYU2CBuSukyAzHzJGt0oTBLGGZkAJrFRKO9jD0iFTfh2bzzUe5nbd77zHTOlPJ2jXHfDKduxMM9KGrqBRvIYIpfW10rFye4HSKySZlrnNQoBMKTJpZ97st/dSLF7Yx/fyolRxvnHjHjtMk0htfkt5/giMdv/YmGnI7F7brx2M5bCO+mBp4H38k+vMFYDCNgH7P7whz94+wbLcSCOqAamogJI4Vw0I1FESVEN9RA6ygHlpJokenR6frfhAJRJFm8IMQZGbfLg6yjDKurQCoCroHVRtFKoZonuamNpZVujJ1ArSWbTSQlhqweSXyfw4SkYcjDevDTpolX3FOqro9pwW75RSCqazhaS9TrbdICji1JfebE1EJfOrqp7XyemVsXdyKIEMJU4e6UIwDUFfJIH9N90EwMMwSczizgc67vkWcTtwafPtzdtydJ/9zxwfqS9hPP0pQdA8KIwsGJo7wV6vvLKKyAPsOJrJSMrdAlKLpqFWAqJdAlr9IJiAYEmvyUHBOLfeaOCrZqYZUqSFv4o11cJN5+A6C5bZIYSlhmiQgOCcrVeE65ZQjNCF2JJWmGk0w6yvSapt0mZbEY2r70JjuaZdaqMFZEZWhE4I3YkQQ8j1bq78Ridu0APIz3Ls9CPSqlyJysrZ7kpyE8584c//KF1VSBF+MjhbkIsz9OMyF38YEaSfVPoxaxNTf3BmnoT0186b9QDt2adtM0EMcAd1rYEgM0NTE9kNmdsfdjA8e0PXyyBSe5a/9qr8SmpUKIVjWkhTC9KaEDgQ1Lv9DUKBBFyxKwMegbUZHGQjO6EK4ZuOz4MpW0IYltj+jh1p9NETGEKDlpNBuHk0w9+8IMWWJ09slcvb5IRWzC1JcUMQ6vJ2w0zBJ3b74pLNuPET60Rh78VPoY2KCUGUhCU8wx8pJNnYKj1U7PGxLFIah/c2TKpq9VSZsv0WXvMQDcnkyv4liWKS5D6s5/9zK3Ry7HwWd43IdfdnCVL8x3zwK1BUt/svty++lIGwSwMQInEx9OZB3MB0GdDQKuQEO1k9moymtQ1dYlDuUwWGpaVOCIqz3rvvfc8nEKH3eD/zwF4+ydyMfoNJxQjCCggDxOkquMwb2A02G0KxfBYyAYxrMAgxito6ed//vMfPw+1deO/aPhtKFh3Kh7Ws9BJHesPHv+dhGe2Lh3FN9kKDnhVG7fJGroRu1RfqlBL1TiZNhyWcCOrNLHHrrf9ejINylGYBnXG4I9//KPjrjNxzL1CzzPtMeL0OkZ+q1Bfl3rxieKDc8mZfp/Ggd634u5oa9GNE59wn9RWw6KXBw544NYgKWQxDd9vRawKCWHplz9+AgRNEB5+IRFmYUCA5Lk1ZiWBaMrFFc2T3wFTaSB8lF6RIQBY5YYAomQQXmhF6wVAoSdCLUqrCbBHQdCAMOJgwQ48TzNQhEEZr1bEOQ7N0m2PzG4SmnDMEVMC7oARVGWVsO/MJsOsV8pYeYMkM9wSDKpIFQ2qsL+hZ744xxcDUUuewfSkBMdwclWZKQyVxUMl5oWwVmC8K8CCr7uRH3Fhmosp6M6evToPH7Cnj1WvZJ4pv6eq4TC5V1+fBVNt6Lk7+lVYv4bwIRIwULPb07AulwcOeODWIClQEH59y80HuAhg4SFuHV6RUEAWUAjFEtN0oEwciis6KaStg+V2k8Q8VXRqtfVsBQ1eoC2SerImxhihWEIKTaaEquGs1sHQgCzDjEWbQSlUDFS9hVHmWbyjwRKwnJRCI5oyGNKdWgTkkgzaibJR7v35YIsZDic5bcpm2OpHU7LactXckkOuBhM0GNr9jBLGsJ/Hmqlx8dlj30nOrtglI0Oe8dJSU3MDkP05/aoLPiV79Xwi+OeWjJ+mZ8qPZITudVFnmwUHDzRM5T0ybjnZxtoE9jSsy+WBAx64NTtO4Mb3GxgBFDVa3uer79K/4RUSUkW44zKxA3PeNk3MUAUdKJGqAFPQ4NK2iWNP/mvxN77xDWdlPP2V4kle2ENASVsmqV0OMzqmuoL5Pnn617iAmzb8reWQ0S+pDJeF7DFfMhUdh7DDo5DHMTRtTIX4LAemdqV1NC/5oBfrKeXUTxTt/nCajgZSEAo2emQyz2VYg2CAQREjTIZm+Z0s70c/+tHXvvY1lxzllPtbb73licEz/qNHj/y+QK+mw9TSwNGT5p0Jp8/XCg+Yi4m4tZBnajI6MsZ0wLeatQwgry+Z1I7TKMHB14UMDXxryduyiY9YF8ymo2nmiF5leeBIDzyNliM7fOBiYqZgKGzUAgn8OcwIMgQPTkF1KVMDEX1FFMQU+cW5RVhJlmCjnEyAZSNFxkq4Ic4lYqojDhhDYCY1Ck1H5Buu7gw4oEFTUybMfqVTR3oBIAUS2T13hIsATFHcdSDdFlhpCHrOHTFmteForgzuuMQ3tP160/GbAicfnK9wW3K+oh8UcCx8N+hWj3HJG1qdEq4mAJHls9JqH67PAkdJIDpgJcxLHk1goklpoodO6OlDJI9gAyJTG8irZ7io/x1w2LGrdXngGA/cGiSdMBMqJlZEqfFhhCCBDtsJJ7blHKYnnkNMwggxbFdaMkWbsaAqDtoSqsu9ogtOo9RUrxk3poGUbd9tl9FARndYUGsooBVzFG4JfuhSxwiSmDQw2wl5TJgiF4NlYBqqZoNpErDQqYAk8K1jZfS7HBqRGTHJx1HjGJEeGM0Mqy4nJyfWHwCWbLRlE4u/ISkBQ+tiahTOx0ch2yy5evqGevh0MgzRCgNhY1kVyX4yANrbUrw5wecCtQ2nSx4byxGZbVAoD4UBvTw9bRSusjzwPB64NUhqkkUCIrAoNqCDw4B+jyRiNQmhqRGXLTSLZImV+BRyjx8/tlXyzW9+kx6XUAkMwR2jECPMpLqodWHSGEAggzGV6OmCqKRnS6dTF9DDklLj1Kbk3Elpit9YaDozWxPb6gvm5Iz4OPDUdBxRQMgZTZYMAbAllzRNo6eNKnayATypMdOPzy3obaGEGAwlCRMNAd0848Mvwm57htZETF1HdJkjTPTQ7SHAcEbPAMORtDpMbAay/usySwj7iS0NwNcelwRcfsr+hEtIG47Bjj24OzoUAUZZEuCO2kUsD1zNA0++ylfr/OJ7TQwLALTwePvtt5lhwWsCJqu2UXeMncW2UC+66Jc9ORHpnUAx1dIi2ZYm/8nS0z21RqkI0SkME9uZmtpMVZPRpEvCCJfoOOp02vSAKXI3UBJntF00F33JJFY9ki4ZP5ckFRz6FftpbLACUK5qjRUSQUBMo4e8xCY7rjttqc1daHwTie8S/YlPfMIeXQDtEMLDhw9///vfgzxganEzz+heNqrmbamoV0lRIrvEcRchRpuS/Yg49Gt1ogDH/puDDT4RNvz73/+2tWUusHKWOKY7e2TKslHrAISpSu2qlwee0wO36Zvkq99sBUaxIWDkL/7BBoDThJmMMLtCkIhM2AEoSwNBAHSW3dCGST9Ysc9rRPlvBmAakUAF+jTuqX3vg8vQwwk9dRmi7s2OQvgiT/SaklBGq77qA5OaJmI0KLpQGD9Ol9lDrOFcmt0OVP8/jqEV+R1sla7yMPSxTe/EFSfzgAMDsjmeqftgND0z95qshLjD6eWf95mpHztJBp0r4Fg5bzeJMRJ2e7bwhG4ImOg4F8y19a9jFtLZEGrT4RngjjCRDHb7ISwVxXS8yTEGW5HuE+QJsNPKxqNHj8Arq/ISgwln7aqXB57HA7cJSffmKTDsSPh1ioMshZNoKTauEB66AJRqSCohVcuqJoxdQhBB6OVMBiJpUKVQF6tFuBozU9HsUbsknwB5hQyFio4KIiYxK4CAzCO2JYs6pk2XoKrLc2vd8WdQtF6ZMfIjU2sC02pqCpACdn6exKo2fOApgltsBFEIH2EWMc7XF2dKM8WEtrrT42f4Hh3kmx7zpZxuRRzb7pCxyFDu/wKQJ6yjJ3TnYR2r8okQUDcjQyDoz1djMwKw1sqTuuDIrKXA6NaI3RggOPh2eIB78VO1VbLo5YEre+A2IalQaZ5iSfxLl/zI51vf+hYkEhiYnojFdnGCeSmnUCjM0iCk5UcST1BCibgVdQ0howFwzuUU85oIBCJkokki2IMzzAArFAgIBkZPoXRXmhdSk9PsIADHZQpn+pSfLSQxs6TWOBmAc1YAs1ZNo5ydLuOAMAWmY0oV1YzhIisAnN86gNeU8BtUJSCxhbCspc0c624UTfDLY7UuNtm9k5ADCYM/Bli49N8H6mhPyWFbtb6UGNEHSibzUohmBpqReZIYAlMxnGzUSXtJ9N///ndN3/ve93xYcFxG7FVPaaaT8GjWa5XlgefxwK1BUmHje2+qEWLST3okGuLzCvOnSowpxZVsS1Q7CynkMD0DquVNaRa6SnFrgc/Kqf/n7ulbuGL6UbmnUXogOCW66Bvs1hGn7qOEgI7gUhe0kA5VdY8Prb785S9j4ihk9DXEgZkS2Gvd4+xdboW3TegKS5TEcBCcBjQVXnKPYRWmFQCfhaVPGZ+JsBYmuhW5DSBc6sVynJOTEyktVwPHX/3qVwSoMkf/W8V8CdNmIMK6cKC+0epKhhHTpFbYQAMY1aVWtDVTo2g1hMzUN8Sn5tti5dRdoSGIGSLH7jStannguTxwm5BUDDRXYSOjEZlCWjQKeE1H1qfwsANlUaQEeSLZNoVD7AJeEMplvvCFL4jehiOPKFAxpaX+BaltYmt5utvuoAemayJTVAt1sIKzjXA0SfzTgXdpFGGIDFl01Gqg9lv8L7ZgaGsAmgDJmDdU02+UBjpbbwdNUr5p5dQzvnmZi1zVqgt/wkdP6D4dLiXQ+fnyUHN0VMATvUupIiAmZvpSUXVOUyvcWxNCkxEruYJ53IiTS6dJK45L+/uSZQekfDpWGBxaoLMpnM7w5p25ddei77YHbg2SCiSfRAGAECdgVCjiXOoTomdCKEIt0iz/gT9h7xSOtVdRN2g4+g2KhqTeYG9PQ7gW5/hCXZRCEJxMgpL4ShCQzQFokY9O0qNuYt42TYk3fdgn2aZdY8ALIMYn2bZXszMbItQRmAxWfCLm5dINiTdkrJ6ppasQUytXWBuxawdJPdGbbCf2eW9gNE+qeZKAXurKbrRTAxAMU4xFp9ZuYPFxeDg7faBGl7d++9vfpjOfkyeTnsRWvTzwnB64NUjaPIUKQgiFU0IizqW8MF0iRqeg9QwIIq290l/ka02gcRsImH73u9/9zW9+AwQFvOTLJsnsY5AfGN31flLtYv80+ItnMtGaTcTJKgkdGO2oI+FGRBBo3CG6vIm6Ic4OxNQ9ZpdmwQz0FHPBAWEepd2QTk5OTIGj5KoQ7c9//nOLqp70f/3rXwNcK6dwjYyOFZDH+QpCeZ99+lkrDZd/WDWjIzBxmGR0tU8EpLZu4DEfoXsdQ1JdVlkeuBYP3CYkLZh30fQEWbigwLiULwYU9BWlQk6hVg7lkbOX5wtgYhNvJAm4jIn2Qxq4YEvaAUYI6CFX9MKFBEgqmRrBQqM0ND5CkOMYiIC1Atq8ItO5S0DTvJpa415qgjchzIysipghOHDoBEyN5Tmw+eI79sBF+loH0EVSb6PJPx8Edm4hpuw1KM1Ux20p68c5dej7iSRJ3lPTT3kdDeoS/vKqIQjnYQm+1Ngzh3MXWlOSzUlGr3p54Hk8cJuQVMCEaCY88TDMmo6p94LfZYHq/835Yagcik5DCHKBl3Bq4wtXowNNoAAg+k0OMJ2oJqyI0j6YeulCQMlyrYojkHDE8qKBvCITvlgzrS+xOm4/XU1nmVuBm6YvGt28so3ZW4+hpwsBYpDRPcMigPuWRRUYqos9IqhX32qfiL57haqKaSLUFEbrroshdKEZgY/j9mZjUEbs5X744awhFAKE1assDzy/B24ZkoqfgqdIKHoLqiNr+KWX7mqqxFJR500ZnjdfffVVPsXUJAiTIdygmoySfK2eXi3AOS/lSV8+65J+8jT4dWMm9SGlE03AiVFoQg9JiGw3BoxKnYwCVVsfrNdevVW413TTlxcNzWZNJjIGADVMs8BBuCRg+moFE23uvO2JW2oP78I4Sg6Xuu/UPFFFfsY1VsNRiNkH7VAqVzPGrY5vW1jXqw8xhaNhEcsDV/bA0y/ilVW8mI4CQwQay7e/MEAjLhsMhfH0KhTFnmCzI+9wj8inWUIqPWwItSJKt3GbPWr8V155BZiWnArXtEl/EIazl2I4C6np0QV6KrQZ9POf/7xtZbSOFvIUQ+tFbfIzX4QS8+Wptz7JqoAMfiG0KvMxmbsp4JDUCtdc8g8BzNPp7Vqj1QpXRKgTUOvOP4g+lOHHcZND+BzZYK3Wy7llvpOo+lx0J6A1OrNXvTzwPB44/Sfpz9P/zvT1pCm2FRFrUmJbQXR57jSTKYzRQld59OiRVTnRa4MFcNS9gE+JIQS21zPbxZbDlrudq/9OMjmKN+Djm2++6aXUCMBnyw7BM7yxLZBO4UNNBLiaT3I4gh4ervA8ZFS7D7lLqZXQE9NtzBGCr3zlK9202EBJ9Z108prUi/fArclJb9o1AljEKjPQlh7mHlFsYyLIC3jH+4UoGFWkmYJZYAtmrfJNJ7csrcp2A4jpvqf2Dl8GYXkM9nG7ZZCwdWbNgXs0Tsw8Fr3jnUoOEyd6apoJqHH4XD0yQ8xYi1geuLIHngLHlVXcjY5AcCYixoY+TGyjUdIELuEmAlx6dH1mXxF+DF4f1nPrWnMvh7vNuLWwH2db4kzdBAmEgy4DyuHX99zLkSTTU78c1gdEGKcuq14eeH4PLCTd92FhuU1e9iU21xOo8VwK12kfVYl1OZKY8Uf+XhEyRKk6XOMEt5NxRV4aX80l56DVJCPOyuCk0y0KTVKZjtuPZpiIVZYHnt8DC0mf+LDg7KLwQxeNF3mZWL22YmilLluA6AH/HmagZ72XE6x1ANN23vrFLX6uCwfVcDYfniLirmw/lBFDTNm5/3QpdohOSoBRKwm6U6MOwc/atjjLA1fzwELSfb8VaXEHE/eFzlyPZAHf5QSzwC6M9RPDiibPmMp2uDNa7yYjJLUAYsPN7tyckeATjuIcAt14zD94zZP5No+pd448PTsRoYuSZHVNVmPtPtniKyetuyY4fjf9u2b1QXhgIekTrwsw4Xf8R0BYF0WXbUchihNYIAhEC2ZEe9AA9PiB7phkfjMp68h+gO9IKU7oWc1LO7+eVgnnrmjMvKoOKKuDUfXAK74uSoSDq5DU5dafLlO4ZS56eeAKHlhI+j9OO4289yFy6P+ReP9CK3Ib2LVsI1MMj0w5aVEdWNDg8r6hav4xcSmhnJR/+MEDOIJb8EEhGXTFZY4tf9ekxEm4mlhEZ6FAqktihOlBGAuBr97pOFVC5r75P9et+to9sJD0qUsnwGLtXT6V21HF5x5z7/KszJZD/70NY3gnQ/RzW/9hxYufP/7xj/OM/3ECVXvoJgDmMHHUHKUgOI2TMV2qAWjZaACqS7+MIOa9sZZf/T9tJ4W9IxGS1isNHdWgZO8jW5fLA1fzwELSq/lt9bqiBwAfFJtV44cPH/rvrf73ATClEdjBxPJQYqCz9RAQqQkIhoNoqyVq/AHT0lJdDOH9hF6/jXYg35tTvNCAcn11uaLdq9vywEEPLCQ96J7VeN0eAGclmxTDNT/PhYl+g++12bJUj/l+j+Qfh/RDUvtRNqZ0kTzCR4SSRZQg1EpgGi0DBaDe8UzSK6V1f/3118GxsZTrns3StzzwxAPr16Lrq/CiPSDr7BEeAoJISPr48eM33ngDevpnohAwvrxV6yl2bpZN0cyFiRGhJ/kKPsAl4NJ/c1J/8Ytf/OxnP+snAGBal+n4oue8xrvrHlhIetc/4ZdvfgAucIRrihSSjX6G/95774E8b9H2AieEVw56QieZMLFownpV7xQ8ecYPVeGvtVEP9YT90N6/hgbcEFn3euG/fC5ZFt16DywkvfUf4e2aALwLOsfsOGr/z9V/07Jl5JFcckrAw76XmwSg6m1H8mmAj+gKjrcjSks/8pGPOPYkG4Wh/k+UV8YQo6F6hl7E8sB1eWAh6XV5cuk5ygNnsQwH9lnKhHT++4uXE0pOIaCHfZvvWundJqTEcPArMFT2Ks91iZaBPnjwwD+FbrOe5MhnX5fRq14euC4PLCS9Lk8uPcd6YNZJrZBCTEVPUBghhXz33Xffeeed/udorzgBf705GwExQ0O4+d///hcKK3TSoLz22mteV9jvpkat1pB6+h5r65JbHjjOAwtJj/PTkrphD4DF7cO7V9z/4x//cDrKtj7cBLJaqxPrFBToBKP2rz760Y+enJw4CXD2h0wMD3lveAZL/b32wELSe/3xvzyTh6Tlm0waSPV+E3tHVk79IyYnomwlyS4JkOwdrx/60IccF7VJZSnAXlMaztZ1eXkmuyy5ex5YSHr3PtPbPSNppmdwD+Om0cM4kAWmijxUKz6shLbST1tJ8yCPLz9tiWDrggHWLXPRywPX64GFpNfrz6Xtih4AkQOCADQtiMlPt3oTAJExQW2Sw9kKL3p54AV4YCHpC3DyGuLZHgCF4eAeStYTs+KSWAWAwl9QOxD87GGWxPLAzXjg/wCrWlHADFCeBgAAAABJRU5ErkJggg==)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "dIdQ9U3yTKtP" + }, + "outputs": [], + "source": [ + "class Model(nn.Module):\n", + " # define nn\n", + " def __init__(self):\n", + " super(Model, self).__init__()\n", + " self.fc1 = nn.Linear(4, 20)\n", + " self.fc2 = nn.Linear(20, 20)\n", + " self.fc3 = nn.Linear(20, 3)\n", + " self.relu = nn.ReLU()\n", + "\n", + " def forward(self, x):\n", + " x = self.fc1(x)\n", + " x = self.relu(x)\n", + " x = self.fc2(x)\n", + " x = self.relu(x)\n", + " x = self.fc3(x)\n", + " x = self.relu(x)\n", + "\n", + " return x\n", + "\n", + "# Initialize Model\n", + "model = Model()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "SfC03XLNXDPZ" + }, + "source": [ + "We will now need to split the dataset into a training set and testing set for ML. This is done fairly easily with the `train_test_split` helper function from sklearn." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "agmbEdmfUO1-", + "outputId": "e7de179f-4150-4e03-d04e-2f0ce4f9cea5" + }, + "outputs": [], + "source": [ + "train_X, test_X, train_y, test_y = train_test_split(\n", + " dataset[dataset.columns[0:4]].values, # use columns 0-4 as X\n", + " dataset.target, # use target as y\n", + " test_size=0.2 # use 20% of data for testing\n", + ")\n", + "\n", + "# Uncomment for sanity checks\n", + "# print(\"train_X: \", train_X)\n", + "# print(\"test_X: \", test_X)\n", + "print(\"train_y: \", train_y)\n", + "print(\"test_y: \", test_y)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_FrQXhAGZGS3" + }, + "source": [ + "We can now define the parameters for training, we will use the [Cross Entropy Loss](https://machinelearningmastery.com/cross-entropy-for-machine-learning/) and [Stochastic Gradient Descent Optimizer](https://en.wikipedia.org/wiki/Stochastic_gradient_descent)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "9PjADXnuXbdk", + "outputId": "31588262-5e9e-4db4-82dc-ea3215a638c9" + }, + "outputs": [], + "source": [ + "# our loss function\n", + "loss_fn = nn.CrossEntropyLoss()\n", + "\n", + "# our optimizer\n", + "optimizer = torch.optim.SGD(model.parameters(), lr=0.01)\n", + "\n", + "\n", + "# use 800 EPOCHS\n", + "EPOCHS = 800\n", + "\n", + "# Convert training data to pytorch variables\n", + "train_X = Variable(torch.Tensor(train_X).float())\n", + "test_X = Variable(torch.Tensor(test_X).float())\n", + "train_y = Variable(torch.Tensor(train_y.values).long())\n", + "test_y = Variable(torch.Tensor(test_y.values).long())\n", + "\n", + "\n", + "loss_list = np.zeros((EPOCHS,))\n", + "accuracy_list = np.zeros((EPOCHS,))\n", + "\n", + "\n", + "# we use tqdm for nice loading bars\n", + "for epoch in tqdm.trange(EPOCHS):\n", + "\n", + " # To train, we get a prediction from the current network\n", + " predicted_y = model(train_X)\n", + "\n", + " # Compute the loss to see how bad or good we are doing\n", + " loss = loss_fn(predicted_y, train_y)\n", + "\n", + " # Append the loss to keep track of our performance\n", + " loss_list[epoch] = loss.item()\n", + "\n", + " # Afterwards, we will need to zero the gradients to reset\n", + " optimizer.zero_grad()\n", + " loss.backward()\n", + " optimizer.step()\n", + "\n", + " # Calculate the accuracy, call torch.no_grad() to prevent updating gradients\n", + " # while calculating accuracy\n", + " with torch.no_grad():\n", + " y_pred = model(test_X)\n", + " correct = (torch.argmax(y_pred, dim=1) == test_y).type(torch.FloatTensor)\n", + " accuracy_list[epoch] = correct.mean()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 432 + }, + "id": "2fHJAgvwboCe", + "outputId": "e9b49c61-b3d9-4a61-e439-dcb239e1342f" + }, + "outputs": [], + "source": [ + "# Plot the Accuracy and Loss\n", + "\n", + "# import matplotlib\n", + "import matplotlib.pyplot as plt\n", + "\n", + "plt.style.use('ggplot')\n", + "\n", + "\n", + "fig, (ax1, ax2) = plt.subplots(2, figsize=(12, 6), sharex=True)\n", + "\n", + "ax1.plot(accuracy_list)\n", + "ax1.set_ylabel(\"Accuracy\")\n", + "ax2.plot(loss_list)\n", + "ax2.set_ylabel(\"Loss\")\n", + "ax2.set_xlabel(\"epochs\");" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "djB-UtvgYbF2" + }, + "source": [ + "## Congratulations! You've just trained a neural network\n", + "\n", + "**Exercise:** The model provided is very simplistic, what are other ways the model can be improved upon?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "JgtwrbMZcgla" + }, + "source": [ + "# Step 2: ZK the Neural Network\n", + "\n", + "Now that we have the Neural Network trained, we can use ezkl to easily ZK our model.\n", + "\n", + "To proceed we will now need to install `ezkl`\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "C_YiqknhdDwN" + }, + "outputs": [], + "source": [ + "# check if notebook is in colab\n", + "try:\n", + " import google.colab\n", + " import subprocess\n", + " import sys\n", + " subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"ezkl\"])\n", + " subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"onnx\"])\n", + "\n", + "# rely on local installation of ezkl if the notebook is not in colab\n", + "except:\n", + " pass\n", + "\n", + "import os\n", + "import json\n", + "import ezkl" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-b_z_d2FdVTB" + }, + "source": [ + "Next, we will need to export the neural network to a `.onnx` file. ezkl reads this `.onnx` file and converts it into a circuit which then allows you to generate proofs as well as verify proofs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "YeKWP0tFeCpq" + }, + "outputs": [], + "source": [ + "# Specify all the files we need\n", + "\n", + "model_path = os.path.join('network.onnx')\n", + "data_path = os.path.join('input.json')\n", + "cal_data_path = os.path.join('calibration.json')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "cQeNw_qndQ8g", + "outputId": "c293814a-9d98-4ea6-ff96-f32742bca9a5" + }, + "outputs": [], + "source": [ + "# After training, export to onnx (network.onnx) and create a data file (input.json)\n", + "\n", + "# create a random input\n", + "# note that given that we want to use a batch of 2 we can provide a model input as follows\n", + "x = test_X[:2].reshape(2, 4)\n", + "\n", + "# Flips the neural net into inference mode\n", + "model.eval()\n", + "\n", + "# Export the model\n", + "torch.onnx.export(model, # model being run\n", + " x, # model input (or a tuple for multiple inputs)\n", + " model_path, # where to save the model (can be a file or file-like object)\n", + " export_params=True, # store the trained parameter weights inside the model file\n", + " opset_version=10, # the ONNX version to export the model to\n", + " do_constant_folding=True, # whether to execute constant folding for optimization\n", + " input_names = ['input'], # the model's input names\n", + " output_names = ['output'], # the model's output names\n", + " dynamic_axes={'input' : {0 : 'batch_size'}, # variable length axes\n", + " 'output' : {0 : 'batch_size'}})\n", + "\n", + "data_array = ((x).detach().numpy()).reshape([-1]).tolist()\n", + "\n", + "print(data_array)\n", + "\n", + "data = dict(input_data = [data_array])\n", + "\n", + " # Serialize data into file:\n", + "json.dump(data, open(data_path, 'w'))\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9P4x79hIeiLO" + }, + "source": [ + "After which we can proceed to generate the settings file for `ezkl` and run calibrate settings to find the optimal settings for `ezkl`.\n", + "\n", + "Instantiate a PyRunArgs object and set the `batch_size` accordingly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "cY25BIyreIX8" + }, + "outputs": [], + "source": [ + "!RUST_LOG=trace\n", + "# TODO: Dictionary outputs\n", + "py_run_args = ezkl.PyRunArgs()\n", + "py_run_args.variables = [(\"batch_size\", 2)]\n", + "res = ezkl.gen_settings(model=\"network.onnx\", output=\"settings.json\", py_run_args=py_run_args)\n", + "assert res == True\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "pe0Zoe-ri_l4", + "outputId": "ed0fe6fe-67ca-4da8-ba9e-d6a8ee451ded" + }, + "outputs": [], + "source": [ + "# use the test set to calibrate the circuit\n", + "cal_data = dict(input_data = test_X.flatten().tolist())\n", + "\n", + "# Serialize calibration data into file:\n", + "json.dump(data, open(cal_data_path, 'w'))\n", + "\n", + "# Optimize for resources, we cap logrows at 12 to reduce setup and proving time, at the expense of accuracy\n", + "# You may want to increase the max logrows if accuracy is a concern\n", + "res = await ezkl.calibrate_settings(target = \"resources\", max_logrows = 12, scales = [2])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MFmPMBQ1jYao" + }, + "source": [ + "Next, we will compile the model. The compilation step allow us to generate proofs faster." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "De5XtpGUerkZ" + }, + "outputs": [], + "source": [ + "res = ezkl.compile_circuit()\n", + "assert res == True" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UbkuSVKljmhA" + }, + "source": [ + "Before we can setup the circuit params, we need a SRS (Structured Reference String). The SRS is used to generate the proofs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "amaTcWG6f2GI" + }, + "outputs": [], + "source": [ + "res = await ezkl.get_srs()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Y92p3GhVj1Jd" + }, + "source": [ + "Now run setup, this will generate a proving key (pk) and verification key (vk). The proving key is used for proving while the verification key is used for verificaton." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "fdsteit9jzfK", + "outputId": "5292ced0-d79b-46fb-df13-fd16355dac90" + }, + "outputs": [], + "source": [ + "res = ezkl.setup()\n", + "\n", + "\n", + "assert res == True" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QYlqpP3jkExm" + }, + "source": [ + "Now, we can generate a proof and verify the proof as a sanity check. We will use the \"evm\" transcript. This will allow us to provide proofs to the EVM." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "yoz5Vks5kaHI" + }, + "outputs": [], + "source": [ + "# Generate the Witness for the proof\n", + "\n", + "# now generate the witness file\n", + "witness_path = os.path.join('witness.json')\n", + "\n", + "res = await ezkl.gen_witness()\n", + "assert os.path.isfile(witness_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tmPmQrL1pF9p" + }, + "source": [ + "Note: Instead of having 3 instance variables which corresponds to the outputs of our neural net, we have 6 instance variables since we have 2 inputs in a batch." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "eKkFBZX1kBdE", + "outputId": "59db6c3f-22d5-4258-c70c-6b353a5173b4" + }, + "outputs": [], + "source": [ + "# Generate the proof\n", + "\n", + "proof_path = os.path.join('proof.json')\n", + "\n", + "proof = ezkl.prove(proof_type=\"single\", proof_path=proof_path)\n", + "\n", + "print(proof)\n", + "assert os.path.isfile(proof_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "DuuH-qcOkQf1", + "outputId": "a5656e5f-1f81-4236-d9b9-8cf80e020a05" + }, + "outputs": [], + "source": [ + "# verify our proof\n", + "\n", + "res = ezkl.verify()\n", + "\n", + "assert res == True\n", + "print(\"verified\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TOSRigalkwH-" + }, + "source": [ + "## Congratulations! You have just turned your Neural Network into a Halo2 Circuit!\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "flrg3NOGwsJh" + }, + "source": [ + "\n", + "# Part 3: Deploying the Verifier\n", + "Now that we have the circuit setup, we can proceed to deploy the verifier onchain.\n", + "\n", + "We will need to setup `solc=0.8.20` for this." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "CVqMeMYqktvl", + "outputId": "5287ca64-dd25-47b1-8d09-e74d9233f9a6" + }, + "outputs": [], + "source": [ + "# check if notebook is in colab\n", + "try:\n", + " import google.colab\n", + " import subprocess\n", + " import sys\n", + " subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"solc-select\"])\n", + " !solc-select install 0.8.20\n", + " !solc-select use 0.8.20\n", + " !solc --version\n", + "\n", + "# rely on local installation if the notebook is not in colab\n", + "except:\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HRHvkMjVlfWU" + }, + "source": [ + "With solc in our environment we can now create the evm verifier." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "gYlw20VZkva7", + "outputId": "4b0a9e46-6c60-45cb-f4d5-5fdfa17335f0" + }, + "outputs": [], + "source": [ + "sol_code_path = os.path.join('Verifier.sol')\n", + "abi_path = os.path.join('Verifier.abi')\n", + "\n", + "res = await ezkl.create_evm_verifier(\n", + " sol_code_path=sol_code_path,\n", + " abi_path=abi_path,\n", + " )\n", + "\n", + "assert res == True\n", + "assert os.path.isfile(sol_code_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "jQSAVMvxrBQD", + "outputId": "e2cf297c-85ed-432c-ad11-7b5350220939" + }, + "outputs": [], + "source": [ + "onchain_input_array = []\n", + "\n", + "# using a loop\n", + "# avoiding printing last comma\n", + "formatted_output = \"[\"\n", + "for i, value in enumerate(proof[\"instances\"]):\n", + " for j, field_element in enumerate(value):\n", + " onchain_input_array.append(ezkl.felt_to_big_endian(field_element))\n", + " formatted_output += '\"' + str(onchain_input_array[-1]) + '\"'\n", + " if j != len(value) - 1:\n", + " formatted_output += \", \"\n", + " if i != len(proof[\"instances\"]) - 1:\n", + " formatted_output += \", \"\n", + "formatted_output += \"]\"\n", + "\n", + "# This will be the values you use onchain\n", + "# copy them over to remix and see if they verify\n", + "# What happens when you change a value?\n", + "print(\"pubInputs: \", formatted_output)\n", + "print(\"proof: \", proof[\"proof\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zrzPxPvZmX9b" + }, + "source": [ + "We will exit colab for the next steps. At the left of colab you can see a folder icon. Click on that.\n", + "\n", + "\n", + "You should see a `Verifier.sol`. Right-click and save it locally.\n", + "\n", + "Now go to [https://remix.ethereum.org](https://remix.ethereum.org).\n", + "\n", + "Create a new file within remix and copy the verifier code over.\n", + "\n", + "Finally, compile the code and deploy. For the demo you can deploy to the test environment within remix.\n", + "\n", + "If everything works, you would have deployed your verifer onchain! Copy the values in the cell above to the respective fields to test if the verifier is working.\n", + "\n", + "**Note that right now this setup accepts random values!**\n", + "\n", + "This might not be great for some applications. For that we will want to use a data attested verifier instead. [See this tutorial.](https://github.com/zkonduit/ezkl/blob/main/examples/notebooks/data_attest.ipynb)\n", + "\n", + "## Congratulations for making it this far!\n", + "\n", + "If you have followed the whole tutorial, you would have deployed a neural network inference model onchain! That's no mean feat!" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.2" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/src/bin/ezkl.rs b/src/bin/ezkl.rs index 44c64cd32..41d0a0fe5 100644 --- a/src/bin/ezkl.rs +++ b/src/bin/ezkl.rs @@ -1,28 +1,28 @@ // ignore file if compiling for wasm #[global_allocator] -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use clap::{CommandFactory, Parser}; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use colored_json::ToColoredJson; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use ezkl::commands::Cli; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use ezkl::execute::run; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use ezkl::logger::init_logger; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use log::{error, info}; #[cfg(not(any(target_arch = "wasm32", feature = "no-banner")))] use rand::prelude::SliceRandom; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] #[cfg(feature = "icicle")] use std::env; #[tokio::main(flavor = "current_thread")] -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub async fn main() { let args = Cli::parse(); @@ -59,7 +59,7 @@ pub async fn main() { } } -#[cfg(target_arch = "wasm32")] +#[cfg(any(not(feature = "ezkl"), target_arch = "wasm32"))] pub fn main() {} #[cfg(not(any(target_arch = "wasm32", feature = "no-banner")))] diff --git a/src/bin/ios_gen_bindings.rs b/src/bin/ios_gen_bindings.rs new file mode 100644 index 000000000..e1bdd6b86 --- /dev/null +++ b/src/bin/ios_gen_bindings.rs @@ -0,0 +1,269 @@ +use camino::Utf8Path; +use std::fs; +use std::fs::remove_dir_all; +use std::path::{Path, PathBuf}; +use std::process::Command; +use uniffi_bindgen::bindings::SwiftBindingGenerator; +use uniffi_bindgen::library_mode::generate_bindings; +use uuid::Uuid; + +fn main() { + let library_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME is not set"); + let mode = determine_build_mode(); + build_bindings(&library_name, mode); +} + +/// Determines the build mode based on the CONFIGURATION environment variable. +/// Defaults to "release" if not set or unrecognized. +/// "release" mode takes longer to build but produces optimized code, which has smaller size and is faster. +fn determine_build_mode() -> &'static str { + match std::env::var("CONFIGURATION").map(|s| s.to_lowercase()) { + Ok(ref config) if config == "debug" => "debug", + _ => "release", + } +} + +/// Builds the Swift bindings and XCFramework for the specified library and build mode. +fn build_bindings(library_name: &str, mode: &str) { + // Get the root directory of this Cargo project + let manifest_dir = std::env::var_os("CARGO_MANIFEST_DIR") + .map(PathBuf::from) + .unwrap_or_else(|| std::env::current_dir().unwrap()); + + // Define the build directory inside the manifest directory + let build_dir = manifest_dir.join("build"); + + // Create a temporary directory to store the bindings and combined library + let tmp_dir = mktemp_local(&build_dir); + + // Define directories for Swift bindings and output bindings + let swift_bindings_dir = tmp_dir.join("SwiftBindings"); + let bindings_out = create_bindings_out_dir(&tmp_dir); + let framework_out = bindings_out.join("EzklCore.xcframework"); + + // Define target architectures for building + // We currently only support iOS devices and simulators running on ARM Macs + // This is due to limiting the library size to under 100MB for GitHub Commit Size Limit + // To support older Macs (Intel), follow the instructions in the comments below + #[allow(clippy::useless_vec)] + let target_archs = vec![ + vec!["aarch64-apple-ios"], // iOS device + vec!["aarch64-apple-ios-sim"], // iOS simulator ARM Mac + // vec!["aarch64-apple-ios-sim", "x86_64-apple-ios"], // TODO - replace the above line with this line to allow running on older Macs (Intel) + ]; + + // Build the library for each architecture and combine them + let out_lib_paths: Vec = target_archs + .iter() + .map(|archs| build_combined_archs(library_name, archs, &build_dir, mode)) + .collect(); + + // Generate the path to the built dynamic library (.dylib) + let out_dylib_path = build_dir.join(format!( + "{}/{}/lib{}.dylib", + target_archs[0][0], mode, library_name + )); + + // Generate Swift bindings using uniffi_bindgen + generate_ios_bindings(&out_dylib_path, &swift_bindings_dir) + .expect("Failed to generate iOS bindings"); + + // Move the generated Swift file to the bindings output directory + fs::rename( + swift_bindings_dir.join(format!("{}.swift", library_name)), + bindings_out.join("EzklCore.swift"), + ) + .expect("Failed to copy swift bindings file"); + + // Rename the `ios_ezklFFI.modulemap` file to `module.modulemap` + fs::rename( + swift_bindings_dir.join(format!("{}FFI.modulemap", library_name)), + swift_bindings_dir.join("module.modulemap"), + ) + .expect("Failed to rename modulemap file"); + + // Create the XCFramework from the combined libraries and Swift bindings + create_xcframework(&out_lib_paths, &swift_bindings_dir, &framework_out); + + // Define the destination directory for the bindings + let bindings_dest = build_dir.join("EzklCoreBindings"); + if bindings_dest.exists() { + fs::remove_dir_all(&bindings_dest).expect("Failed to remove existing bindings directory"); + } + + // Move the bindings output to the destination directory + fs::rename(&bindings_out, &bindings_dest).expect("Failed to move framework into place"); + + // Clean up temporary directories + cleanup_temp_dirs(&build_dir); +} + +/// Creates the output directory for the bindings. +/// Returns the path to the bindings output directory. +fn create_bindings_out_dir(base_dir: &Path) -> PathBuf { + let bindings_out = base_dir.join("EzklCoreBindings"); + fs::create_dir_all(&bindings_out).expect("Failed to create bindings output directory"); + bindings_out +} + +/// Builds the library for each architecture and combines them into a single library using lipo. +/// Returns the path to the combined library. +fn build_combined_archs( + library_name: &str, + archs: &[&str], + build_dir: &Path, + mode: &str, +) -> PathBuf { + // Build the library for each architecture + let out_lib_paths: Vec = archs + .iter() + .map(|&arch| { + build_for_arch(arch, build_dir, mode); + build_dir + .join(arch) + .join(mode) + .join(format!("lib{}.a", library_name)) + }) + .collect(); + + // Create a unique temporary directory for the combined library + let lib_out = mktemp_local(build_dir).join(format!("lib{}.a", library_name)); + + // Combine the libraries using lipo + let mut lipo_cmd = Command::new("lipo"); + lipo_cmd + .arg("-create") + .arg("-output") + .arg(lib_out.to_str().unwrap()); + for lib_path in &out_lib_paths { + lipo_cmd.arg(lib_path.to_str().unwrap()); + } + + let status = lipo_cmd.status().expect("Failed to run lipo command"); + if !status.success() { + panic!("lipo command failed with status: {}", status); + } + + lib_out +} + +/// Builds the library for a specific architecture. +fn build_for_arch(arch: &str, build_dir: &Path, mode: &str) { + // Ensure the target architecture is installed + install_arch(arch); + + // Run cargo build for the specified architecture and mode + let mut build_cmd = Command::new("cargo"); + build_cmd + .arg("build") + .arg("--no-default-features") + .arg("--features") + .arg("ios-bindings"); + + if mode == "release" { + build_cmd.arg("--release"); + } + build_cmd + .arg("--lib") + .env("CARGO_BUILD_TARGET_DIR", build_dir) + .env("CARGO_BUILD_TARGET", arch); + + let status = build_cmd.status().expect("Failed to run cargo build"); + if !status.success() { + panic!("cargo build failed for architecture: {}", arch); + } +} + +/// Installs the specified target architecture using rustup. +fn install_arch(arch: &str) { + let status = Command::new("rustup") + .arg("target") + .arg("add") + .arg(arch) + .status() + .expect("Failed to run rustup command"); + + if !status.success() { + panic!("Failed to install target architecture: {}", arch); + } +} + +/// Generates Swift bindings for the iOS library using uniffi_bindgen. +fn generate_ios_bindings(dylib_path: &Path, binding_dir: &Path) -> Result<(), std::io::Error> { + // Remove existing binding directory if it exists + if binding_dir.exists() { + remove_dir_all(binding_dir)?; + } + + // Generate the Swift bindings using uniffi_bindgen + generate_bindings( + Utf8Path::from_path(dylib_path).ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid dylib path") + })?, + None, + &SwiftBindingGenerator, + None, + Utf8Path::from_path(binding_dir).ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Invalid Swift bindings directory", + ) + })?, + true, + ) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; + + Ok(()) +} + +/// Creates an XCFramework from the combined libraries and Swift bindings. +fn create_xcframework(lib_paths: &[PathBuf], swift_bindings_dir: &Path, framework_out: &Path) { + let mut xcbuild_cmd = Command::new("xcodebuild"); + xcbuild_cmd.arg("-create-xcframework"); + + // Add each library and its corresponding headers to the xcodebuild command + for lib_path in lib_paths { + println!("Including library: {:?}", lib_path); + xcbuild_cmd.arg("-library"); + xcbuild_cmd.arg(lib_path.to_str().unwrap()); + xcbuild_cmd.arg("-headers"); + xcbuild_cmd.arg(swift_bindings_dir.to_str().unwrap()); + } + + xcbuild_cmd.arg("-output"); + xcbuild_cmd.arg(framework_out.to_str().unwrap()); + + let status = xcbuild_cmd.status().expect("Failed to run xcodebuild"); + if !status.success() { + panic!("xcodebuild failed with status: {}", status); + } +} + +/// Creates a temporary directory inside the build path with a unique UUID. +/// This ensures unique build artifacts for concurrent builds. +fn mktemp_local(build_path: &Path) -> PathBuf { + let dir = tmp_local(build_path).join(Uuid::new_v4().to_string()); + fs::create_dir(&dir).expect("Failed to create temporary directory"); + dir +} + +/// Gets the path to the local temporary directory inside the build path. +fn tmp_local(build_path: &Path) -> PathBuf { + let tmp_path = build_path.join("tmp"); + if let Ok(metadata) = fs::metadata(&tmp_path) { + if !metadata.is_dir() { + panic!("Expected 'tmp' to be a directory"); + } + } else { + fs::create_dir_all(&tmp_path).expect("Failed to create local temporary directory"); + } + tmp_path +} + +/// Cleans up temporary directories inside the build path. +fn cleanup_temp_dirs(build_dir: &Path) { + let tmp_dir = build_dir.join("tmp"); + if tmp_dir.exists() { + fs::remove_dir_all(tmp_dir).expect("Failed to remove temporary directories"); + } +} diff --git a/src/bindings/mod.rs b/src/bindings/mod.rs new file mode 100644 index 000000000..df4dbb81f --- /dev/null +++ b/src/bindings/mod.rs @@ -0,0 +1,12 @@ +/// Python bindings +#[cfg(feature = "python-bindings")] +pub mod python; +/// Universal bindings for all platforms +#[cfg(any( + feature = "ios-bindings", + all(target_arch = "wasm32", target_os = "unknown") +))] +pub mod universal; +/// wasm prover and verifier +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] +pub mod wasm; diff --git a/src/python.rs b/src/bindings/python.rs similarity index 100% rename from src/python.rs rename to src/bindings/python.rs diff --git a/src/bindings/universal.rs b/src/bindings/universal.rs new file mode 100644 index 000000000..a68fdf0ff --- /dev/null +++ b/src/bindings/universal.rs @@ -0,0 +1,579 @@ +use halo2_proofs::{ + plonk::*, + poly::{ + commitment::{CommitmentScheme, ParamsProver}, + ipa::{ + commitment::{IPACommitmentScheme, ParamsIPA}, + multiopen::{ProverIPA, VerifierIPA}, + strategy::SingleStrategy as IPASingleStrategy, + }, + kzg::{ + commitment::{KZGCommitmentScheme, ParamsKZG}, + multiopen::{ProverSHPLONK, VerifierSHPLONK}, + strategy::SingleStrategy as KZGSingleStrategy, + }, + VerificationStrategy, + }, +}; +use std::fmt::Display; +use std::io::BufReader; +use std::str::FromStr; + +use crate::{ + circuit::region::RegionSettings, + graph::GraphSettings, + pfsys::{ + create_proof_circuit, + evm::aggregation_kzg::{AggregationCircuit, PoseidonTranscript}, + verify_proof_circuit, TranscriptType, + }, + tensor::TensorType, + CheckMode, Commitments, EZKLError as InnerEZKLError, +}; + +use crate::graph::{GraphCircuit, GraphWitness}; +use halo2_solidity_verifier::encode_calldata; +use halo2curves::{ + bn256::{Bn256, Fr, G1Affine}, + ff::{FromUniformBytes, PrimeField}, +}; +use snark_verifier::{loader::native::NativeLoader, system::halo2::transcript::evm::EvmTranscript}; + +/// Wrapper around the Error Message +#[cfg_attr(feature = "ios-bindings", derive(uniffi::Error))] +#[derive(Debug)] +pub enum EZKLError { + /// Some Comment + InternalError(String), +} + +impl Display for EZKLError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + EZKLError::InternalError(e) => write!(f, "Internal error: {}", e), + } + } +} + +impl From for EZKLError { + fn from(e: InnerEZKLError) -> Self { + EZKLError::InternalError(e.to_string()) + } +} + +/// Encode verifier calldata from proof and ethereum vk_address +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn encode_verifier_calldata( + // TODO - shuold it be pub(crate) or pub or pub(super)? + proof: Vec, + vk_address: Option>, +) -> Result, EZKLError> { + let snark: crate::pfsys::Snark = + serde_json::from_slice(&proof[..]).map_err(InnerEZKLError::from)?; + + let vk_address: Option<[u8; 20]> = if let Some(vk_address) = vk_address { + let array: [u8; 20] = + serde_json::from_slice(&vk_address[..]).map_err(InnerEZKLError::from)?; + Some(array) + } else { + None + }; + + let flattened_instances = snark.instances.into_iter().flatten(); + + let encoded = encode_calldata( + vk_address, + &snark.proof, + &flattened_instances.collect::>(), + ); + + Ok(encoded) +} + +/// Generate witness from compiled circuit and input json +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn gen_witness(compiled_circuit: Vec, input: Vec) -> Result, EZKLError> { + let mut circuit: crate::graph::GraphCircuit = bincode::deserialize(&compiled_circuit[..]) + .map_err(|e| { + EZKLError::InternalError(format!("Failed to deserialize compiled model: {}", e)) + })?; + let input: crate::graph::input::GraphData = serde_json::from_slice(&input[..]) + .map_err(|e| EZKLError::InternalError(format!("Failed to deserialize input: {}", e)))?; + + let mut input = circuit + .load_graph_input(&input) + .map_err(|e| EZKLError::InternalError(format!("{}", e)))?; + + let witness = circuit + .forward::>( + &mut input, + None, + None, + RegionSettings::all_true( + circuit.settings().run_args.decomp_base, + circuit.settings().run_args.decomp_legs, + ), + ) + .map_err(|e| EZKLError::InternalError(format!("{}", e)))?; + + serde_json::to_vec(&witness) + .map_err(|e| EZKLError::InternalError(format!("Failed to serialize witness: {}", e))) +} + +/// Generate verifying key from compiled circuit, and parameters srs +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn gen_vk( + compiled_circuit: Vec, + srs: Vec, + compress_selectors: bool, +) -> Result, EZKLError> { + let mut reader = BufReader::new(&srs[..]); + let params: ParamsKZG = get_params(&mut reader)?; + + let circuit: GraphCircuit = bincode::deserialize(&compiled_circuit[..]) + .map_err(|e| EZKLError::InternalError(format!("Failed to deserialize circuit: {}", e)))?; + + let vk = create_vk_lean::, Fr, GraphCircuit>( + &circuit, + ¶ms, + compress_selectors, + ) + .map_err(|e| EZKLError::InternalError(format!("Failed to create verifying key: {}", e)))?; + + let mut serialized_vk = Vec::new(); + vk.write(&mut serialized_vk, halo2_proofs::SerdeFormat::RawBytes) + .map_err(|e| { + EZKLError::InternalError(format!("Failed to serialize verifying key: {}", e)) + })?; + + Ok(serialized_vk) +} + +/// Generate proving key from vk, compiled circuit and parameters srs +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn gen_pk( + vk: Vec, + compiled_circuit: Vec, + srs: Vec, +) -> Result, EZKLError> { + let mut reader = BufReader::new(&srs[..]); + let params: ParamsKZG = get_params(&mut reader)?; + + let circuit: GraphCircuit = bincode::deserialize(&compiled_circuit[..]) + .map_err(|e| EZKLError::InternalError(format!("Failed to deserialize circuit: {}", e)))?; + + let mut reader = BufReader::new(&vk[..]); + let vk = VerifyingKey::::read::<_, GraphCircuit>( + &mut reader, + halo2_proofs::SerdeFormat::RawBytes, + circuit.settings().clone(), + ) + .map_err(|e| EZKLError::InternalError(format!("Failed to deserialize verifying key: {}", e)))?; + + let pk = create_pk_lean::, Fr, GraphCircuit>(vk, &circuit, ¶ms) + .map_err(|e| EZKLError::InternalError(format!("Failed to create proving key: {}", e)))?; + + let mut serialized_pk = Vec::new(); + pk.write(&mut serialized_pk, halo2_proofs::SerdeFormat::RawBytes) + .map_err(|e| EZKLError::InternalError(format!("Failed to serialize proving key: {}", e)))?; + + Ok(serialized_pk) +} + +/// Verify proof with vk, proof json, circuit settings json and srs +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn verify( + proof: Vec, + vk: Vec, + settings: Vec, + srs: Vec, +) -> Result { + let circuit_settings: GraphSettings = serde_json::from_slice(&settings[..]) + .map_err(|e| EZKLError::InternalError(format!("Failed to deserialize settings: {}", e)))?; + + let proof: crate::pfsys::Snark = serde_json::from_slice(&proof[..]) + .map_err(|e| EZKLError::InternalError(format!("Failed to deserialize proof: {}", e)))?; + + let mut reader = BufReader::new(&vk[..]); + let vk = VerifyingKey::::read::<_, GraphCircuit>( + &mut reader, + halo2_proofs::SerdeFormat::RawBytes, + circuit_settings.clone(), + ) + .map_err(|e| EZKLError::InternalError(format!("Failed to deserialize vk: {}", e)))?; + + let orig_n = 1 << circuit_settings.run_args.logrows; + let commitment = circuit_settings.run_args.commitment.into(); + + let mut reader = BufReader::new(&srs[..]); + let result = match commitment { + Commitments::KZG => { + let params: ParamsKZG = get_params(&mut reader)?; + let strategy = KZGSingleStrategy::new(params.verifier_params()); + match proof.transcript_type { + TranscriptType::EVM => verify_proof_circuit::< + VerifierSHPLONK<'_, Bn256>, + KZGCommitmentScheme, + KZGSingleStrategy<_>, + _, + EvmTranscript, + >(&proof, ¶ms, &vk, strategy, orig_n), + TranscriptType::Poseidon => { + verify_proof_circuit::< + VerifierSHPLONK<'_, Bn256>, + KZGCommitmentScheme, + KZGSingleStrategy<_>, + _, + PoseidonTranscript, + >(&proof, ¶ms, &vk, strategy, orig_n) + } + } + } + Commitments::IPA => { + let params: ParamsIPA<_> = get_params(&mut reader)?; + let strategy = IPASingleStrategy::new(params.verifier_params()); + match proof.transcript_type { + TranscriptType::EVM => verify_proof_circuit::< + VerifierIPA<_>, + IPACommitmentScheme, + IPASingleStrategy<_>, + _, + EvmTranscript, + >(&proof, ¶ms, &vk, strategy, orig_n), + TranscriptType::Poseidon => { + verify_proof_circuit::< + VerifierIPA<_>, + IPACommitmentScheme, + IPASingleStrategy<_>, + _, + PoseidonTranscript, + >(&proof, ¶ms, &vk, strategy, orig_n) + } + } + } + }; + + match result { + Ok(_) => Ok(true), + Err(e) => Err(EZKLError::InternalError(format!( + "Verification failed: {}", + e + ))), + } +} + +/// Verify aggregate proof with vk, proof, circuit settings and srs +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn verify_aggr( + proof: Vec, + vk: Vec, + logrows: u64, + srs: Vec, + commitment: &str, +) -> Result { + let proof: crate::pfsys::Snark = serde_json::from_slice(&proof[..]) + .map_err(|e| EZKLError::InternalError(format!("Failed to deserialize proof: {}", e)))?; + + let mut reader = BufReader::new(&vk[..]); + let vk = VerifyingKey::::read::<_, AggregationCircuit>( + &mut reader, + halo2_proofs::SerdeFormat::RawBytes, + (), + ) + .map_err(|e| EZKLError::InternalError(format!("Failed to deserialize vk: {}", e)))?; + + let commit = Commitments::from_str(commitment) + .map_err(|e| EZKLError::InternalError(format!("Invalid commitment: {}", e)))?; + + let orig_n = 1 << logrows; + + let mut reader = BufReader::new(&srs[..]); + let result = match commit { + Commitments::KZG => { + let params: ParamsKZG = get_params(&mut reader)?; + let strategy = KZGSingleStrategy::new(params.verifier_params()); + match proof.transcript_type { + TranscriptType::EVM => verify_proof_circuit::< + VerifierSHPLONK<'_, Bn256>, + KZGCommitmentScheme, + KZGSingleStrategy<_>, + _, + EvmTranscript, + >(&proof, ¶ms, &vk, strategy, orig_n), + + TranscriptType::Poseidon => { + verify_proof_circuit::< + VerifierSHPLONK<'_, Bn256>, + KZGCommitmentScheme, + KZGSingleStrategy<_>, + _, + PoseidonTranscript, + >(&proof, ¶ms, &vk, strategy, orig_n) + } + } + } + Commitments::IPA => { + let params: ParamsIPA<_> = + halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader).map_err( + |e| EZKLError::InternalError(format!("Failed to deserialize params: {}", e)), + )?; + let strategy = IPASingleStrategy::new(params.verifier_params()); + match proof.transcript_type { + TranscriptType::EVM => verify_proof_circuit::< + VerifierIPA<_>, + IPACommitmentScheme, + IPASingleStrategy<_>, + _, + EvmTranscript, + >(&proof, ¶ms, &vk, strategy, orig_n), + TranscriptType::Poseidon => { + verify_proof_circuit::< + VerifierIPA<_>, + IPACommitmentScheme, + IPASingleStrategy<_>, + _, + PoseidonTranscript, + >(&proof, ¶ms, &vk, strategy, orig_n) + } + } + } + }; + + result + .map(|_| true) + .map_err(|e| EZKLError::InternalError(format!("{}", e))) +} + +/// Prove in browser with compiled circuit, witness json, proving key, and srs +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn prove( + witness: Vec, + pk: Vec, + compiled_circuit: Vec, + srs: Vec, +) -> Result, EZKLError> { + #[cfg(feature = "det-prove")] + log::set_max_level(log::LevelFilter::Debug); + #[cfg(not(feature = "det-prove"))] + log::set_max_level(log::LevelFilter::Info); + + let mut circuit: GraphCircuit = bincode::deserialize(&compiled_circuit[..]) + .map_err(|e| EZKLError::InternalError(format!("Failed to deserialize circuit: {}", e)))?; + + let data: GraphWitness = serde_json::from_slice(&witness[..]).map_err(InnerEZKLError::from)?; + + let mut reader = BufReader::new(&pk[..]); + let pk = ProvingKey::::read::<_, GraphCircuit>( + &mut reader, + halo2_proofs::SerdeFormat::RawBytes, + circuit.settings().clone(), + ) + .map_err(|e| EZKLError::InternalError(format!("Failed to deserialize proving key: {}", e)))?; + + circuit + .load_graph_witness(&data) + .map_err(InnerEZKLError::from)?; + let public_inputs = circuit + .prepare_public_inputs(&data) + .map_err(InnerEZKLError::from)?; + let proof_split_commits: Option = data.into(); + + let mut reader = BufReader::new(&srs[..]); + let commitment = circuit.settings().run_args.commitment.into(); + + let proof = match commitment { + Commitments::KZG => { + let params: ParamsKZG = + halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader).map_err( + |e| EZKLError::InternalError(format!("Failed to deserialize srs: {}", e)), + )?; + + create_proof_circuit::< + KZGCommitmentScheme, + _, + ProverSHPLONK<_>, + VerifierSHPLONK<_>, + KZGSingleStrategy<_>, + _, + EvmTranscript<_, _, _, _>, + EvmTranscript<_, _, _, _>, + >( + circuit, + vec![public_inputs], + ¶ms, + &pk, + CheckMode::UNSAFE, + Commitments::KZG, + TranscriptType::EVM, + proof_split_commits, + None, + ) + } + Commitments::IPA => { + let params: ParamsIPA<_> = + halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader).map_err( + |e| EZKLError::InternalError(format!("Failed to deserialize srs: {}", e)), + )?; + + create_proof_circuit::< + IPACommitmentScheme, + _, + ProverIPA<_>, + VerifierIPA<_>, + IPASingleStrategy<_>, + _, + EvmTranscript<_, _, _, _>, + EvmTranscript<_, _, _, _>, + >( + circuit, + vec![public_inputs], + ¶ms, + &pk, + CheckMode::UNSAFE, + Commitments::IPA, + TranscriptType::EVM, + proof_split_commits, + None, + ) + } + } + .map_err(InnerEZKLError::from)?; + + Ok(serde_json::to_vec(&proof).map_err(InnerEZKLError::from)?) +} + +/// Validate the witness json +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn witness_validation(witness: Vec) -> Result { + let _: GraphWitness = serde_json::from_slice(&witness[..]).map_err(InnerEZKLError::from)?; + + Ok(true) +} + +/// Validate the compiled circuit +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn compiled_circuit_validation(compiled_circuit: Vec) -> Result { + let _: GraphCircuit = bincode::deserialize(&compiled_circuit[..]).map_err(|e| { + EZKLError::InternalError(format!("Failed to deserialize compiled circuit: {}", e)) + })?; + + Ok(true) +} + +/// Validate the input json +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn input_validation(input: Vec) -> Result { + let _: crate::graph::input::GraphData = + serde_json::from_slice(&input[..]).map_err(InnerEZKLError::from)?; + + Ok(true) +} + +/// Validate the proof json +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn proof_validation(proof: Vec) -> Result { + let _: crate::pfsys::Snark = + serde_json::from_slice(&proof[..]).map_err(InnerEZKLError::from)?; + + Ok(true) +} + +/// Validate the verifying key given the settings json +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn vk_validation(vk: Vec, settings: Vec) -> Result { + let circuit_settings: GraphSettings = + serde_json::from_slice(&settings[..]).map_err(InnerEZKLError::from)?; + + let mut reader = BufReader::new(&vk[..]); + let _ = VerifyingKey::::read::<_, GraphCircuit>( + &mut reader, + halo2_proofs::SerdeFormat::RawBytes, + circuit_settings, + ) + .map_err(|e| EZKLError::InternalError(format!("Failed to deserialize verifying key: {}", e)))?; + + Ok(true) +} + +/// Validate the proving key given the settings json +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn pk_validation(pk: Vec, settings: Vec) -> Result { + let circuit_settings: GraphSettings = + serde_json::from_slice(&settings[..]).map_err(InnerEZKLError::from)?; + + let mut reader = BufReader::new(&pk[..]); + let _ = ProvingKey::::read::<_, GraphCircuit>( + &mut reader, + halo2_proofs::SerdeFormat::RawBytes, + circuit_settings, + ) + .map_err(|e| EZKLError::InternalError(format!("Failed to deserialize proving key: {}", e)))?; + + Ok(true) +} + +/// Validate the settings json +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn settings_validation(settings: Vec) -> Result { + let _: GraphSettings = serde_json::from_slice(&settings[..]).map_err(InnerEZKLError::from)?; + + Ok(true) +} + +/// Validate the srs +#[cfg_attr(feature = "ios-bindings", uniffi::export)] +pub(crate) fn srs_validation(srs: Vec) -> Result { + let mut reader = BufReader::new(&srs[..]); + let _: ParamsKZG = + halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader).map_err(|e| { + EZKLError::InternalError(format!("Failed to deserialize params: {}", e)) + })?; + + Ok(true) +} + +// HELPER FUNCTIONS + +fn get_params< + Scheme: for<'a> halo2_proofs::poly::commitment::Params<'a, halo2curves::bn256::G1Affine>, +>( + mut reader: &mut BufReader<&[u8]>, +) -> Result { + halo2_proofs::poly::commitment::Params::::read(&mut reader) + .map_err(|e| EZKLError::InternalError(format!("Failed to deserialize params: {}", e))) +} + +/// Creates a [ProvingKey] for a [GraphCircuit] (`circuit`) with specific [CommitmentScheme] parameters (`params`) for the WASM target +pub fn create_vk_lean>( + circuit: &C, + params: &'_ Scheme::ParamsProver, + compress_selectors: bool, +) -> Result, halo2_proofs::plonk::Error> +where + C: Circuit, + ::Scalar: FromUniformBytes<64>, +{ + // Real proof + let empty_circuit = >::without_witnesses(circuit); + + // Initialize the verifying key + let vk = keygen_vk_custom(params, &empty_circuit, compress_selectors)?; + Ok(vk) +} +/// Creates a [ProvingKey] from a [VerifyingKey] for a [GraphCircuit] (`circuit`) with specific [CommitmentScheme] parameters (`params`) for the WASM target +pub fn create_pk_lean>( + vk: VerifyingKey, + circuit: &C, + params: &'_ Scheme::ParamsProver, +) -> Result, halo2_proofs::plonk::Error> +where + C: Circuit, + ::Scalar: FromUniformBytes<64>, +{ + // Real proof + let empty_circuit = >::without_witnesses(circuit); + + // Initialize the proving key + let pk = keygen_pk(params, vk, &empty_circuit)?; + Ok(pk) +} diff --git a/src/bindings/wasm.rs b/src/bindings/wasm.rs new file mode 100644 index 000000000..3adc41843 --- /dev/null +++ b/src/bindings/wasm.rs @@ -0,0 +1,372 @@ +use crate::{ + circuit::modules::{ + polycommit::PolyCommitChip, + poseidon::{ + spec::{PoseidonSpec, POSEIDON_RATE, POSEIDON_WIDTH}, + PoseidonChip, + }, + Module, + }, + fieldutils::{felt_to_integer_rep, integer_rep_to_felt}, + graph::{ + modules::POSEIDON_LEN_GRAPH, quantize_float, scale_to_multiplier, GraphCircuit, + GraphSettings, + }, +}; +use console_error_panic_hook; +use halo2_proofs::{ + plonk::*, + poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}, +}; +use halo2curves::{ + bn256::{Bn256, Fr, G1Affine}, + ff::PrimeField, +}; +use wasm_bindgen::prelude::*; +use wasm_bindgen_console_logger::DEFAULT_LOGGER; + +use crate::bindings::universal::{ + compiled_circuit_validation, encode_verifier_calldata, gen_pk, gen_vk, gen_witness, + input_validation, pk_validation, proof_validation, settings_validation, srs_validation, + verify_aggr, vk_validation, witness_validation, EZKLError as ExternalEZKLError, +}; +#[cfg(feature = "web")] +pub use wasm_bindgen_rayon::init_thread_pool; + +impl From for JsError { + fn from(e: ExternalEZKLError) -> Self { + JsError::new(&format!("{}", e)) + } +} + +#[wasm_bindgen] +/// Initialize logger for wasm +pub fn init_logger() { + log::set_logger(&DEFAULT_LOGGER).unwrap(); +} + +#[wasm_bindgen] +/// Initialize panic hook for wasm +pub fn init_panic_hook() { + console_error_panic_hook::set_once(); +} + +/// Wrapper around the halo2 encode call data method +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn encodeVerifierCalldata( + proof: wasm_bindgen::Clamped>, + vk_address: Option>, +) -> Result, JsError> { + encode_verifier_calldata(proof.0, vk_address).map_err(JsError::from) +} + +/// Converts a hex string to a byte array +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn feltToBigEndian(array: wasm_bindgen::Clamped>) -> Result { + let felt: Fr = serde_json::from_slice(&array[..]) + .map_err(|e| JsError::new(&format!("Failed to deserialize field element: {}", e)))?; + Ok(format!("{:?}", felt)) +} + +/// Converts a felt to a little endian string +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn feltToLittleEndian(array: wasm_bindgen::Clamped>) -> Result { + let felt: Fr = serde_json::from_slice(&array[..]) + .map_err(|e| JsError::new(&format!("Failed to deserialize field element: {}", e)))?; + let repr = serde_json::to_string(&felt).unwrap(); + let b: String = serde_json::from_str(&repr).unwrap(); + Ok(b) +} + +/// Converts a hex string to a byte array +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn feltToInt( + array: wasm_bindgen::Clamped>, +) -> Result>, JsError> { + let felt: Fr = serde_json::from_slice(&array[..]) + .map_err(|e| JsError::new(&format!("Failed to deserialize field element: {}", e)))?; + Ok(wasm_bindgen::Clamped( + serde_json::to_vec(&felt_to_integer_rep(felt)) + .map_err(|e| JsError::new(&format!("Failed to serialize integer: {}", e)))?, + )) +} + +/// Converts felts to a floating point element +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn feltToFloat( + array: wasm_bindgen::Clamped>, + scale: crate::Scale, +) -> Result { + let felt: Fr = serde_json::from_slice(&array[..]) + .map_err(|e| JsError::new(&format!("Failed to deserialize field element: {}", e)))?; + let int_rep = felt_to_integer_rep(felt); + let multiplier = scale_to_multiplier(scale); + Ok(int_rep as f64 / multiplier) +} + +/// Converts a floating point number to a hex string representing a fixed point field element +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn floatToFelt( + input: f64, + scale: crate::Scale, +) -> Result>, JsError> { + let int_rep = + quantize_float(&input, 0.0, scale).map_err(|e| JsError::new(&format!("{}", e)))?; + let felt = integer_rep_to_felt(int_rep); + let vec = crate::pfsys::field_to_string::(&felt); + Ok(wasm_bindgen::Clamped(serde_json::to_vec(&vec).map_err( + |e| JsError::new(&format!("Failed to serialize a float to felt{}", e)), + )?)) +} + +/// Generate a kzg commitment. +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn kzgCommit( + message: wasm_bindgen::Clamped>, + vk: wasm_bindgen::Clamped>, + settings: wasm_bindgen::Clamped>, + params_ser: wasm_bindgen::Clamped>, +) -> Result>, JsError> { + let message: Vec = serde_json::from_slice(&message[..]) + .map_err(|e| JsError::new(&format!("Failed to deserialize message: {}", e)))?; + + let mut reader = std::io::BufReader::new(¶ms_ser[..]); + let params: ParamsKZG = + halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader) + .map_err(|e| JsError::new(&format!("Failed to deserialize params: {}", e)))?; + + let mut reader = std::io::BufReader::new(&vk[..]); + let circuit_settings: GraphSettings = serde_json::from_slice(&settings[..]) + .map_err(|e| JsError::new(&format!("Failed to deserialize settings: {}", e)))?; + let vk = VerifyingKey::::read::<_, GraphCircuit>( + &mut reader, + halo2_proofs::SerdeFormat::RawBytes, + circuit_settings, + ) + .map_err(|e| JsError::new(&format!("Failed to deserialize vk: {}", e)))?; + + let output = PolyCommitChip::commit::>( + message, + (vk.cs().blinding_factors() + 1) as u32, + ¶ms, + ); + + Ok(wasm_bindgen::Clamped( + serde_json::to_vec(&output).map_err(|e| JsError::new(&format!("{}", e)))?, + )) +} + +/// Converts a buffer to vector of 4 u64s representing a fixed point field element +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn bufferToVecOfFelt( + buffer: wasm_bindgen::Clamped>, +) -> Result>, JsError> { + // Convert the buffer to a slice + let buffer: &[u8] = &buffer; + + // Divide the buffer into chunks of 64 bytes + let chunks = buffer.chunks_exact(16); + + // Get the remainder + let remainder = chunks.remainder(); + + // Add 0s to the remainder to make it 64 bytes + let mut remainder = remainder.to_vec(); + + // Collect chunks into a Vec<[u8; 16]>. + let chunks: Result, JsError> = chunks + .map(|slice| { + let array: [u8; 16] = slice + .try_into() + .map_err(|_| JsError::new("failed to slice input chunks"))?; + Ok(array) + }) + .collect(); + + let mut chunks = chunks?; + + if remainder.len() != 0 { + remainder.resize(16, 0); + // Convert the Vec to [u8; 16] + let remainder_array: [u8; 16] = remainder + .try_into() + .map_err(|_| JsError::new("failed to slice remainder"))?; + // append the remainder to the chunks + chunks.push(remainder_array); + } + + // Convert each chunk to a field element + let field_elements: Vec = chunks + .iter() + .map(|x| PrimeField::from_u128(u8_array_to_u128_le(*x))) + .collect(); + + Ok(wasm_bindgen::Clamped( + serde_json::to_vec(&field_elements) + .map_err(|e| JsError::new(&format!("Failed to serialize field elements: {}", e)))?, + )) +} + +/// Generate a poseidon hash in browser. Input message +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn poseidonHash( + message: wasm_bindgen::Clamped>, +) -> Result>, JsError> { + let message: Vec = serde_json::from_slice(&message[..]) + .map_err(|e| JsError::new(&format!("Failed to deserialize message: {}", e)))?; + + let output = + PoseidonChip::::run( + message.clone(), + ) + .map_err(|e| JsError::new(&format!("{}", e)))?; + + Ok(wasm_bindgen::Clamped(serde_json::to_vec(&output).map_err( + |e| JsError::new(&format!("Failed to serialize poseidon hash output: {}", e)), + )?)) +} + +/// Generate a witness file from input.json, compiled model and a settings.json file. +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn genWitness( + compiled_circuit: wasm_bindgen::Clamped>, + input: wasm_bindgen::Clamped>, +) -> Result, JsError> { + gen_witness(compiled_circuit.0, input.0).map_err(JsError::from) +} + +/// Generate verifying key in browser +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn genVk( + compiled_circuit: wasm_bindgen::Clamped>, + params_ser: wasm_bindgen::Clamped>, + compress_selectors: bool, +) -> Result, JsError> { + gen_vk(compiled_circuit.0, params_ser.0, compress_selectors).map_err(JsError::from) +} + +/// Generate proving key in browser +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn genPk( + vk: wasm_bindgen::Clamped>, + compiled_circuit: wasm_bindgen::Clamped>, + params_ser: wasm_bindgen::Clamped>, +) -> Result, JsError> { + gen_pk(vk.0, compiled_circuit.0, params_ser.0).map_err(JsError::from) +} + +/// Verify proof in browser using wasm +#[wasm_bindgen] +pub fn verify( + proof_js: wasm_bindgen::Clamped>, + vk: wasm_bindgen::Clamped>, + settings: wasm_bindgen::Clamped>, + srs: wasm_bindgen::Clamped>, +) -> Result { + super::universal::verify(proof_js.0, vk.0, settings.0, srs.0).map_err(JsError::from) +} + +/// Verify aggregate proof in browser using wasm +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn verifyAggr( + proof_js: wasm_bindgen::Clamped>, + vk: wasm_bindgen::Clamped>, + logrows: u64, + srs: wasm_bindgen::Clamped>, + commitment: &str, +) -> Result { + verify_aggr(proof_js.0, vk.0, logrows, srs.0, commitment).map_err(JsError::from) +} + +/// Prove in browser using wasm +#[wasm_bindgen] +pub fn prove( + witness: wasm_bindgen::Clamped>, + pk: wasm_bindgen::Clamped>, + compiled_circuit: wasm_bindgen::Clamped>, + srs: wasm_bindgen::Clamped>, +) -> Result, JsError> { + super::universal::prove(witness.0, pk.0, compiled_circuit.0, srs.0).map_err(JsError::from) +} + +// VALIDATION FUNCTIONS + +/// Witness file validation +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn witnessValidation(witness: wasm_bindgen::Clamped>) -> Result { + witness_validation(witness.0).map_err(JsError::from) +} +/// Compiled circuit validation +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn compiledCircuitValidation( + compiled_circuit: wasm_bindgen::Clamped>, +) -> Result { + compiled_circuit_validation(compiled_circuit.0).map_err(JsError::from) +} +/// Input file validation +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn inputValidation(input: wasm_bindgen::Clamped>) -> Result { + input_validation(input.0).map_err(JsError::from) +} +/// Proof file validation +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn proofValidation(proof: wasm_bindgen::Clamped>) -> Result { + proof_validation(proof.0).map_err(JsError::from) +} +/// Vk file validation +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn vkValidation( + vk: wasm_bindgen::Clamped>, + settings: wasm_bindgen::Clamped>, +) -> Result { + vk_validation(vk.0, settings.0).map_err(JsError::from) +} +/// Pk file validation +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn pkValidation( + pk: wasm_bindgen::Clamped>, + settings: wasm_bindgen::Clamped>, +) -> Result { + pk_validation(pk.0, settings.0).map_err(JsError::from) +} +/// Settings file validation +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn settingsValidation(settings: wasm_bindgen::Clamped>) -> Result { + settings_validation(settings.0).map_err(JsError::from) +} +/// Srs file validation +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn srsValidation(srs: wasm_bindgen::Clamped>) -> Result { + srs_validation(srs.0).map_err(JsError::from) +} + +/// HELPER FUNCTIONS +pub fn u8_array_to_u128_le(arr: [u8; 16]) -> u128 { + let mut n: u128 = 0; + for &b in arr.iter().rev() { + n <<= 8; + n |= b as u128; + } + n +} diff --git a/src/circuit/modules/polycommit.rs b/src/circuit/modules/polycommit.rs index 0a2de89d6..c9a4cfa54 100644 --- a/src/circuit/modules/polycommit.rs +++ b/src/circuit/modules/polycommit.rs @@ -219,7 +219,7 @@ mod tests { fn polycommit_chip_for_a_range_of_input_sizes() { let rng = rand::rngs::OsRng; - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] env_logger::init(); { @@ -247,7 +247,7 @@ mod tests { #[test] #[ignore] fn polycommit_chip_much_longer_input() { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] env_logger::init(); let rng = rand::rngs::OsRng; diff --git a/src/circuit/modules/poseidon.rs b/src/circuit/modules/poseidon.rs index 5a05fe81a..f2a295a1c 100644 --- a/src/circuit/modules/poseidon.rs +++ b/src/circuit/modules/poseidon.rs @@ -560,7 +560,7 @@ mod tests { fn hash_for_a_range_of_input_sizes() { let rng = rand::rngs::OsRng; - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] env_logger::init(); { diff --git a/src/circuit/ops/chip.rs b/src/circuit/ops/chip.rs index 1316d5f98..446ff069e 100644 --- a/src/circuit/ops/chip.rs +++ b/src/circuit/ops/chip.rs @@ -14,6 +14,7 @@ use pyo3::{ types::PyString, }; use serde::{Deserialize, Serialize}; +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tosubcommand::ToFlags; use crate::{ @@ -49,6 +50,7 @@ impl std::fmt::Display for CheckMode { } } +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] impl ToFlags for CheckMode { /// Convert the struct to a subcommand string fn to_flags(&self) -> Vec { @@ -88,6 +90,7 @@ impl std::fmt::Display for Tolerance { } } +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] impl ToFlags for Tolerance { /// Convert the struct to a subcommand string fn to_flags(&self) -> Vec { @@ -174,7 +177,7 @@ impl<'source> FromPyObject<'source> for Tolerance { #[derive(Clone, Debug, Default)] pub struct DynamicLookups { /// [Selector]s generated when configuring the layer. We use a [BTreeMap] as we expect to configure many dynamic lookup ops. - pub lookup_selectors: BTreeMap<(usize, usize), Selector>, + pub lookup_selectors: BTreeMap<(usize, (usize, usize)), Selector>, /// Selectors for the dynamic lookup tables pub table_selectors: Vec, /// Inputs: @@ -206,7 +209,7 @@ impl DynamicLookups { #[derive(Clone, Debug, Default)] pub struct Shuffles { /// [Selector]s generated when configuring the layer. We use a [BTreeMap] as we expect to configure many dynamic lookup ops. - pub input_selectors: BTreeMap<(usize, usize), Selector>, + pub input_selectors: BTreeMap<(usize, (usize, usize)), Selector>, /// Selectors for the dynamic lookup tables pub reference_selectors: Vec, /// Inputs: @@ -643,57 +646,73 @@ impl BaseConfig { } for t in tables.iter() { - if !t.is_advice() || t.num_blocks() > 1 || t.num_inner_cols() > 1 { + if !t.is_advice() || t.num_inner_cols() > 1 { return Err(CircuitError::WrongDynamicColumnType(t.name().to_string())); } } + // assert all tables have the same number of inner columns + if tables + .iter() + .map(|t| t.num_blocks()) + .collect::>() + .windows(2) + .any(|w| w[0] != w[1]) + { + return Err(CircuitError::WrongDynamicColumnType( + "tables inner cols".to_string(), + )); + } + let one = Expression::Constant(F::ONE); - let s_ltable = cs.complex_selector(); + for q in 0..tables[0].num_blocks() { + let s_ltable = cs.complex_selector(); - for x in 0..lookups[0].num_blocks() { - for y in 0..lookups[0].num_inner_cols() { - let s_lookup = cs.complex_selector(); + for x in 0..lookups[0].num_blocks() { + for y in 0..lookups[0].num_inner_cols() { + let s_lookup = cs.complex_selector(); - cs.lookup_any("lookup", |cs| { - let s_lookupq = cs.query_selector(s_lookup); - let mut expression = vec![]; - let s_ltableq = cs.query_selector(s_ltable); - let mut lookup_queries = vec![one.clone()]; + cs.lookup_any("lookup", |cs| { + let s_lookupq = cs.query_selector(s_lookup); + let mut expression = vec![]; + let s_ltableq = cs.query_selector(s_ltable); + let mut lookup_queries = vec![one.clone()]; - for lookup in lookups { - lookup_queries.push(match lookup { - VarTensor::Advice { inner: advices, .. } => { - cs.query_advice(advices[x][y], Rotation(0)) - } - _ => unreachable!(), - }); - } + for lookup in lookups { + lookup_queries.push(match lookup { + VarTensor::Advice { inner: advices, .. } => { + cs.query_advice(advices[x][y], Rotation(0)) + } + _ => unreachable!(), + }); + } - let mut table_queries = vec![one.clone()]; - for table in tables { - table_queries.push(match table { - VarTensor::Advice { inner: advices, .. } => { - cs.query_advice(advices[0][0], Rotation(0)) - } - _ => unreachable!(), - }); - } + let mut table_queries = vec![one.clone()]; + for table in tables { + table_queries.push(match table { + VarTensor::Advice { inner: advices, .. } => { + cs.query_advice(advices[q][0], Rotation(0)) + } + _ => unreachable!(), + }); + } - let lhs = lookup_queries.into_iter().map(|c| c * s_lookupq.clone()); - let rhs = table_queries.into_iter().map(|c| c * s_ltableq.clone()); - expression.extend(lhs.zip(rhs)); + let lhs = lookup_queries.into_iter().map(|c| c * s_lookupq.clone()); + let rhs = table_queries.into_iter().map(|c| c * s_ltableq.clone()); + expression.extend(lhs.zip(rhs)); - expression - }); - self.dynamic_lookups - .lookup_selectors - .entry((x, y)) - .or_insert(s_lookup); + expression + }); + self.dynamic_lookups + .lookup_selectors + .entry((q, (x, y))) + .or_insert(s_lookup); + } } + + self.dynamic_lookups.table_selectors.push(s_ltable); } - self.dynamic_lookups.table_selectors.push(s_ltable); // if we haven't previously initialized the input/output, do so now if self.dynamic_lookups.tables.is_empty() { @@ -726,57 +745,72 @@ impl BaseConfig { } for t in references.iter() { - if !t.is_advice() || t.num_blocks() > 1 || t.num_inner_cols() > 1 { + if !t.is_advice() || t.num_inner_cols() > 1 { return Err(CircuitError::WrongDynamicColumnType(t.name().to_string())); } } + // assert all tables have the same number of blocks + if references + .iter() + .map(|t| t.num_blocks()) + .collect::>() + .windows(2) + .any(|w| w[0] != w[1]) + { + return Err(CircuitError::WrongDynamicColumnType( + "references inner cols".to_string(), + )); + } + let one = Expression::Constant(F::ONE); - let s_reference = cs.complex_selector(); + for q in 0..references[0].num_blocks() { + let s_reference = cs.complex_selector(); - for x in 0..inputs[0].num_blocks() { - for y in 0..inputs[0].num_inner_cols() { - let s_input = cs.complex_selector(); + for x in 0..inputs[0].num_blocks() { + for y in 0..inputs[0].num_inner_cols() { + let s_input = cs.complex_selector(); - cs.lookup_any("lookup", |cs| { - let s_inputq = cs.query_selector(s_input); - let mut expression = vec![]; - let s_referenceq = cs.query_selector(s_reference); - let mut input_queries = vec![one.clone()]; + cs.lookup_any("lookup", |cs| { + let s_inputq = cs.query_selector(s_input); + let mut expression = vec![]; + let s_referenceq = cs.query_selector(s_reference); + let mut input_queries = vec![one.clone()]; - for input in inputs { - input_queries.push(match input { - VarTensor::Advice { inner: advices, .. } => { - cs.query_advice(advices[x][y], Rotation(0)) - } - _ => unreachable!(), - }); - } + for input in inputs { + input_queries.push(match input { + VarTensor::Advice { inner: advices, .. } => { + cs.query_advice(advices[x][y], Rotation(0)) + } + _ => unreachable!(), + }); + } - let mut ref_queries = vec![one.clone()]; - for reference in references { - ref_queries.push(match reference { - VarTensor::Advice { inner: advices, .. } => { - cs.query_advice(advices[0][0], Rotation(0)) - } - _ => unreachable!(), - }); - } + let mut ref_queries = vec![one.clone()]; + for reference in references { + ref_queries.push(match reference { + VarTensor::Advice { inner: advices, .. } => { + cs.query_advice(advices[q][0], Rotation(0)) + } + _ => unreachable!(), + }); + } - let lhs = input_queries.into_iter().map(|c| c * s_inputq.clone()); - let rhs = ref_queries.into_iter().map(|c| c * s_referenceq.clone()); - expression.extend(lhs.zip(rhs)); + let lhs = input_queries.into_iter().map(|c| c * s_inputq.clone()); + let rhs = ref_queries.into_iter().map(|c| c * s_referenceq.clone()); + expression.extend(lhs.zip(rhs)); - expression - }); - self.shuffles - .input_selectors - .entry((x, y)) - .or_insert(s_input); + expression + }); + self.shuffles + .input_selectors + .entry((q, (x, y))) + .or_insert(s_input); + } } + self.shuffles.reference_selectors.push(s_reference); } - self.shuffles.reference_selectors.push(s_reference); // if we haven't previously initialized the input/output, do so now if self.shuffles.references.is_empty() { diff --git a/src/circuit/ops/layouts.rs b/src/circuit/ops/layouts.rs index 37bbc02e1..12966f4f8 100644 --- a/src/circuit/ops/layouts.rs +++ b/src/circuit/ops/layouts.rs @@ -979,8 +979,16 @@ pub(crate) fn dynamic_lookup, CircuitError>>()?; @@ -1023,20 +1039,23 @@ pub(crate) fn dynamic_lookup, CircuitError>>()?; } - region.increment_dynamic_lookup_col_coord(table_len); + region.increment_dynamic_lookup_col_coord(table_len + flush_len_0); region.increment_dynamic_lookup_index(1); region.increment(lookup_len); @@ -1064,22 +1083,33 @@ pub(crate) fn shuffles, CircuitError>>()?; } - region.increment_shuffle_col_coord(reference_len); + region.increment_shuffle_col_coord(reference_len + flush_len_ref); region.increment_shuffle_index(1); region.increment(reference_len); diff --git a/src/circuit/ops/mod.rs b/src/circuit/ops/mod.rs index 47aee6529..552a782fc 100644 --- a/src/circuit/ops/mod.rs +++ b/src/circuit/ops/mod.rs @@ -255,7 +255,7 @@ impl Constant { self.raw_values = Tensor::new(None, &[0]).unwrap(); } - /// + /// Pre-assign a value pub fn pre_assign(&mut self, val: ValTensor) { self.pre_assigned_val = Some(val) } diff --git a/src/circuit/ops/region.rs b/src/circuit/ops/region.rs index c03bb638a..aa66df1ac 100644 --- a/src/circuit/ops/region.rs +++ b/src/circuit/ops/region.rs @@ -3,7 +3,7 @@ use crate::{ fieldutils::IntegerRep, tensor::{Tensor, TensorType, ValTensor, ValType, VarTensor}, }; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use colored::Colorize; use halo2_proofs::{ circuit::Region, @@ -180,6 +180,7 @@ pub struct RegionCtx<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Ha statistics: RegionStatistics, settings: RegionSettings, assigned_constants: ConstantsMap, + max_dynamic_input_len: usize, } impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a, F> { @@ -193,11 +194,16 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a self.settings.legs } - #[cfg(not(target_arch = "wasm32"))] + /// get the max dynamic input len + pub fn max_dynamic_input_len(&self) -> usize { + self.max_dynamic_input_len + } + + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] /// pub fn debug_report(&self) { log::debug!( - "(rows={}, coord={}, constants={}, max_lookup_inputs={}, min_lookup_inputs={}, max_range_size={}, dynamic_lookup_col_coord={}, shuffle_col_coord={})", + "(rows={}, coord={}, constants={}, max_lookup_inputs={}, min_lookup_inputs={}, max_range_size={}, dynamic_lookup_col_coord={}, shuffle_col_coord={}, max_dynamic_input_len={})", self.row().to_string().blue(), self.linear_coord().to_string().yellow(), self.total_constants().to_string().red(), @@ -205,7 +211,9 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a self.min_lookup_inputs().to_string().green(), self.max_range_size().to_string().green(), self.dynamic_lookup_col_coord().to_string().green(), - self.shuffle_col_coord().to_string().green()); + self.shuffle_col_coord().to_string().green(), + self.max_dynamic_input_len().to_string().green() + ); } /// @@ -223,6 +231,11 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a self.dynamic_lookup_index.index += n; } + /// increment the max dynamic input len + pub fn update_max_dynamic_input_len(&mut self, n: usize) { + self.max_dynamic_input_len = self.max_dynamic_input_len.max(n); + } + /// pub fn increment_dynamic_lookup_col_coord(&mut self, n: usize) { self.dynamic_lookup_index.col_coord += n; @@ -274,6 +287,7 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a statistics: RegionStatistics::default(), settings: RegionSettings::all_true(decomp_base, decomp_legs), assigned_constants: HashMap::new(), + max_dynamic_input_len: 0, } } @@ -310,6 +324,7 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a statistics: RegionStatistics::default(), settings, assigned_constants: HashMap::new(), + max_dynamic_input_len: 0, } } @@ -331,6 +346,7 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a statistics: RegionStatistics::default(), settings, assigned_constants: HashMap::new(), + max_dynamic_input_len: 0, } } @@ -583,9 +599,12 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a &mut self, var: &VarTensor, values: &ValTensor, - ) -> Result, CircuitError> { + ) -> Result<(ValTensor, usize), CircuitError> { + + self.update_max_dynamic_input_len(values.len()); + if let Some(region) = &self.region { - Ok(var.assign( + Ok(var.assign_exact_column( &mut region.borrow_mut(), self.combined_dynamic_shuffle_coord(), values, @@ -596,7 +615,11 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a let values_map = values.create_constants_map_iterator(); self.assigned_constants.par_extend(values_map); } - Ok(values.clone()) + + let flush_len = var.get_column_flush(self.combined_dynamic_shuffle_coord(), values)?; + + // get the diff between the current column and the next row + Ok((values.clone(), flush_len)) } } @@ -605,7 +628,7 @@ impl<'a, F: PrimeField + TensorType + PartialOrd + std::hash::Hash> RegionCtx<'a &mut self, var: &VarTensor, values: &ValTensor, - ) -> Result, CircuitError> { + ) -> Result<(ValTensor, usize), CircuitError> { self.assign_dynamic_lookup(var, values) } diff --git a/src/circuit/table.rs b/src/circuit/table.rs index 95600cc57..4be1fa6df 100644 --- a/src/circuit/table.rs +++ b/src/circuit/table.rs @@ -15,7 +15,7 @@ use crate::{ tensor::{Tensor, TensorType}, }; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use crate::execute::EZKL_REPO_PATH; use crate::circuit::lookup::LookupOp; @@ -28,14 +28,14 @@ pub const RANGE_MULTIPLIER: IntegerRep = 2; /// The safety factor offset for the number of rows in the lookup table. pub const RESERVED_BLINDING_ROWS_PAD: usize = 3; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] lazy_static::lazy_static! { /// an optional directory to read and write the lookup table cache pub static ref LOOKUP_CACHE: String = format!("{}/cache", *EZKL_REPO_PATH); } /// The lookup table cache is disabled on wasm32 target. -#[cfg(target_arch = "wasm32")] +#[cfg(any(not(feature = "ezkl"), target_arch = "wasm32"))] pub const LOOKUP_CACHE: &str = ""; #[derive(Debug, Clone)] diff --git a/src/circuit/tests.rs b/src/circuit/tests.rs index cb1bdf8cc..3ba499544 100644 --- a/src/circuit/tests.rs +++ b/src/circuit/tests.rs @@ -8,7 +8,10 @@ use halo2_proofs::{ }; use halo2curves::bn256::Fr as F; use halo2curves::ff::{Field, PrimeField}; -#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] +#[cfg(not(any( + all(target_arch = "wasm32", target_os = "unknown"), + not(feature = "ezkl") +)))] use ops::lookup::LookupOp; use ops::region::RegionCtx; use rand::rngs::OsRng; @@ -244,7 +247,10 @@ mod matmul_col_overflow { } #[cfg(test)] -#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] +#[cfg(all( + feature = "ezkl", + not(all(target_arch = "wasm32", target_os = "unknown")) +))] mod matmul_col_ultra_overflow_double_col { use halo2_proofs::poly::kzg::{ @@ -362,7 +368,10 @@ mod matmul_col_ultra_overflow_double_col { } #[cfg(test)] -#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] +#[cfg(all( + feature = "ezkl", + not(all(target_arch = "wasm32", target_os = "unknown")) +))] mod matmul_col_ultra_overflow { use halo2_proofs::poly::kzg::{ @@ -1145,7 +1154,10 @@ mod conv { } #[cfg(test)] -#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] +#[cfg(all( + feature = "ezkl", + not(all(target_arch = "wasm32", target_os = "unknown")) +))] mod conv_col_ultra_overflow { use halo2_proofs::poly::{ @@ -1286,7 +1298,10 @@ mod conv_col_ultra_overflow { #[cfg(test)] // not wasm 32 unknown -#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] +#[cfg(all( + feature = "ezkl", + not(all(target_arch = "wasm32", target_os = "unknown")) +))] mod conv_relu_col_ultra_overflow { use halo2_proofs::poly::kzg::{ @@ -1501,7 +1516,7 @@ mod add_w_shape_casting { // parameters let a = Tensor::from((0..LEN).map(|i| Value::known(F::from(i as u64 + 1)))); - let b = Tensor::from((0..1).map(|i| Value::known(F::from(i as u64 + 1)))); + let b = Tensor::from((0..1).map(|i| Value::known(F::from(i + 1)))); let circuit = MyCircuit:: { inputs: [ValTensor::from(a), ValTensor::from(b)], @@ -2449,7 +2464,10 @@ mod relu { } #[cfg(test)] -#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] +#[cfg(all( + feature = "ezkl", + not(all(target_arch = "wasm32", target_os = "unknown")) +))] mod lookup_ultra_overflow { use super::*; use halo2_proofs::{ diff --git a/src/commands.rs b/src/commands.rs index 8fc835a3e..5fd14d4fe 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,4 +1,3 @@ -#[cfg(not(target_arch = "wasm32"))] use alloy::primitives::Address as H160; use clap::{Command, Parser, Subcommand}; use clap_complete::{generate, Generator, Shell}; @@ -17,7 +16,6 @@ use tosubcommand::{ToFlags, ToSubcommand}; use crate::{pfsys::ProofType, Commitments, RunArgs}; use crate::circuit::CheckMode; -#[cfg(not(target_arch = "wasm32"))] use crate::graph::TestDataSource; use crate::pfsys::TranscriptType; @@ -250,29 +248,24 @@ impl From<&str> for ContractType { } } - -#[cfg(not(target_arch = "wasm32"))] #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, PartialOrd)] /// wrapper for H160 to make it easy to parse into flag vals pub struct H160Flag { inner: H160, } -#[cfg(not(target_arch = "wasm32"))] impl From for H160 { fn from(val: H160Flag) -> H160 { val.inner } } -#[cfg(not(target_arch = "wasm32"))] impl ToFlags for H160Flag { fn to_flags(&self) -> Vec { vec![format!("{:#x}", self.inner)] } } -#[cfg(not(target_arch = "wasm32"))] impl From<&str> for H160Flag { fn from(s: &str) -> Self { Self { @@ -468,8 +461,7 @@ pub enum Commands { }, /// Calibrates the proving scale, lookup bits and logrows from a circuit settings file. - #[cfg(not(target_arch = "wasm32"))] - CalibrateSettings { + CalibrateSettings { /// The path to the .json calibration data file. #[arg(short = 'D', long, default_value = DEFAULT_CALIBRATION_FILE, value_hint = clap::ValueHint::FilePath)] data: Option, @@ -519,8 +511,7 @@ pub enum Commands { commitment: Option, }, - #[cfg(not(target_arch = "wasm32"))] - /// Gets an SRS from a circuit settings file. + /// Gets an SRS from a circuit settings file. #[command(name = "get-srs")] GetSrs { /// The path to output the desired srs file, if set to None will save to $EZKL_REPO_PATH/srs @@ -655,8 +646,7 @@ pub enum Commands { #[arg(long, default_value = DEFAULT_DISABLE_SELECTOR_COMPRESSION, action = clap::ArgAction::SetTrue)] disable_selector_compression: Option, }, - #[cfg(not(target_arch = "wasm32"))] - /// Deploys a test contact that the data attester reads from and creates a data attestation formatted input.json file that contains call data information + /// Deploys a test contact that the data attester reads from and creates a data attestation formatted input.json file that contains call data information #[command(arg_required_else_help = true)] SetupTestEvmData { /// 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) @@ -680,8 +670,7 @@ pub enum Commands { #[arg(long, default_value = "on-chain", value_hint = clap::ValueHint::Other)] output_source: TestDataSource, }, - #[cfg(not(target_arch = "wasm32"))] - /// The Data Attestation Verifier contract stores the account calls to fetch data to feed into ezkl. This call data can be updated by an admin account. This tests that admin account is able to update this call data. + /// The Data Attestation Verifier contract stores the account calls to fetch data to feed into ezkl. This call data can be updated by an admin account. This tests that admin account is able to update this call data. #[command(arg_required_else_help = true)] TestUpdateAccountCalls { /// The path to the verifier contract's address @@ -694,8 +683,7 @@ pub enum Commands { #[arg(short = 'U', long, value_hint = clap::ValueHint::Url)] rpc_url: Option, }, - #[cfg(not(target_arch = "wasm32"))] - /// Swaps the positions in the transcript that correspond to commitments + /// Swaps the positions in the transcript that correspond to commitments SwapProofCommitments { /// The path to the proof file #[arg(short = 'P', long, default_value = DEFAULT_PROOF, value_hint = clap::ValueHint::FilePath)] @@ -705,8 +693,7 @@ pub enum Commands { witness_path: Option, }, - #[cfg(not(target_arch = "wasm32"))] - /// Loads model, data, and creates proof + /// Loads model, data, and creates proof Prove { /// The path to the .json witness file (generated using the gen-witness command) #[arg(short = 'W', long, default_value = DEFAULT_WITNESS, value_hint = clap::ValueHint::FilePath)] @@ -736,8 +723,7 @@ pub enum Commands { #[arg(long, default_value = DEFAULT_CHECKMODE, value_hint = clap::ValueHint::Other)] check_mode: Option, }, - #[cfg(not(target_arch = "wasm32"))] - /// Encodes a proof into evm calldata + /// Encodes a proof into evm calldata #[command(name = "encode-evm-calldata")] EncodeEvmCalldata { /// The path to the proof file (generated using the prove command) @@ -750,8 +736,7 @@ pub enum Commands { #[arg(long, value_hint = clap::ValueHint::Other)] addr_vk: Option, }, - #[cfg(not(target_arch = "wasm32"))] - /// Creates an Evm verifier for a single proof + /// Creates an Evm verifier for a single proof #[command(name = "create-evm-verifier")] CreateEvmVerifier { /// The path to SRS, if None will use $EZKL_REPO_PATH/srs/kzg{logrows}.srs @@ -769,12 +754,11 @@ pub enum Commands { /// The path to output the Solidity verifier ABI #[arg(long, default_value = DEFAULT_VERIFIER_ABI, value_hint = clap::ValueHint::FilePath)] abi_path: Option, - /// Whether the to render the verifier as reusable or not. If true, you will need to deploy a VK artifact, passing it as part of the calldata to the verifier. + /// Whether the to render the verifier as reusable or not. If true, you will need to deploy a VK artifact, passing it as part of the calldata to the verifier. #[arg(long, default_value = DEFAULT_RENDER_REUSABLE, action = clap::ArgAction::SetTrue)] reusable: Option, }, - #[cfg(not(target_arch = "wasm32"))] - /// Creates an Evm verifier artifact for a single proof to be used by the reusable verifier + /// Creates an Evm verifier artifact for a single proof to be used by the reusable verifier #[command(name = "create-evm-vka")] CreateEvmVKArtifact { /// The path to SRS, if None will use $EZKL_REPO_PATH/srs/kzg{logrows}.srs @@ -793,8 +777,7 @@ pub enum Commands { #[arg(long, default_value = DEFAULT_VK_ABI, value_hint = clap::ValueHint::FilePath)] abi_path: Option, }, - #[cfg(not(target_arch = "wasm32"))] - /// Creates an Evm verifier that attests to on-chain inputs for a single proof + /// Creates an Evm verifier that attests to on-chain inputs for a single proof #[command(name = "create-evm-da")] CreateEvmDataAttestation { /// The path to load circuit settings .json file from (generated using the gen-settings command) @@ -818,8 +801,7 @@ pub enum Commands { witness: Option, }, - #[cfg(not(target_arch = "wasm32"))] - /// Creates an Evm verifier for an aggregate proof + /// Creates an Evm verifier for an aggregate proof #[command(name = "create-evm-verifier-aggr")] CreateEvmVerifierAggr { /// The path to SRS, if None will use $EZKL_REPO_PATH/srs/kzg{logrows}.srs @@ -840,7 +822,7 @@ pub enum Commands { // logrows used for aggregation circuit #[arg(long, default_value = DEFAULT_AGGREGATED_LOGROWS, value_hint = clap::ValueHint::Other)] logrows: Option, - /// Whether the to render the verifier as reusable or not. If true, you will need to deploy a VK artifact, passing it as part of the calldata to the verifier. + /// Whether the to render the verifier as reusable or not. If true, you will need to deploy a VK artifact, passing it as part of the calldata to the verifier. #[arg(long, default_value = DEFAULT_RENDER_REUSABLE, action = clap::ArgAction::SetTrue)] reusable: Option, }, @@ -883,8 +865,7 @@ pub enum Commands { #[arg(long, default_value = DEFAULT_COMMITMENT, value_hint = clap::ValueHint::Other)] commitment: Option, }, - #[cfg(not(target_arch = "wasm32"))] - /// Deploys an evm contract (verifier, reusable verifier, or vk artifact) that is generated by ezkl + /// Deploys an evm contract (verifier, reusable verifier, or vk artifact) that is generated by ezkl DeployEvm { /// The path to the Solidity code (generated using the create-evm-verifier command) #[arg(long, default_value = DEFAULT_SOL_CODE, value_hint = clap::ValueHint::FilePath)] @@ -902,7 +883,7 @@ pub enum Commands { #[arg(short = 'P', long, value_hint = clap::ValueHint::Other)] private_key: Option, /// Deployed verifier manager contract's address - /// Use to facilitate reusable verifier and vk artifact deployment + /// Used to facilitate reusable verifier and vk artifact deployment #[arg(long, value_hint = clap::ValueHint::Other)] addr_verifier_manager: Option, /// Deployed reusable verifier contract's address @@ -913,8 +894,7 @@ pub enum Commands { #[arg(long = "contract-type", short = 'C', default_value = DEFAULT_CONTRACT_DEPLOYMENT_TYPE, value_hint = clap::ValueHint::Other)] contract: ContractType, }, - #[cfg(not(target_arch = "wasm32"))] - /// Deploys an evm verifier that allows for data attestation + /// Deploys an evm verifier that allows for data attestation #[command(name = "deploy-evm-da")] 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) @@ -939,8 +919,7 @@ pub enum Commands { #[arg(short = 'P', long, value_hint = clap::ValueHint::Other)] private_key: Option, }, - #[cfg(not(target_arch = "wasm32"))] - /// Verifies a proof using a local Evm executor, returning accept or reject + /// Verifies a proof using a local Evm executor, returning accept or reject #[command(name = "verify-evm")] VerifyEvm { /// The path to the proof file (generated using the prove command) diff --git a/src/eth.rs b/src/eth.rs index d0fe3be16..eaa4dddb1 100644 --- a/src/eth.rs +++ b/src/eth.rs @@ -1,7 +1,6 @@ use crate::graph::input::{CallsToAccount, FileSourceInner, GraphData}; use crate::graph::modules::POSEIDON_INSTANCES; use crate::graph::DataSource; -#[cfg(not(target_arch = "wasm32"))] use crate::graph::GraphSettings; use crate::pfsys::evm::EvmVerificationError; use crate::pfsys::Snark; @@ -11,8 +10,6 @@ use alloy::core::primitives::Bytes; use alloy::core::primitives::U256; use alloy::dyn_abi::abi::token::{DynSeqToken, PackedSeqToken, WordToken}; use alloy::dyn_abi::abi::TokenSeq; -#[cfg(target_arch = "wasm32")] -use alloy::prelude::Wallet; // use alloy::providers::Middleware; use alloy::json_abi::JsonAbi; use alloy::node_bindings::Anvil; @@ -295,7 +292,6 @@ pub type EthersClient = Arc< pub type ContractFactory = CallBuilder, Arc, ()>; /// 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"))] pub async fn setup_eth_backend( rpc_url: Option<&str>, private_key: Option<&str>, @@ -717,7 +713,6 @@ pub async fn update_account_calls( } /// Verify a proof using a Solidity verifier contract -#[cfg(not(target_arch = "wasm32"))] pub async fn verify_proof_via_solidity( proof: Snark, addr: H160, @@ -819,7 +814,6 @@ pub async fn setup_test_contract, Ethereum>>( /// 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_verifier: H160, @@ -932,7 +926,6 @@ pub async fn test_on_chain_data, Ethereum>>( } /// Reads on-chain inputs, returning the raw encoded data returned from making all the calls in on_chain_input_data -#[cfg(not(target_arch = "wasm32"))] pub async fn read_on_chain_inputs, Ethereum>>( client: Arc, address: H160, @@ -966,7 +959,6 @@ pub async fn read_on_chain_inputs, Ethereum>> } /// -#[cfg(not(target_arch = "wasm32"))] pub async fn evm_quantize, Ethereum>>( client: Arc, scales: Vec, @@ -1067,7 +1059,6 @@ fn get_sol_contract_factory<'a, M: 'static + Provider, Ethereum>, T } /// Compiles a solidity verifier contract and returns the abi, bytecode, and runtime bytecode -#[cfg(not(target_arch = "wasm32"))] pub async fn get_contract_artifacts( sol_code_path: PathBuf, contract_name: &str, diff --git a/src/execute.rs b/src/execute.rs index 546448fc5..e5846ac43 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -1,18 +1,13 @@ use crate::circuit::region::RegionSettings; use crate::circuit::CheckMode; -#[cfg(not(target_arch = "wasm32"))] use crate::commands::CalibrationTarget; -#[cfg(not(target_arch = "wasm32"))] use crate::eth::{deploy_contract_via_solidity, deploy_da_verifier_via_solidity}; -#[cfg(not(target_arch = "wasm32"))] #[allow(unused_imports)] 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"))] use crate::graph::{TestDataSource, TestSources}; use crate::pfsys::evm::aggregation_kzg::{AggregationCircuit, PoseidonTranscript}; -#[cfg(not(target_arch = "wasm32"))] use crate::pfsys::{ create_keys, load_pk, load_vk, save_params, save_pk, Snark, StrategyType, TranscriptType, }; @@ -21,11 +16,9 @@ use crate::pfsys::{ }; use crate::pfsys::{save_vk, srs::*}; use crate::tensor::TensorError; -#[cfg(not(target_arch = "wasm32"))] use crate::EZKL_BUF_CAPACITY; use crate::{commands::*, EZKLError}; use crate::{Commitments, RunArgs}; -#[cfg(not(target_arch = "wasm32"))] use colored::Colorize; #[cfg(unix)] use gag::Gag; @@ -45,17 +38,13 @@ use halo2_proofs::poly::kzg::{ }; use halo2_proofs::poly::VerificationStrategy; use halo2_proofs::transcript::{EncodedChallenge, TranscriptReadBuffer}; -#[cfg(not(target_arch = "wasm32"))] use halo2_solidity_verifier; use halo2curves::bn256::{Bn256, Fr, G1Affine}; use halo2curves::ff::{FromUniformBytes, WithSmallOrderMulGroup}; use halo2curves::serde::SerdeObject; -#[cfg(not(target_arch = "wasm32"))] use indicatif::{ProgressBar, ProgressStyle}; use instant::Instant; -#[cfg(not(target_arch = "wasm32"))] use itertools::Itertools; -#[cfg(not(target_arch = "wasm32"))] use log::debug; use log::{info, trace, warn}; use serde::de::DeserializeOwned; @@ -65,9 +54,7 @@ use snark_verifier::system::halo2::compile; use snark_verifier::system::halo2::transcript::evm::EvmTranscript; use snark_verifier::system::halo2::Config; use std::fs::File; -#[cfg(not(target_arch = "wasm32"))] use std::io::BufWriter; -#[cfg(not(target_arch = "wasm32"))] use std::io::{Cursor, Write}; use std::path::Path; use std::path::PathBuf; @@ -128,7 +115,6 @@ pub async fn run(command: Commands) -> Result { logrows as u32, commitment.unwrap_or(Commitments::from_str(DEFAULT_COMMITMENT).unwrap()), ), - #[cfg(not(target_arch = "wasm32"))] Commands::GetSrs { srs_path, settings_path, @@ -145,7 +131,6 @@ pub async fn run(command: Commands) -> Result { settings_path.unwrap_or(DEFAULT_SETTINGS.into()), args, ), - #[cfg(not(target_arch = "wasm32"))] Commands::CalibrateSettings { model, settings_path, @@ -188,7 +173,6 @@ pub async fn run(command: Commands) -> Result { model.unwrap_or(DEFAULT_MODEL.into()), witness.unwrap_or(DEFAULT_WITNESS.into()), ), - #[cfg(not(target_arch = "wasm32"))] Commands::CreateEvmVerifier { vk_path, srs_path, @@ -207,7 +191,6 @@ pub async fn run(command: Commands) -> Result { ) .await } - #[cfg(not(target_arch = "wasm32"))] Commands::EncodeEvmCalldata { proof_path, calldata_path, @@ -235,7 +218,6 @@ pub async fn run(command: Commands) -> Result { ) .await } - #[cfg(not(target_arch = "wasm32"))] Commands::CreateEvmDataAttestation { settings_path, sol_code_path, @@ -252,7 +234,6 @@ pub async fn run(command: Commands) -> Result { ) .await } - #[cfg(not(target_arch = "wasm32"))] Commands::CreateEvmVerifierAggr { vk_path, srs_path, @@ -298,7 +279,6 @@ pub async fn run(command: Commands) -> Result { disable_selector_compression .unwrap_or(DEFAULT_DISABLE_SELECTOR_COMPRESSION.parse().unwrap()), ), - #[cfg(not(target_arch = "wasm32"))] Commands::SetupTestEvmData { data, compiled_circuit, @@ -317,13 +297,11 @@ pub async fn run(command: Commands) -> Result { ) .await } - #[cfg(not(target_arch = "wasm32"))] Commands::TestUpdateAccountCalls { addr, data, rpc_url, } => test_update_account_calls(addr, data.unwrap_or(DEFAULT_DATA.into()), rpc_url).await, - #[cfg(not(target_arch = "wasm32"))] Commands::SwapProofCommitments { proof_path, witness_path, @@ -333,7 +311,6 @@ pub async fn run(command: Commands) -> Result { ) .map(|e| serde_json::to_string(&e).unwrap()), - #[cfg(not(target_arch = "wasm32"))] Commands::Prove { witness, compiled_circuit, @@ -493,7 +470,6 @@ pub async fn run(command: Commands) -> Result { ) .await } - #[cfg(not(target_arch = "wasm32"))] Commands::VerifyEvm { proof_path, addr_verifier, @@ -613,7 +589,6 @@ pub(crate) fn gen_srs_cmd( Ok(String::new()) } -#[cfg(not(target_arch = "wasm32"))] async fn fetch_srs(uri: &str) -> Result, EZKLError> { let pb = { let pb = init_spinner(); @@ -633,7 +608,6 @@ async fn fetch_srs(uri: &str) -> Result, EZKLError> { Ok(std::mem::take(&mut buf)) } -#[cfg(not(target_arch = "wasm32"))] pub(crate) fn get_file_hash(path: &PathBuf) -> Result { use std::io::Read; let file = std::fs::File::open(path)?; @@ -652,7 +626,6 @@ pub(crate) fn get_file_hash(path: &PathBuf) -> Result { Ok(hash) } -#[cfg(not(target_arch = "wasm32"))] fn check_srs_hash( logrows: u32, srs_path: Option, @@ -678,7 +651,6 @@ fn check_srs_hash( Ok(hash) } -#[cfg(not(target_arch = "wasm32"))] pub(crate) async fn get_srs_cmd( srs_path: Option, settings_path: Option, @@ -721,12 +693,9 @@ pub(crate) async fn get_srs_cmd( let srs_uri = format!("{}{}", PUBLIC_SRS_URL, k); let mut reader = Cursor::new(fetch_srs(&srs_uri).await?); // check the SRS - #[cfg(not(target_arch = "wasm32"))] let pb = init_spinner(); - #[cfg(not(target_arch = "wasm32"))] pb.set_message("Validating SRS (this may take a while) ..."); let params = ParamsKZG::::read(&mut reader)?; - #[cfg(not(target_arch = "wasm32"))] pb.finish_with_message("SRS validated."); info!("Saving SRS to disk..."); @@ -780,9 +749,8 @@ pub(crate) async fn gen_witness( None }; - #[cfg(not(target_arch = "wasm32"))] let mut input = circuit.load_graph_input(&data).await?; - #[cfg(target_arch = "wasm32")] + #[cfg(any(not(feature = "ezkl"), target_arch = "wasm32"))] let mut input = circuit.load_graph_input(&data)?; // if any of the settings have kzg visibility then we need to load the srs @@ -878,7 +846,6 @@ pub(crate) fn gen_circuit_settings( } // not for wasm targets -#[cfg(not(target_arch = "wasm32"))] pub(crate) fn init_spinner() -> ProgressBar { let pb = indicatif::ProgressBar::new_spinner(); pb.set_draw_target(indicatif::ProgressDrawTarget::stdout()); @@ -900,7 +867,6 @@ pub(crate) fn init_spinner() -> ProgressBar { } // not for wasm targets -#[cfg(not(target_arch = "wasm32"))] pub(crate) fn init_bar(len: u64) -> ProgressBar { let pb = ProgressBar::new(len); pb.set_draw_target(indicatif::ProgressDrawTarget::stdout()); @@ -914,7 +880,6 @@ pub(crate) fn init_bar(len: u64) -> ProgressBar { pb } -#[cfg(not(target_arch = "wasm32"))] use colored_json::ToColoredJson; #[derive(Debug, Clone, Tabled)] @@ -1014,7 +979,6 @@ impl AccuracyResults { } /// Calibrate the circuit parameters to a given a dataset -#[cfg(not(target_arch = "wasm32"))] #[allow(trivial_casts)] #[allow(clippy::too_many_arguments)] pub(crate) async fn calibrate( @@ -1147,12 +1111,12 @@ pub(crate) async fn calibrate( }; // if unix get a gag - #[cfg(unix)] + #[cfg(all(not(not(feature = "ezkl")), unix))] let _r = match Gag::stdout() { Ok(g) => Some(g), _ => None, }; - #[cfg(unix)] + #[cfg(all(not(not(feature = "ezkl")), unix))] let _g = match Gag::stderr() { Ok(g) => Some(g), _ => None, @@ -1211,9 +1175,9 @@ pub(crate) async fn calibrate( } // drop the gag - #[cfg(unix)] + #[cfg(all(not(not(feature = "ezkl")), unix))] drop(_r); - #[cfg(unix)] + #[cfg(all(not(not(feature = "ezkl")), unix))] drop(_g); let result = forward_pass_res.get(&key).ok_or("key not found")?; @@ -1261,6 +1225,7 @@ pub(crate) async fn calibrate( num_rows: new_settings.num_rows, total_assignments: new_settings.total_assignments, total_const_size: new_settings.total_const_size, + total_dynamic_col_size: new_settings.total_dynamic_col_size, ..settings.clone() }; @@ -1378,7 +1343,9 @@ pub(crate) async fn calibrate( let lookup_log_rows = best_params.lookup_log_rows_with_blinding(); let module_log_row = best_params.module_constraint_logrows_with_blinding(); let instance_logrows = best_params.log2_total_instances_with_blinding(); - let dynamic_lookup_logrows = best_params.dynamic_lookup_and_shuffle_logrows_with_blinding(); + let dynamic_lookup_logrows = + best_params.min_dynamic_lookup_and_shuffle_logrows_with_blinding(); + let range_check_logrows = best_params.range_check_log_rows_with_blinding(); let mut reduction = std::cmp::max(lookup_log_rows, module_log_row); @@ -1428,7 +1395,6 @@ pub(crate) fn mock( Ok(String::new()) } -#[cfg(not(target_arch = "wasm32"))] pub(crate) async fn create_evm_verifier( vk_path: PathBuf, srs_path: Option, @@ -1525,7 +1491,6 @@ pub(crate) async fn create_evm_vka( Ok(String::new()) } -#[cfg(not(target_arch = "wasm32"))] pub(crate) async fn create_evm_data_attestation( settings_path: PathBuf, sol_code_path: PathBuf, @@ -1602,7 +1567,6 @@ pub(crate) async fn create_evm_data_attestation( Ok(String::new()) } -#[cfg(not(target_arch = "wasm32"))] pub(crate) async fn deploy_da_evm( data: PathBuf, settings_path: PathBuf, @@ -1629,7 +1593,6 @@ pub(crate) async fn deploy_da_evm( Ok(String::new()) } -#[cfg(not(target_arch = "wasm32"))] pub(crate) async fn deploy_evm( sol_code_path: PathBuf, rpc_url: Option, @@ -1715,7 +1678,6 @@ pub(crate) fn encode_evm_calldata( Ok(encoded) } -#[cfg(not(target_arch = "wasm32"))] pub(crate) async fn verify_evm( proof_path: PathBuf, addr_verifier: H160Flag, @@ -1755,7 +1717,6 @@ pub(crate) async fn verify_evm( Ok(String::new()) } -#[cfg(not(target_arch = "wasm32"))] pub(crate) async fn create_evm_aggregate_verifier( vk_path: PathBuf, srs_path: Option, @@ -1879,7 +1840,6 @@ pub(crate) fn setup( Ok(String::new()) } -#[cfg(not(target_arch = "wasm32"))] pub(crate) async fn setup_test_evm_witness( data_path: PathBuf, compiled_circuit_path: PathBuf, @@ -1915,9 +1875,7 @@ pub(crate) async fn setup_test_evm_witness( Ok(String::new()) } -#[cfg(not(target_arch = "wasm32"))] use crate::pfsys::ProofType; -#[cfg(not(target_arch = "wasm32"))] pub(crate) async fn test_update_account_calls( addr: H160Flag, data: PathBuf, @@ -1930,7 +1888,6 @@ pub(crate) async fn test_update_account_calls( Ok(String::new()) } -#[cfg(not(target_arch = "wasm32"))] #[allow(clippy::too_many_arguments)] pub(crate) fn prove( data_path: PathBuf, @@ -2128,7 +2085,6 @@ pub(crate) fn mock_aggregate( } } // proof aggregation - #[cfg(not(target_arch = "wasm32"))] let pb = { let pb = init_spinner(); pb.set_message("Aggregating (may take a while)..."); @@ -2140,7 +2096,6 @@ pub(crate) fn mock_aggregate( let prover = halo2_proofs::dev::MockProver::run(logrows, &circuit, vec![circuit.instances()]) .map_err(|e| ExecutionError::MockProverError(e.to_string()))?; prover.verify().map_err(ExecutionError::VerifyError)?; - #[cfg(not(target_arch = "wasm32"))] pb.finish_with_message("Done."); Ok(String::new()) } @@ -2235,7 +2190,6 @@ pub(crate) fn aggregate( } // proof aggregation - #[cfg(not(target_arch = "wasm32"))] let pb = { let pb = init_spinner(); pb.set_message("Aggregating (may take a while)..."); @@ -2385,7 +2339,6 @@ pub(crate) fn aggregate( ); snark.save(&proof_path)?; - #[cfg(not(target_arch = "wasm32"))] pb.finish_with_message("Done."); Ok(snark) diff --git a/src/graph/errors.rs b/src/graph/errors.rs index a7652fc0b..2b7e1efd1 100644 --- a/src/graph/errors.rs +++ b/src/graph/errors.rs @@ -48,7 +48,10 @@ pub enum GraphError { #[error("failed to ser/deser model: {0}")] ModelSerialize(#[from] bincode::Error), /// Tract error - #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] + #[cfg(all( + feature = "ezkl", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] #[error("[tract] {0}")] TractError(#[from] tract_onnx::prelude::TractError), /// Packing exponent is too large @@ -85,11 +88,17 @@ pub enum GraphError { #[error("unknown dimension batch_size in model inputs, set batch_size in variables")] MissingBatchSize, /// Tokio postgres error - #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] + #[cfg(all( + feature = "ezkl", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] #[error("[tokio postgres] {0}")] TokioPostgresError(#[from] tokio_postgres::Error), /// Eth error - #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] + #[cfg(all( + feature = "ezkl", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] #[error("[eth] {0}")] EthError(#[from] crate::eth::EthError), /// Json error diff --git a/src/graph/input.rs b/src/graph/input.rs index e3f324c64..dbef47ecc 100644 --- a/src/graph/input.rs +++ b/src/graph/input.rs @@ -2,9 +2,9 @@ use super::errors::GraphError; use super::quantize_float; use crate::circuit::InputType; use crate::fieldutils::integer_rep_to_felt; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use crate::graph::postgres::Client; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use crate::tensor::Tensor; use crate::EZKL_BUF_CAPACITY; use halo2curves::bn256::Fr as Fp; @@ -20,12 +20,12 @@ use std::io::BufReader; use std::io::BufWriter; use std::io::Read; use std::panic::UnwindSafe; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tract_onnx::tract_core::{ tract_data::{prelude::Tensor as TractTensor, TVec}, value::TValue, }; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tract_onnx::tract_hir::tract_num_traits::ToPrimitive; type Decimals = u8; @@ -171,7 +171,7 @@ impl OnChainSource { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] /// Inner elements of inputs/outputs coming from postgres DB #[derive(Clone, Debug, Deserialize, Serialize, Default, PartialOrd, PartialEq)] pub struct PostgresSource { @@ -189,7 +189,7 @@ pub struct PostgresSource { pub port: String, } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] impl PostgresSource { /// Create a new PostgresSource pub fn new( @@ -268,7 +268,7 @@ impl PostgresSource { } impl OnChainSource { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] /// Create dummy local on-chain data to test the OnChain data source pub async fn test_from_file_data( data: &FileSource, @@ -359,7 +359,7 @@ pub enum DataSource { /// On-chain data source. The first element is the calls to the account, and the second is the RPC url. OnChain(OnChainSource), /// Postgres DB - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] DB(PostgresSource), } @@ -419,7 +419,7 @@ impl<'de> Deserialize<'de> for DataSource { if let Ok(t) = second_try { return Ok(DataSource::OnChain(t)); } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] { let third_try: Result = serde_json::from_str(this_json.get()); if let Ok(t) = third_try { @@ -445,7 +445,7 @@ impl UnwindSafe for GraphData {} impl GraphData { // not wasm - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] /// Convert the input data to tract data pub fn to_tract_data( &self, @@ -530,7 +530,7 @@ impl GraphData { "on-chain data cannot be split into batches".to_string(), )) } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] GraphData { input_data: DataSource::DB(data), output_data: _, diff --git a/src/graph/mod.rs b/src/graph/mod.rs index 667744d65..763630abf 100644 --- a/src/graph/mod.rs +++ b/src/graph/mod.rs @@ -7,7 +7,7 @@ pub mod modules; /// Inner elements of a computational graph that represent a single operation / constraints. pub mod node; /// postgres helper functions -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub mod postgres; /// Helper functions pub mod utilities; @@ -17,18 +17,19 @@ pub mod vars; /// errors for the graph pub mod errors; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use colored_json::ToColoredJson; -#[cfg(unix)] +#[cfg(all(not(not(feature = "ezkl")), unix))] use gag::Gag; use halo2_proofs::plonk::VerifyingKey; use halo2_proofs::poly::commitment::CommitmentScheme; pub use input::DataSource; use itertools::Itertools; +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tosubcommand::ToFlags; use self::errors::GraphError; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use self::input::OnChainSource; use self::input::{FileSource, GraphData}; use self::modules::{GraphModules, ModuleConfigs, ModuleForwardResult, ModuleSizes}; @@ -48,7 +49,7 @@ use halo2_proofs::{ }; use halo2curves::bn256::{self, Fr as Fp, G1Affine}; use halo2curves::ff::{Field, PrimeField}; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use lazy_static::lazy_static; use log::{debug, error, trace, warn}; use maybe_rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; @@ -78,7 +79,7 @@ pub const MAX_NUM_LOOKUP_COLS: usize = 12; pub const MAX_LOOKUP_ABS: IntegerRep = (MAX_NUM_LOOKUP_COLS as IntegerRep) * 2_i128.pow(MAX_PUBLIC_SRS); -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] lazy_static! { /// Max circuit area pub static ref EZKL_MAX_CIRCUIT_AREA: Option = @@ -89,7 +90,7 @@ lazy_static! { }; } -#[cfg(target_arch = "wasm32")] +#[cfg(any(not(feature = "ezkl"), target_arch = "wasm32"))] const EZKL_MAX_CIRCUIT_AREA: Option = None; /// @@ -384,7 +385,7 @@ fn insert_poseidon_hash_pydict(pydict: &PyDict, poseidon_hash: &Vec) -> Resu #[cfg(feature = "python-bindings")] fn insert_polycommit_pydict(pydict: &PyDict, commits: &Vec>) -> Result<(), PyErr> { - use crate::python::PyG1Affine; + use crate::bindings::python::PyG1Affine; let poseidon_hash: Vec> = commits .iter() .map(|c| c.iter().map(|x| PyG1Affine::from(*x)).collect()) @@ -407,6 +408,8 @@ pub struct GraphSettings { pub total_const_size: usize, /// total dynamic column size pub total_dynamic_col_size: usize, + /// max dynamic column input length + pub max_dynamic_input_len: usize, /// number of dynamic lookups pub num_dynamic_lookups: usize, /// number of shuffles @@ -484,6 +487,13 @@ impl GraphSettings { .ceil() as u32 } + /// calculate the number of rows required for the dynamic lookup and shuffle + pub fn min_dynamic_lookup_and_shuffle_logrows_with_blinding(&self) -> u32 { + (self.max_dynamic_input_len as f64 + RESERVED_BLINDING_ROWS as f64) + .log2() + .ceil() as u32 + } + fn dynamic_lookup_and_shuffle_col_size(&self) -> usize { self.total_dynamic_col_size + self.total_shuffle_col_size } @@ -697,6 +707,7 @@ impl std::fmt::Display for TestDataSource { } } +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] impl ToFlags for TestDataSource {} impl From for TestDataSource { @@ -885,7 +896,7 @@ impl GraphCircuit { public_inputs.processed_outputs = elements.processed_outputs.clone(); } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] debug!( "rescaled and processed public inputs: {}", serde_json::to_string(&public_inputs)?.to_colored_json_auto()? @@ -895,7 +906,7 @@ impl GraphCircuit { } /// - #[cfg(target_arch = "wasm32")] + #[cfg(any(not(feature = "ezkl"), target_arch = "wasm32"))] pub fn load_graph_input(&mut self, data: &GraphData) -> Result>, GraphError> { let shapes = self.model().graph.input_shapes()?; let scales = self.model().graph.get_input_scales(); @@ -922,7 +933,7 @@ impl GraphCircuit { } /// - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub async fn load_graph_input( &mut self, data: &GraphData, @@ -936,7 +947,7 @@ impl GraphCircuit { .await } - #[cfg(target_arch = "wasm32")] + #[cfg(any(not(feature = "ezkl"), target_arch = "wasm32"))] /// Process the data source for the model fn process_data_source( &mut self, @@ -953,7 +964,7 @@ impl GraphCircuit { } } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] /// Process the data source for the model async fn process_data_source( &mut self, @@ -983,7 +994,7 @@ impl GraphCircuit { } /// Prepare on chain test data - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub async fn load_on_chain_data( &mut self, source: OnChainSource, @@ -1202,12 +1213,12 @@ impl GraphCircuit { settings.required_range_checks = vec![(0, max_range_size)]; let mut cs = ConstraintSystem::default(); // if unix get a gag - #[cfg(unix)] + #[cfg(all(not(not(feature = "ezkl")), unix))] let _r = match Gag::stdout() { Ok(g) => Some(g), _ => None, }; - #[cfg(unix)] + #[cfg(all(not(not(feature = "ezkl")), unix))] let _g = match Gag::stderr() { Ok(g) => Some(g), _ => None, @@ -1216,9 +1227,9 @@ impl GraphCircuit { Self::configure_with_params(&mut cs, settings); // drop the gag - #[cfg(unix)] + #[cfg(all(not(not(feature = "ezkl")), unix))] drop(_r); - #[cfg(unix)] + #[cfg(all(not(not(feature = "ezkl")), unix))] drop(_g); #[cfg(feature = "mv-lookup")] @@ -1347,7 +1358,7 @@ impl GraphCircuit { visibility, ); - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] log::trace!( "witness: \n {}", &witness.as_json()?.to_colored_json_auto()? @@ -1357,7 +1368,7 @@ impl GraphCircuit { } /// Create a new circuit from a set of input data and [RunArgs]. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub fn from_run_args( run_args: &RunArgs, model_path: &std::path::Path, @@ -1367,7 +1378,7 @@ impl GraphCircuit { } /// Create a new circuit from a set of input data and [GraphSettings]. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub fn from_settings( params: &GraphSettings, model_path: &std::path::Path, @@ -1382,7 +1393,7 @@ impl GraphCircuit { } /// - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub async fn populate_on_chain_test_data( &mut self, data: &mut GraphData, @@ -1475,7 +1486,7 @@ impl CircuitSize { } } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] /// Export the ezkl configuration as json pub fn as_json(&self) -> Result { let serialized = match serde_json::to_string(&self) { @@ -1563,7 +1574,7 @@ impl Circuit for GraphCircuit { let circuit_size = CircuitSize::from_cs(cs, params.run_args.logrows); - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] debug!( "circuit size: \n {}", circuit_size diff --git a/src/graph/model.rs b/src/graph/model.rs index e53c4c098..eb89344ad 100644 --- a/src/graph/model.rs +++ b/src/graph/model.rs @@ -21,9 +21,9 @@ use crate::{ }; use halo2curves::bn256::Fr as Fp; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use super::input::GraphData; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use colored::Colorize; use halo2_proofs::{ circuit::{Layouter, Value}, @@ -36,29 +36,29 @@ use log::{debug, info, trace}; use serde::Deserialize; use serde::Serialize; use std::collections::BTreeMap; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use std::collections::HashMap; use std::collections::HashSet; use std::fs; use std::io::Read; use std::path::PathBuf; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tabled::Table; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tract_onnx; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tract_onnx::prelude::{ Framework, Graph, InferenceFact, InferenceModelExt, SymbolValues, TypedFact, TypedOp, }; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tract_onnx::tract_core::internal::DatumType; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tract_onnx::tract_hir::ops::scan::Scan; use unzip_n::unzip_n; unzip_n!(pub 3); -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] type TractResult = (Graph>, SymbolValues); /// The result of a forward pass. #[derive(Clone, Debug)] @@ -103,6 +103,8 @@ pub struct DummyPassRes { pub num_rows: usize, /// num dynamic lookups pub num_dynamic_lookups: usize, + /// max dynamic lookup input len + pub max_dynamic_input_len: usize, /// dynamic lookup col size pub dynamic_lookup_col_coord: usize, /// num shuffles @@ -360,6 +362,14 @@ impl NodeType { NodeType::SubGraph { .. } => SupportedOp::Unknown(Unknown), } } + + /// check if it is a softmax + pub fn is_softmax(&self) -> bool { + match self { + NodeType::Node(n) => n.is_softmax(), + NodeType::SubGraph { .. } => false, + } + } } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] @@ -470,7 +480,7 @@ impl Model { /// # Arguments /// * `reader` - A reader for an Onnx file. /// * `run_args` - [RunArgs] - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub fn new(reader: &mut dyn std::io::Read, run_args: &RunArgs) -> Result { let visibility = VarVisibility::from_args(run_args)?; @@ -517,7 +527,7 @@ impl Model { check_mode: CheckMode, ) -> Result { let instance_shapes = self.instance_shapes()?; - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] debug!( "{} {} {}", "model has".blue(), @@ -562,6 +572,7 @@ impl Model { num_rows: res.num_rows, total_assignments: res.linear_coord, required_lookups: res.lookup_ops.into_iter().collect(), + max_dynamic_input_len: res.max_dynamic_input_len, required_range_checks: res.range_checks.into_iter().collect(), model_output_scales: self.graph.get_output_scales()?, model_input_scales: self.graph.get_input_scales(), @@ -574,13 +585,13 @@ impl Model { version: env!("CARGO_PKG_VERSION").to_string(), num_blinding_factors: None, // unix time timestamp - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] timestamp: Some( instant::SystemTime::now() .duration_since(instant::SystemTime::UNIX_EPOCH)? .as_millis(), ), - #[cfg(target_arch = "wasm32")] + #[cfg(any(not(feature = "ezkl"), target_arch = "wasm32"))] timestamp: None, }) } @@ -609,7 +620,7 @@ impl Model { /// * `reader` - A reader for an Onnx file. /// * `scale` - The scale to use for quantization. /// * `public_params` - Whether to make the params public. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] fn load_onnx_using_tract( reader: &mut dyn std::io::Read, run_args: &RunArgs, @@ -664,7 +675,7 @@ impl Model { /// * `reader` - A reader for an Onnx file. /// * `scale` - The scale to use for quantization. /// * `public_params` - Whether to make the params public. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] fn load_onnx_model( reader: &mut dyn std::io::Read, run_args: &RunArgs, @@ -700,7 +711,7 @@ impl Model { } /// Formats nodes (including subgraphs) into tables ! - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub fn table_nodes(&self) -> String { let mut node_accumulator = vec![]; let mut string = String::new(); @@ -742,7 +753,7 @@ impl Model { /// * `visibility` - Which inputs to the model are public and private (params, inputs, outputs) using [VarVisibility]. /// * `input_scales` - The scales of the model's inputs. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub fn nodes_from_graph( graph: &Graph>, run_args: &RunArgs, @@ -931,7 +942,7 @@ impl Model { Ok(nodes) } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] /// Removes all nodes that are consts with 0 uses fn remove_unused_nodes(nodes: &mut BTreeMap) { // remove all nodes that are consts with 0 uses now @@ -950,7 +961,7 @@ impl Model { }); } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] /// Run tract onnx model on sample data ! pub fn run_onnx_predictions( run_args: &RunArgs, @@ -991,7 +1002,7 @@ impl Model { /// Creates a `Model` from parsed run_args /// # Arguments /// * `params` - A [GraphSettings] struct holding parsed CLI arguments. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub fn from_run_args(run_args: &RunArgs, model: &std::path::Path) -> Result { let mut file = std::fs::File::open(model).map_err(|e| { GraphError::ReadWriteFileError(model.display().to_string(), e.to_string()) @@ -1166,7 +1177,7 @@ impl Model { })?; } // Then number of columns in the circuits - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] thread_safe_region.debug_report(); *constants = thread_safe_region.assigned_constants().clone(); @@ -1197,7 +1208,7 @@ impl Model { for (idx, node) in self.graph.nodes.iter() { debug!("laying out {}: {}", idx, node.as_str(),); // Then number of columns in the circuits - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] region.debug_report(); debug!("input indices: {:?}", node.inputs()); debug!("output scales: {:?}", node.out_scales()); @@ -1451,7 +1462,7 @@ impl Model { trace!("dummy model layout took: {:?}", duration); // Then number of columns in the circuits - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] region.debug_report(); let outputs = outputs @@ -1465,6 +1476,7 @@ impl Model { let res = DummyPassRes { num_rows: region.row(), linear_coord: region.linear_coord(), + max_dynamic_input_len: region.max_dynamic_input_len(), total_const_size: region.total_constants(), lookup_ops: region.used_lookups(), range_checks: region.used_range_checks(), diff --git a/src/graph/node.rs b/src/graph/node.rs index a46654752..34b1fbdb1 100644 --- a/src/graph/node.rs +++ b/src/graph/node.rs @@ -1,9 +1,9 @@ use super::scale_to_multiplier; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use super::utilities::node_output_shapes; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use super::VarScales; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use super::Visibility; use crate::circuit::hybrid::HybridOp; use crate::circuit::lookup::LookupOp; @@ -13,29 +13,29 @@ use crate::circuit::Constant; use crate::circuit::Input; use crate::circuit::Op; use crate::circuit::Unknown; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use crate::graph::errors::GraphError; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use crate::graph::new_op_from_onnx; use crate::tensor::TensorError; use halo2curves::bn256::Fr as Fp; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use log::trace; use serde::Deserialize; use serde::Serialize; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use std::collections::BTreeMap; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use std::fmt; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tabled::Tabled; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tract_onnx::{ self, prelude::{Node as OnnxNode, SymbolValues, TypedFact, TypedOp}, }; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] fn display_vector(v: &Vec) -> String { if !v.is_empty() { format!("{:?}", v) @@ -44,7 +44,7 @@ fn display_vector(v: &Vec) -> String { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] fn display_opkind(v: &SupportedOp) -> String { v.as_string() } @@ -303,7 +303,7 @@ impl SupportedOp { } } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] fn homogenous_rescale( &self, in_scales: Vec, @@ -441,7 +441,7 @@ pub struct Node { pub num_uses: usize, } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] impl Tabled for Node { const LENGTH: usize = 6; @@ -481,7 +481,7 @@ impl Node { /// * `other_nodes` - [BTreeMap] of other previously initialized [Node]s in the computational graph. /// * `public_params` - flag if parameters of model are public /// * `idx` - The node's unique identifier. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] #[allow(clippy::too_many_arguments)] pub fn new( node: OnnxNode>, @@ -623,9 +623,18 @@ impl Node { num_uses, }) } + + /// check if it is a softmax node + pub fn is_softmax(&self) -> bool { + if let SupportedOp::Hybrid(HybridOp::Softmax { .. }) = self.opkind { + true + } else { + false + } + } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] fn rescale_const_with_single_use( constant: &mut Constant, in_scales: Vec, diff --git a/src/graph/postgres.rs b/src/graph/postgres.rs index e5d59e65a..aef4d1f53 100644 --- a/src/graph/postgres.rs +++ b/src/graph/postgres.rs @@ -1,7 +1,7 @@ use log::{debug, error, info}; use std::fmt::Debug; use std::net::IpAddr; -#[cfg(unix)] +#[cfg(all(not(not(feature = "ezkl")), unix))] use std::path::Path; use std::str::FromStr; use std::sync::Arc; @@ -150,7 +150,7 @@ impl Config { /// Adds a Unix socket host to the configuration. /// /// Unlike `host`, this method allows non-UTF8 paths. - #[cfg(unix)] + #[cfg(all(not(not(feature = "ezkl")), unix))] pub fn host_path(&mut self, host: T) -> &mut Config where T: AsRef, diff --git a/src/graph/utilities.rs b/src/graph/utilities.rs index 8bb2eb193..8c77269f7 100644 --- a/src/graph/utilities.rs +++ b/src/graph/utilities.rs @@ -1,12 +1,12 @@ use super::errors::GraphError; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use super::VarScales; use super::{Rescaled, SupportedOp, Visibility}; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use crate::circuit::hybrid::HybridOp; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use crate::circuit::lookup::LookupOp; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use crate::circuit::poly::PolyOp; use crate::circuit::Op; use crate::fieldutils::IntegerRep; @@ -14,13 +14,13 @@ use crate::tensor::{Tensor, TensorError, TensorType}; use halo2curves::bn256::Fr as Fp; use halo2curves::ff::PrimeField; use itertools::Itertools; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use log::{debug, warn}; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use std::sync::Arc; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tract_onnx::prelude::{DatumType, Node as OnnxNode, TypedFact, TypedOp}; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tract_onnx::tract_core::ops::{ array::{ Gather, GatherElements, GatherNd, MultiBroadcastTo, OneHot, ScatterElements, ScatterNd, @@ -33,7 +33,7 @@ use tract_onnx::tract_core::ops::{ nn::{LeakyRelu, Reduce, Softmax}, Downsample, }; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tract_onnx::tract_hir::{ internal::DimLike, ops::array::{Pad, PadMode, TypedConcat}, @@ -90,7 +90,7 @@ pub fn multiplier_to_scale(mult: f64) -> crate::Scale { mult.log2().round() as crate::Scale } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] /// extract padding from a onnx node. pub fn extract_padding( pool_spec: &PoolSpec, @@ -109,7 +109,7 @@ pub fn extract_padding( Ok(padding) } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] /// Extracts the strides from a onnx node. pub fn extract_strides(pool_spec: &PoolSpec) -> Result, GraphError> { Ok(pool_spec @@ -120,7 +120,7 @@ pub fn extract_strides(pool_spec: &PoolSpec) -> Result, GraphError> { } /// Gets the shape of a onnx node's outlets. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub fn node_output_shapes( node: &OnnxNode>, symbol_values: &SymbolValues, @@ -135,9 +135,9 @@ pub fn node_output_shapes( } Ok(shapes) } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tract_onnx::prelude::SymbolValues; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] /// Extracts the raw values from a tensor. pub fn extract_tensor_value( input: Arc, @@ -246,7 +246,7 @@ pub fn extract_tensor_value( Ok(const_value) } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] fn load_op( op: &dyn tract_onnx::prelude::Op, idx: usize, @@ -270,7 +270,7 @@ fn load_op( /// * `param_visibility` - [Visibility] of the node. /// * `node` - the [OnnxNode] to be matched. /// * `inputs` - the node's inputs. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub fn new_op_from_onnx( idx: usize, scales: &VarScales, diff --git a/src/graph/vars.rs b/src/graph/vars.rs index 14d3bf476..595fbaf5f 100644 --- a/src/graph/vars.rs +++ b/src/graph/vars.rs @@ -14,6 +14,7 @@ use pyo3::{ }; use serde::{Deserialize, Serialize}; +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tosubcommand::ToFlags; use self::errors::GraphError; @@ -64,6 +65,7 @@ impl Display for Visibility { } } +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] impl ToFlags for Visibility { fn to_flags(&self) -> Vec { vec![format!("{}", self)] @@ -441,7 +443,7 @@ impl ModelVars { let dynamic_lookup = VarTensor::new_advice(cs, logrows, 1, dynamic_lookup_and_shuffle_size); if dynamic_lookup.num_blocks() > 1 { - panic!("dynamic lookup or shuffle should only have one block"); + warn!("dynamic lookup has {} blocks", dynamic_lookup.num_blocks()); }; advices.push(dynamic_lookup); } diff --git a/src/lib.rs b/src/lib.rs index d5b4e964f..ffa770699 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,12 +30,16 @@ //! /// Error type +// #[cfg_attr(not(feature = "ezkl"), derive(uniffi::Error))] #[derive(thiserror::Error, Debug)] #[allow(missing_docs)] pub enum EZKLError { #[error("[aggregation] {0}")] AggregationError(#[from] pfsys::evm::aggregation_kzg::AggregationError), - #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] + #[cfg(all( + feature = "ezkl", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] #[error("[eth] {0}")] EthError(#[from] eth::EthError), #[error("[graph] {0}")] @@ -54,7 +58,10 @@ pub enum EZKLError { JsonError(#[from] serde_json::Error), #[error("[utf8] {0}")] Utf8Error(#[from] std::str::Utf8Error), - #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] + #[cfg(all( + feature = "ezkl", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] #[error("[reqwest] {0}")] ReqwestError(#[from] reqwest::Error), #[error("[fmt] {0}")] @@ -63,7 +70,10 @@ pub enum EZKLError { Halo2Error(#[from] halo2_proofs::plonk::Error), #[error("[Uncategorized] {0}")] UncategorizedError(String), - #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] + #[cfg(all( + feature = "ezkl", + not(all(target_arch = "wasm32", target_os = "unknown")) + ))] #[error("[execute] {0}")] ExecutionError(#[from] execute::ExecutionError), #[error("[srs] {0}")] @@ -85,7 +95,9 @@ impl From for EZKLError { use std::str::FromStr; use circuit::{table::Range, CheckMode, Tolerance}; +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use clap::Args; +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use fieldutils::IntegerRep; use graph::Visibility; use halo2_proofs::poly::{ @@ -93,52 +105,62 @@ use halo2_proofs::poly::{ }; use halo2curves::bn256::{Bn256, G1Affine}; use serde::{Deserialize, Serialize}; +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tosubcommand::ToFlags; +/// Bindings managment +#[cfg(any( + feature = "ios-bindings", + all(target_arch = "wasm32", target_os = "unknown"), + feature = "python-bindings" +))] +pub mod bindings; /// Methods for configuring tensor operations and assigning values to them in a Halo2 circuit. pub mod circuit; /// CLI commands. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub mod commands; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] // abigen doesn't generate docs for this module #[allow(missing_docs)] /// Utility functions for contracts pub mod eth; /// Command execution /// -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] pub mod execute; /// Utilities for converting from Halo2 Field types to integers (and vice-versa). pub mod fieldutils; /// Methods for loading onnx format models and automatically laying them out in /// a Halo2 circuit. -#[cfg(feature = "onnx")] +#[cfg(any(feature = "onnx", not(feature = "ezkl")))] pub mod graph; /// beautiful logging -#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] +#[cfg(all( + feature = "ezkl", + not(all(target_arch = "wasm32", target_os = "unknown")) +))] pub mod logger; /// Tools for proofs and verification used by cli pub mod pfsys; -/// Python bindings -#[cfg(feature = "python-bindings")] -pub mod python; /// srs sha hashes -#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] +#[cfg(all( + feature = "ezkl", + not(all(target_arch = "wasm32", target_os = "unknown")) +))] pub mod srs_sha; /// An implementation of multi-dimensional tensors. pub mod tensor; -/// wasm prover and verifier -#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] -pub mod wasm; +#[cfg(feature = "ios-bindings")] +uniffi::setup_scaffolding!(); -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use lazy_static::lazy_static; /// The denominator in the fixed point representation used when quantizing inputs pub type Scale = i32; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] // Buf writer capacity lazy_static! { /// The capacity of the buffer used for writing to disk @@ -153,10 +175,10 @@ lazy_static! { } -#[cfg(target_arch = "wasm32")] +#[cfg(any(not(feature = "ezkl"), target_arch = "wasm32"))] const EZKL_KEY_FORMAT: &str = "raw-bytes"; -#[cfg(target_arch = "wasm32")] +#[cfg(any(not(feature = "ezkl"), target_arch = "wasm32"))] const EZKL_BUF_CAPACITY: &usize = &8000; #[derive( @@ -209,6 +231,7 @@ impl std::fmt::Display for Commitments { } } +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] impl ToFlags for Commitments { /// Convert the struct to a subcommand string fn to_flags(&self) -> Vec { @@ -231,57 +254,67 @@ impl From for Commitments { } /// Parameters specific to a proving run -#[derive(Debug, Args, Deserialize, Serialize, Clone, PartialEq, PartialOrd, ToFlags)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] +#[cfg_attr( + all(feature = "ezkl", not(target_arch = "wasm32")), + derive(Args, ToFlags) +)] pub struct RunArgs { /// The tolerance for error on model outputs - #[arg(short = 'T', long, default_value = "0", value_hint = clap::ValueHint::Other)] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(short = 'T', long, default_value = "0", value_hint = clap::ValueHint::Other))] pub tolerance: Tolerance, /// The denominator in the fixed point representation used when quantizing inputs - #[arg(short = 'S', long, default_value = "7", value_hint = clap::ValueHint::Other)] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(short = 'S', long, default_value = "7", value_hint = clap::ValueHint::Other))] pub input_scale: Scale, /// The denominator in the fixed point representation used when quantizing parameters - #[arg(long, default_value = "7", value_hint = clap::ValueHint::Other)] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(long, default_value = "7", value_hint = clap::ValueHint::Other))] pub param_scale: Scale, /// if the scale is ever > scale_rebase_multiplier * input_scale then the scale is rebased to input_scale (this a more advanced parameter, use with caution) - #[arg(long, default_value = "1", value_hint = clap::ValueHint::Other)] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(long, default_value = "1", value_hint = clap::ValueHint::Other))] pub scale_rebase_multiplier: u32, /// The min and max elements in the lookup table input column - #[arg(short = 'B', long, value_parser = parse_key_val::, default_value = "-32768->32768")] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(short = 'B', long, value_parser = parse_key_val::, default_value = "-32768->32768"))] pub lookup_range: Range, /// The log_2 number of rows - #[arg(short = 'K', long, default_value = "17", value_hint = clap::ValueHint::Other)] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(short = 'K', long, default_value = "17", value_hint = clap::ValueHint::Other))] pub logrows: u32, /// The log_2 number of rows - #[arg(short = 'N', long, default_value = "2", value_hint = clap::ValueHint::Other)] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(short = 'N', long, default_value = "2", value_hint = clap::ValueHint::Other))] pub num_inner_cols: usize, /// Hand-written parser for graph variables, eg. batch_size=1 - #[arg(short = 'V', long, value_parser = parse_key_val::, default_value = "batch_size->1", value_delimiter = ',', value_hint = clap::ValueHint::Other)] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(short = 'V', long, value_parser = parse_key_val::, default_value = "batch_size->1", value_delimiter = ',', value_hint = clap::ValueHint::Other))] pub variables: Vec<(String, usize)>, /// Flags whether inputs are public, private, fixed, hashed, polycommit - #[arg(long, default_value = "private", value_hint = clap::ValueHint::Other)] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(long, default_value = "private", value_hint = clap::ValueHint::Other))] pub input_visibility: Visibility, /// Flags whether outputs are public, private, fixed, hashed, polycommit - #[arg(long, default_value = "public", value_hint = clap::ValueHint::Other)] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(long, default_value = "public", value_hint = clap::ValueHint::Other))] pub output_visibility: Visibility, /// Flags whether params are fixed, private, hashed, polycommit - #[arg(long, default_value = "private", value_hint = clap::ValueHint::Other)] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(long, default_value = "private", value_hint = clap::ValueHint::Other))] pub param_visibility: Visibility, - #[arg(long, default_value = "false")] + #[cfg_attr( + all(feature = "ezkl", not(target_arch = "wasm32")), + arg(long, default_value = "false") + )] /// Rebase the scale using lookup table for division instead of using a range check pub div_rebasing: bool, /// Should constants with 0.0 fraction be rebased to scale 0 - #[arg(long, default_value = "false")] + #[cfg_attr( + all(feature = "ezkl", not(target_arch = "wasm32")), + arg(long, default_value = "false") + )] pub rebase_frac_zero_constants: bool, /// check mode (safe, unsafe, etc) - #[arg(long, default_value = "unsafe", value_hint = clap::ValueHint::Other)] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(long, default_value = "unsafe", value_hint = clap::ValueHint::Other))] pub check_mode: CheckMode, /// commitment scheme - #[arg(long, default_value = "kzg", value_hint = clap::ValueHint::Other)] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(long, default_value = "kzg", value_hint = clap::ValueHint::Other))] pub commitment: Option, /// the base used for decompositions - #[arg(long, default_value = "16384", value_hint = clap::ValueHint::Other)] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(long, default_value = "16384", value_hint = clap::ValueHint::Other))] pub decomp_base: usize, - #[arg(long, default_value = "2", value_hint = clap::ValueHint::Other)] + #[cfg_attr(all(feature = "ezkl", not(target_arch = "wasm32")), arg(long, default_value = "2", value_hint = clap::ValueHint::Other))] /// the number of legs used for decompositions pub decomp_legs: usize, } @@ -354,6 +387,7 @@ impl RunArgs { } /// Parse a single key-value pair +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] fn parse_key_val( s: &str, ) -> Result<(T, U), Box> diff --git a/src/pfsys/evm/aggregation_kzg.rs b/src/pfsys/evm/aggregation_kzg.rs index addd4b390..8728a81f9 100644 --- a/src/pfsys/evm/aggregation_kzg.rs +++ b/src/pfsys/evm/aggregation_kzg.rs @@ -1,7 +1,7 @@ -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use crate::graph::CircuitSize; use crate::pfsys::{Snark, SnarkWitness}; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use colored_json::ToColoredJson; use halo2_proofs::circuit::AssignedCell; use halo2_proofs::plonk::{self}; @@ -20,7 +20,7 @@ use halo2_wrong_ecc::{ use halo2curves::bn256::{Bn256, Fq, Fr, G1Affine}; use halo2curves::ff::PrimeField; use itertools::Itertools; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use log::debug; use log::trace; use rand::rngs::OsRng; @@ -200,7 +200,7 @@ impl AggregationConfig { let range_config = RangeChip::::configure(meta, &main_gate_config, composition_bits, overflow_bits); - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] { let circuit_size = CircuitSize::from_cs(meta, 23); diff --git a/src/pfsys/mod.rs b/src/pfsys/mod.rs index a83c11138..9acee1a0a 100644 --- a/src/pfsys/mod.rs +++ b/src/pfsys/mod.rs @@ -13,6 +13,7 @@ use crate::circuit::CheckMode; use crate::graph::GraphWitness; use crate::pfsys::evm::aggregation_kzg::PoseidonTranscript; use crate::{Commitments, EZKL_BUF_CAPACITY, EZKL_KEY_FORMAT}; +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use clap::ValueEnum; use halo2_proofs::circuit::Value; use halo2_proofs::plonk::{ @@ -42,6 +43,7 @@ use std::io::{self, BufReader, BufWriter, Cursor, Write}; use std::ops::Deref; use std::path::PathBuf; use thiserror::Error as thisError; +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] use tosubcommand::ToFlags; use halo2curves::bn256::{Bn256, Fr, G1Affine}; @@ -56,8 +58,10 @@ fn serde_format_from_str(s: &str) -> halo2_proofs::SerdeFormat { } #[allow(missing_docs)] -#[derive( - ValueEnum, Copy, Clone, Default, Debug, PartialEq, Eq, Deserialize, Serialize, PartialOrd, +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Deserialize, Serialize, PartialOrd)] +#[cfg_attr( + all(feature = "ezkl", not(target_arch = "wasm32")), + derive(ValueEnum) )] pub enum ProofType { #[default] @@ -77,7 +81,7 @@ impl std::fmt::Display for ProofType { ) } } - +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] impl ToFlags for ProofType { fn to_flags(&self) -> Vec { vec![format!("{}", self)] @@ -129,17 +133,38 @@ impl<'source> pyo3::FromPyObject<'source> for ProofType { } #[allow(missing_docs)] -#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[cfg_attr( + all(feature = "ezkl", not(target_arch = "wasm32")), + derive(ValueEnum) +)] pub enum StrategyType { Single, Accum, } impl std::fmt::Display for StrategyType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.to_possible_value() - .expect("no values are skipped") - .get_name() - .fmt(f) + // When the `ezkl` feature is disabled or we're targeting `wasm32`, use basic string representation. + #[cfg(any(not(feature = "ezkl"), target_arch = "wasm32"))] + { + write!( + f, + "{}", + match self { + StrategyType::Single => "single", + StrategyType::Accum => "accum", + } + ) + } + + // When the `ezkl` feature is enabled and we're not targeting `wasm32`, use `to_possible_value`. + #[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] + { + self.to_possible_value() + .expect("no values are skipped") + .get_name() + .fmt(f) + } } } #[cfg(feature = "python-bindings")] @@ -177,8 +202,10 @@ pub enum PfSysError { } #[allow(missing_docs)] -#[derive( - ValueEnum, Default, Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, PartialOrd, +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, PartialOrd)] +#[cfg_attr( + all(feature = "ezkl", not(target_arch = "wasm32")), + derive(ValueEnum) )] pub enum TranscriptType { Poseidon, @@ -198,7 +225,7 @@ impl std::fmt::Display for TranscriptType { ) } } - +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] impl ToFlags for TranscriptType { fn to_flags(&self) -> Vec { vec![format!("{}", self)] @@ -862,7 +889,7 @@ pub fn save_params( //////////////////////// #[cfg(test)] -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] mod tests { use super::*; diff --git a/src/tensor/val.rs b/src/tensor/val.rs index 579d81f3e..13ac0a792 100644 --- a/src/tensor/val.rs +++ b/src/tensor/val.rs @@ -541,7 +541,7 @@ impl ValTensor { let mut is_empty = true; x.map(|_| is_empty = false); if is_empty { - return Ok::<_, TensorError>(vec![Value::::unknown(); n + 1]); + Ok::<_, TensorError>(vec![Value::::unknown(); n + 1]) } else { let mut res = vec![Value::unknown(); n + 1]; let mut int_rep = 0; diff --git a/src/tensor/var.rs b/src/tensor/var.rs index 677c855c6..2e53ce65b 100644 --- a/src/tensor/var.rs +++ b/src/tensor/var.rs @@ -396,6 +396,53 @@ impl VarTensor { Ok(res) } + /// Helper function to get the remaining size of the column + pub fn get_column_flush( + &self, + offset: usize, + values: &ValTensor, + ) -> Result { + if values.len() > self.col_size() { + error!("Values are too large for the column"); + return Err(halo2_proofs::plonk::Error::Synthesis); + } + + // this can only be called on columns that have a single inner column + if self.num_inner_cols() != 1 { + error!("This function can only be called on columns with a single inner column"); + return Err(halo2_proofs::plonk::Error::Synthesis); + } + + // check if the values fit in the remaining space of the column + let current_cartesian = self.cartesian_coord(offset); + let final_cartesian = self.cartesian_coord(offset + values.len()); + + let mut flush_len = 0; + if current_cartesian.0 != final_cartesian.0 { + debug!("Values overflow the column, flushing to next column"); + // diff is the number of values that overflow the column + flush_len += self.col_size() - current_cartesian.2; + } + + Ok(flush_len) + } + + /// Assigns [ValTensor] to the columns of the inner tensor. Whereby the values are assigned to a single column, without overflowing. + /// So for instance if we are assigning 10 values and we are at index 18 of the column, and the columns are of length 20, we skip the last 2 values of current column and start from the beginning of the next column. + pub fn assign_exact_column( + &self, + region: &mut Region, + offset: usize, + values: &ValTensor, + constants: &mut ConstantsMap, + ) -> Result<(ValTensor, usize), halo2_proofs::plonk::Error> { + let flush_len = self.get_column_flush(offset, values)?; + + let assigned_vals = self.assign(region, offset + flush_len, values, constants)?; + + Ok((assigned_vals, flush_len)) + } + /// Assigns specific values (`ValTensor`) to the columns of the inner tensor but allows for column wrapping for accumulated operations. /// Duplication occurs by copying the last cell of the column to the first cell next column and creating a copy constraint between the two. pub fn dummy_assign_with_duplication< diff --git a/src/wasm.rs b/src/wasm.rs deleted file mode 100644 index d3cff44f7..000000000 --- a/src/wasm.rs +++ /dev/null @@ -1,793 +0,0 @@ -use crate::{ - circuit::{ - modules::{ - polycommit::PolyCommitChip, - poseidon::{ - spec::{PoseidonSpec, POSEIDON_RATE, POSEIDON_WIDTH}, - PoseidonChip, - }, - Module, - }, - region::RegionSettings, - }, - fieldutils::{felt_to_integer_rep, integer_rep_to_felt}, - graph::{ - modules::POSEIDON_LEN_GRAPH, quantize_float, scale_to_multiplier, GraphCircuit, - GraphSettings, - }, - pfsys::{ - create_proof_circuit, - evm::aggregation_kzg::{AggregationCircuit, PoseidonTranscript}, - verify_proof_circuit, TranscriptType, - }, - tensor::TensorType, - CheckMode, Commitments, -}; -use console_error_panic_hook; -use halo2_proofs::{ - plonk::*, - poly::{ - commitment::{CommitmentScheme, ParamsProver}, - ipa::{ - commitment::{IPACommitmentScheme, ParamsIPA}, - multiopen::{ProverIPA, VerifierIPA}, - strategy::SingleStrategy as IPASingleStrategy, - }, - kzg::{ - commitment::{KZGCommitmentScheme, ParamsKZG}, - multiopen::{ProverSHPLONK, VerifierSHPLONK}, - strategy::SingleStrategy as KZGSingleStrategy, - }, - VerificationStrategy, - }, -}; -use halo2_solidity_verifier::encode_calldata; -use halo2curves::{ - bn256::{Bn256, Fr, G1Affine}, - ff::{FromUniformBytes, PrimeField}, -}; -use snark_verifier::{loader::native::NativeLoader, system::halo2::transcript::evm::EvmTranscript}; -use std::str::FromStr; -use wasm_bindgen::prelude::*; -use wasm_bindgen_console_logger::DEFAULT_LOGGER; - -#[cfg(feature = "web")] -pub use wasm_bindgen_rayon::init_thread_pool; - -#[wasm_bindgen] -/// Initialize logger for wasm -pub fn init_logger() { - log::set_logger(&DEFAULT_LOGGER).unwrap(); -} - -#[wasm_bindgen] -/// Initialize panic hook for wasm -pub fn init_panic_hook() { - console_error_panic_hook::set_once(); -} - -/// Wrapper around the halo2 encode call data method -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn encodeVerifierCalldata( - proof: wasm_bindgen::Clamped>, - vk_address: Option>, -) -> Result, JsError> { - let snark: crate::pfsys::Snark = serde_json::from_slice(&proof[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize proof: {}", e)))?; - - let vk_address: Option<[u8; 20]> = if let Some(vk_address) = vk_address { - let array: [u8; 20] = serde_json::from_slice(&vk_address[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize vk address: {}", e)))?; - Some(array) - } else { - None - }; - - let flattened_instances = snark.instances.into_iter().flatten(); - - let encoded = encode_calldata( - vk_address, - &snark.proof, - &flattened_instances.collect::>(), - ); - - Ok(encoded) -} - -/// Converts a hex string to a byte array -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn feltToBigEndian(array: wasm_bindgen::Clamped>) -> Result { - let felt: Fr = serde_json::from_slice(&array[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize field element: {}", e)))?; - Ok(format!("{:?}", felt)) -} - -/// Converts a felt to a little endian string -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn feltToLittleEndian(array: wasm_bindgen::Clamped>) -> Result { - let felt: Fr = serde_json::from_slice(&array[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize field element: {}", e)))?; - let repr = serde_json::to_string(&felt).unwrap(); - let b: String = serde_json::from_str(&repr).unwrap(); - Ok(b) -} - -/// Converts a hex string to a byte array -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn feltToInt( - array: wasm_bindgen::Clamped>, -) -> Result>, JsError> { - let felt: Fr = serde_json::from_slice(&array[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize field element: {}", e)))?; - Ok(wasm_bindgen::Clamped( - serde_json::to_vec(&felt_to_integer_rep(felt)) - .map_err(|e| JsError::new(&format!("Failed to serialize integer: {}", e)))?, - )) -} - -/// Converts felts to a floating point element -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn feltToFloat( - array: wasm_bindgen::Clamped>, - scale: crate::Scale, -) -> Result { - let felt: Fr = serde_json::from_slice(&array[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize field element: {}", e)))?; - let int_rep = felt_to_integer_rep(felt); - let multiplier = scale_to_multiplier(scale); - Ok(int_rep as f64 / multiplier) -} - -/// Converts a floating point number to a hex string representing a fixed point field element -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn floatToFelt( - input: f64, - scale: crate::Scale, -) -> Result>, JsError> { - let int_rep = - quantize_float(&input, 0.0, scale).map_err(|e| JsError::new(&format!("{}", e)))?; - let felt = integer_rep_to_felt(int_rep); - let vec = crate::pfsys::field_to_string::(&felt); - Ok(wasm_bindgen::Clamped(serde_json::to_vec(&vec).map_err( - |e| JsError::new(&format!("Failed to serialize a float to felt{}", e)), - )?)) -} - -/// Generate a kzg commitment. -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn kzgCommit( - message: wasm_bindgen::Clamped>, - vk: wasm_bindgen::Clamped>, - settings: wasm_bindgen::Clamped>, - params_ser: wasm_bindgen::Clamped>, -) -> Result>, JsError> { - let message: Vec = serde_json::from_slice(&message[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize message: {}", e)))?; - - let mut reader = std::io::BufReader::new(¶ms_ser[..]); - let params: ParamsKZG = - halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader) - .map_err(|e| JsError::new(&format!("Failed to deserialize params: {}", e)))?; - - let mut reader = std::io::BufReader::new(&vk[..]); - let circuit_settings: GraphSettings = serde_json::from_slice(&settings[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize settings: {}", e)))?; - let vk = VerifyingKey::::read::<_, GraphCircuit>( - &mut reader, - halo2_proofs::SerdeFormat::RawBytes, - circuit_settings, - ) - .map_err(|e| JsError::new(&format!("Failed to deserialize vk: {}", e)))?; - - let output = PolyCommitChip::commit::>( - message, - (vk.cs().blinding_factors() + 1) as u32, - ¶ms, - ); - - Ok(wasm_bindgen::Clamped( - serde_json::to_vec(&output).map_err(|e| JsError::new(&format!("{}", e)))?, - )) -} - -/// Converts a buffer to vector of 4 u64s representing a fixed point field element -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn bufferToVecOfFelt( - buffer: wasm_bindgen::Clamped>, -) -> Result>, JsError> { - // Convert the buffer to a slice - let buffer: &[u8] = &buffer; - - // Divide the buffer into chunks of 64 bytes - let chunks = buffer.chunks_exact(16); - - // Get the remainder - let remainder = chunks.remainder(); - - // Add 0s to the remainder to make it 64 bytes - let mut remainder = remainder.to_vec(); - - // Collect chunks into a Vec<[u8; 16]>. - let chunks: Result, JsError> = chunks - .map(|slice| { - let array: [u8; 16] = slice - .try_into() - .map_err(|_| JsError::new("failed to slice input chunks"))?; - Ok(array) - }) - .collect(); - - let mut chunks = chunks?; - - if remainder.len() != 0 { - remainder.resize(16, 0); - // Convert the Vec to [u8; 16] - let remainder_array: [u8; 16] = remainder - .try_into() - .map_err(|_| JsError::new("failed to slice remainder"))?; - // append the remainder to the chunks - chunks.push(remainder_array); - } - - // Convert each chunk to a field element - let field_elements: Vec = chunks - .iter() - .map(|x| PrimeField::from_u128(u8_array_to_u128_le(*x))) - .collect(); - - Ok(wasm_bindgen::Clamped( - serde_json::to_vec(&field_elements) - .map_err(|e| JsError::new(&format!("Failed to serialize field elements: {}", e)))?, - )) -} - -/// Generate a poseidon hash in browser. Input message -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn poseidonHash( - message: wasm_bindgen::Clamped>, -) -> Result>, JsError> { - let message: Vec = serde_json::from_slice(&message[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize message: {}", e)))?; - - let output = - PoseidonChip::::run( - message.clone(), - ) - .map_err(|e| JsError::new(&format!("{}", e)))?; - - Ok(wasm_bindgen::Clamped(serde_json::to_vec(&output).map_err( - |e| JsError::new(&format!("Failed to serialize poseidon hash output: {}", e)), - )?)) -} - -/// Generate a witness file from input.json, compiled model and a settings.json file. -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn genWitness( - compiled_circuit: wasm_bindgen::Clamped>, - input: wasm_bindgen::Clamped>, -) -> Result, JsError> { - let mut circuit: crate::graph::GraphCircuit = bincode::deserialize(&compiled_circuit[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize compiled model: {}", e)))?; - let input: crate::graph::input::GraphData = serde_json::from_slice(&input[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize input: {}", e)))?; - - let mut input = circuit - .load_graph_input(&input) - .map_err(|e| JsError::new(&format!("{}", e)))?; - - let witness = circuit - .forward::>( - &mut input, - None, - None, - RegionSettings::all_true( - circuit.settings().run_args.decomp_base, - circuit.settings().run_args.decomp_legs, - ), - ) - .map_err(|e| JsError::new(&format!("{}", e)))?; - - serde_json::to_vec(&witness) - .map_err(|e| JsError::new(&format!("Failed to serialize witness: {}", e))) -} - -/// Generate verifying key in browser -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn genVk( - compiled_circuit: wasm_bindgen::Clamped>, - params_ser: wasm_bindgen::Clamped>, - compress_selectors: bool, -) -> Result, JsError> { - // Read in kzg params - let mut reader = std::io::BufReader::new(¶ms_ser[..]); - let params: ParamsKZG = - halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader) - .map_err(|e| JsError::new(&format!("Failed to deserialize params: {}", e)))?; - // Read in compiled circuit - let circuit: crate::graph::GraphCircuit = bincode::deserialize(&compiled_circuit[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize compiled model: {}", e)))?; - - // Create verifying key - let vk = create_vk_wasm::, Fr, GraphCircuit>( - &circuit, - ¶ms, - compress_selectors, - ) - .map_err(Box::::from) - .map_err(|e| JsError::new(&format!("Failed to create verifying key: {}", e)))?; - - let mut serialized_vk = Vec::new(); - vk.write(&mut serialized_vk, halo2_proofs::SerdeFormat::RawBytes) - .map_err(|e| JsError::new(&format!("Failed to serialize vk: {}", e)))?; - - Ok(serialized_vk) -} - -/// Generate proving key in browser -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn genPk( - vk: wasm_bindgen::Clamped>, - compiled_circuit: wasm_bindgen::Clamped>, - params_ser: wasm_bindgen::Clamped>, -) -> Result, JsError> { - // Read in kzg params - let mut reader = std::io::BufReader::new(¶ms_ser[..]); - let params: ParamsKZG = - halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader) - .map_err(|e| JsError::new(&format!("Failed to deserialize params: {}", e)))?; - // Read in compiled circuit - let circuit: crate::graph::GraphCircuit = bincode::deserialize(&compiled_circuit[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize compiled model: {}", e)))?; - - // Read in verifying key - let mut reader = std::io::BufReader::new(&vk[..]); - let vk = VerifyingKey::::read::<_, GraphCircuit>( - &mut reader, - halo2_proofs::SerdeFormat::RawBytes, - circuit.settings().clone(), - ) - .map_err(|e| JsError::new(&format!("Failed to deserialize verifying key: {}", e)))?; - // Create proving key - let pk = create_pk_wasm::, Fr, GraphCircuit>(vk, &circuit, ¶ms) - .map_err(Box::::from) - .map_err(|e| JsError::new(&format!("Failed to create proving key: {}", e)))?; - - let mut serialized_pk = Vec::new(); - pk.write(&mut serialized_pk, halo2_proofs::SerdeFormat::RawBytes) - .map_err(|e| JsError::new(&format!("Failed to serialize pk: {}", e)))?; - - Ok(serialized_pk) -} - -/// Verify proof in browser using wasm -#[wasm_bindgen] -pub fn verify( - proof_js: wasm_bindgen::Clamped>, - vk: wasm_bindgen::Clamped>, - settings: wasm_bindgen::Clamped>, - srs: wasm_bindgen::Clamped>, -) -> Result { - let circuit_settings: GraphSettings = serde_json::from_slice(&settings[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize settings: {}", e)))?; - - let proof: crate::pfsys::Snark = serde_json::from_slice(&proof_js[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize proof: {}", e)))?; - - let mut reader = std::io::BufReader::new(&vk[..]); - let vk = VerifyingKey::::read::<_, GraphCircuit>( - &mut reader, - halo2_proofs::SerdeFormat::RawBytes, - circuit_settings.clone(), - ) - .map_err(|e| JsError::new(&format!("Failed to deserialize vk: {}", e)))?; - - let orig_n = 1 << circuit_settings.run_args.logrows; - - let commitment = circuit_settings.run_args.commitment.into(); - - let mut reader = std::io::BufReader::new(&srs[..]); - let result = match commitment { - Commitments::KZG => { - let params: ParamsKZG = - halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader) - .map_err(|e| JsError::new(&format!("Failed to deserialize params: {}", e)))?; - let strategy = KZGSingleStrategy::new(params.verifier_params()); - match proof.transcript_type { - TranscriptType::EVM => verify_proof_circuit::< - VerifierSHPLONK<'_, Bn256>, - KZGCommitmentScheme, - KZGSingleStrategy<_>, - _, - EvmTranscript, - >(&proof, ¶ms, &vk, strategy, orig_n), - - TranscriptType::Poseidon => { - verify_proof_circuit::< - VerifierSHPLONK<'_, Bn256>, - KZGCommitmentScheme, - KZGSingleStrategy<_>, - _, - PoseidonTranscript, - >(&proof, ¶ms, &vk, strategy, orig_n) - } - } - } - Commitments::IPA => { - let params: ParamsIPA<_> = - halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader) - .map_err(|e| JsError::new(&format!("Failed to deserialize params: {}", e)))?; - let strategy = IPASingleStrategy::new(params.verifier_params()); - match proof.transcript_type { - TranscriptType::EVM => verify_proof_circuit::< - VerifierIPA<_>, - IPACommitmentScheme, - IPASingleStrategy<_>, - _, - EvmTranscript, - >(&proof, ¶ms, &vk, strategy, orig_n), - TranscriptType::Poseidon => { - verify_proof_circuit::< - VerifierIPA<_>, - IPACommitmentScheme, - IPASingleStrategy<_>, - _, - PoseidonTranscript, - >(&proof, ¶ms, &vk, strategy, orig_n) - } - } - } - }; - - match result { - Ok(_) => Ok(true), - Err(e) => Err(JsError::new(&format!("{}", e))), - } -} - -#[wasm_bindgen] -#[allow(non_snake_case)] -/// Verify aggregate proof in browser using wasm -pub fn verifyAggr( - proof_js: wasm_bindgen::Clamped>, - vk: wasm_bindgen::Clamped>, - logrows: u64, - srs: wasm_bindgen::Clamped>, - commitment: &str, -) -> Result { - let proof: crate::pfsys::Snark = serde_json::from_slice(&proof_js[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize proof: {}", e)))?; - - let mut reader = std::io::BufReader::new(&vk[..]); - let vk = VerifyingKey::::read::<_, AggregationCircuit>( - &mut reader, - halo2_proofs::SerdeFormat::RawBytes, - (), - ) - .map_err(|e| JsError::new(&format!("Failed to deserialize vk: {}", e)))?; - - let commit = Commitments::from_str(commitment).map_err(|e| JsError::new(&format!("{}", e)))?; - - let orig_n = 1 << logrows; - - let mut reader = std::io::BufReader::new(&srs[..]); - let result = match commit { - Commitments::KZG => { - let params: ParamsKZG = - halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader) - .map_err(|e| JsError::new(&format!("Failed to deserialize params: {}", e)))?; - let strategy = KZGSingleStrategy::new(params.verifier_params()); - match proof.transcript_type { - TranscriptType::EVM => verify_proof_circuit::< - VerifierSHPLONK<'_, Bn256>, - KZGCommitmentScheme, - KZGSingleStrategy<_>, - _, - EvmTranscript, - >(&proof, ¶ms, &vk, strategy, orig_n), - - TranscriptType::Poseidon => { - verify_proof_circuit::< - VerifierSHPLONK<'_, Bn256>, - KZGCommitmentScheme, - KZGSingleStrategy<_>, - _, - PoseidonTranscript, - >(&proof, ¶ms, &vk, strategy, orig_n) - } - } - } - Commitments::IPA => { - let params: ParamsIPA<_> = - halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader) - .map_err(|e| JsError::new(&format!("Failed to deserialize params: {}", e)))?; - let strategy = IPASingleStrategy::new(params.verifier_params()); - match proof.transcript_type { - TranscriptType::EVM => verify_proof_circuit::< - VerifierIPA<_>, - IPACommitmentScheme, - IPASingleStrategy<_>, - _, - EvmTranscript, - >(&proof, ¶ms, &vk, strategy, orig_n), - TranscriptType::Poseidon => { - verify_proof_circuit::< - VerifierIPA<_>, - IPACommitmentScheme, - IPASingleStrategy<_>, - _, - PoseidonTranscript, - >(&proof, ¶ms, &vk, strategy, orig_n) - } - } - } - }; - - match result { - Ok(_) => Ok(true), - Err(e) => Err(JsError::new(&format!("{}", e))), - } -} - -/// Prove in browser using wasm -#[wasm_bindgen] -pub fn prove( - witness: wasm_bindgen::Clamped>, - pk: wasm_bindgen::Clamped>, - compiled_circuit: wasm_bindgen::Clamped>, - srs: wasm_bindgen::Clamped>, -) -> Result, JsError> { - #[cfg(feature = "det-prove")] - log::set_max_level(log::LevelFilter::Debug); - #[cfg(not(feature = "det-prove"))] - log::set_max_level(log::LevelFilter::Info); - - // read in circuit - let mut circuit: crate::graph::GraphCircuit = bincode::deserialize(&compiled_circuit[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize circuit: {}", e)))?; - - // read in model input - let data: crate::graph::GraphWitness = serde_json::from_slice(&witness[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize witness: {}", e)))?; - - // read in proving key - let mut reader = std::io::BufReader::new(&pk[..]); - let pk = ProvingKey::::read::<_, GraphCircuit>( - &mut reader, - halo2_proofs::SerdeFormat::RawBytes, - circuit.settings().clone(), - ) - .map_err(|e| JsError::new(&format!("Failed to deserialize proving key: {}", e)))?; - - // prep public inputs - circuit - .load_graph_witness(&data) - .map_err(|e| JsError::new(&format!("{}", e)))?; - let public_inputs = circuit - .prepare_public_inputs(&data) - .map_err(|e| JsError::new(&format!("{}", e)))?; - let proof_split_commits: Option = data.into(); - - // read in kzg params - let mut reader = std::io::BufReader::new(&srs[..]); - let commitment = circuit.settings().run_args.commitment.into(); - // creates and verifies the proof - let proof = match commitment { - Commitments::KZG => { - let params: ParamsKZG = - halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader) - .map_err(|e| JsError::new(&format!("Failed to deserialize srs: {}", e)))?; - - create_proof_circuit::< - KZGCommitmentScheme, - _, - ProverSHPLONK<_>, - VerifierSHPLONK<_>, - KZGSingleStrategy<_>, - _, - EvmTranscript<_, _, _, _>, - EvmTranscript<_, _, _, _>, - >( - circuit, - vec![public_inputs], - ¶ms, - &pk, - CheckMode::UNSAFE, - crate::Commitments::KZG, - TranscriptType::EVM, - proof_split_commits, - None, - ) - } - Commitments::IPA => { - let params: ParamsIPA<_> = - halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader) - .map_err(|e| JsError::new(&format!("Failed to deserialize srs: {}", e)))?; - - create_proof_circuit::< - IPACommitmentScheme, - _, - ProverIPA<_>, - VerifierIPA<_>, - IPASingleStrategy<_>, - _, - EvmTranscript<_, _, _, _>, - EvmTranscript<_, _, _, _>, - >( - circuit, - vec![public_inputs], - ¶ms, - &pk, - CheckMode::UNSAFE, - crate::Commitments::IPA, - TranscriptType::EVM, - proof_split_commits, - None, - ) - } - } - .map_err(|e| JsError::new(&format!("{}", e)))?; - - Ok(serde_json::to_string(&proof) - .map_err(|e| JsError::new(&format!("{}", e)))? - .into_bytes()) -} - -// VALIDATION FUNCTIONS - -/// Witness file validation -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn witnessValidation(witness: wasm_bindgen::Clamped>) -> Result { - let _: crate::graph::GraphWitness = serde_json::from_slice(&witness[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize witness: {}", e)))?; - - Ok(true) -} -/// Compiled circuit validation -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn compiledCircuitValidation( - compiled_circuit: wasm_bindgen::Clamped>, -) -> Result { - let _: crate::graph::GraphCircuit = bincode::deserialize(&compiled_circuit[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize compiled circuit: {}", e)))?; - - Ok(true) -} -/// Input file validation -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn inputValidation(input: wasm_bindgen::Clamped>) -> Result { - let _: crate::graph::input::GraphData = serde_json::from_slice(&input[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize input: {}", e)))?; - - Ok(true) -} -/// Proof file validation -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn proofValidation(proof: wasm_bindgen::Clamped>) -> Result { - let _: crate::pfsys::Snark = serde_json::from_slice(&proof[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize proof: {}", e)))?; - - Ok(true) -} -/// Vk file validation -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn vkValidation( - vk: wasm_bindgen::Clamped>, - settings: wasm_bindgen::Clamped>, -) -> Result { - let circuit_settings: GraphSettings = serde_json::from_slice(&settings[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize settings: {}", e)))?; - let mut reader = std::io::BufReader::new(&vk[..]); - let _ = VerifyingKey::::read::<_, GraphCircuit>( - &mut reader, - halo2_proofs::SerdeFormat::RawBytes, - circuit_settings, - ) - .map_err(|e| JsError::new(&format!("Failed to deserialize vk: {}", e)))?; - - Ok(true) -} -/// Pk file validation -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn pkValidation( - pk: wasm_bindgen::Clamped>, - settings: wasm_bindgen::Clamped>, -) -> Result { - let circuit_settings: GraphSettings = serde_json::from_slice(&settings[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize settings: {}", e)))?; - let mut reader = std::io::BufReader::new(&pk[..]); - let _ = ProvingKey::::read::<_, GraphCircuit>( - &mut reader, - halo2_proofs::SerdeFormat::RawBytes, - circuit_settings, - ) - .map_err(|e| JsError::new(&format!("Failed to deserialize proving key: {}", e)))?; - - Ok(true) -} -/// Settings file validation -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn settingsValidation(settings: wasm_bindgen::Clamped>) -> Result { - let _: GraphSettings = serde_json::from_slice(&settings[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize settings: {}", e)))?; - - Ok(true) -} -/// Srs file validation -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn srsValidation(srs: wasm_bindgen::Clamped>) -> Result { - let mut reader = std::io::BufReader::new(&srs[..]); - let _: ParamsKZG = - halo2_proofs::poly::commitment::Params::<'_, G1Affine>::read(&mut reader) - .map_err(|e| JsError::new(&format!("Failed to deserialize params: {}", e)))?; - - Ok(true) -} - -// HELPER FUNCTIONS - -/// Creates a [ProvingKey] for a [GraphCircuit] (`circuit`) with specific [CommitmentScheme] parameters (`params`) for the WASM target -#[cfg(target_arch = "wasm32")] -pub fn create_vk_wasm>( - circuit: &C, - params: &'_ Scheme::ParamsProver, - compress_selectors: bool, -) -> Result, halo2_proofs::plonk::Error> -where - C: Circuit, - ::Scalar: FromUniformBytes<64>, -{ - // Real proof - let empty_circuit = >::without_witnesses(circuit); - - // Initialize the verifying key - let vk = keygen_vk_custom(params, &empty_circuit, compress_selectors)?; - Ok(vk) -} -/// Creates a [ProvingKey] from a [VerifyingKey] for a [GraphCircuit] (`circuit`) with specific [CommitmentScheme] parameters (`params`) for the WASM target -#[cfg(target_arch = "wasm32")] -pub fn create_pk_wasm>( - vk: VerifyingKey, - circuit: &C, - params: &'_ Scheme::ParamsProver, -) -> Result, halo2_proofs::plonk::Error> -where - C: Circuit, - ::Scalar: FromUniformBytes<64>, -{ - // Real proof - let empty_circuit = >::without_witnesses(circuit); - - // Initialize the proving key - let pk = keygen_pk(params, vk, &empty_circuit)?; - Ok(pk) -} - -/// -pub fn u8_array_to_u128_le(arr: [u8; 16]) -> u128 { - let mut n: u128 = 0; - for &b in arr.iter().rev() { - n <<= 8; - n |= b as u128; - } - n -} diff --git a/tests/wasm/calibration.json b/tests/assets/calibration.json similarity index 100% rename from tests/wasm/calibration.json rename to tests/assets/calibration.json diff --git a/tests/wasm/input.json b/tests/assets/input.json similarity index 100% rename from tests/wasm/input.json rename to tests/assets/input.json diff --git a/tests/wasm/kzg b/tests/assets/kzg similarity index 100% rename from tests/wasm/kzg rename to tests/assets/kzg diff --git a/tests/wasm/kzg1.srs b/tests/assets/kzg1.srs similarity index 100% rename from tests/wasm/kzg1.srs rename to tests/assets/kzg1.srs diff --git a/tests/wasm/model.compiled b/tests/assets/model.compiled similarity index 81% rename from tests/wasm/model.compiled rename to tests/assets/model.compiled index cfc1cf862ca51db9a13342a2e13788c12f779cb0..2794a23dec0dc4b8a6341202c95b02d1d1a85dd4 100644 GIT binary patch delta 31 mcmeC=o5;6e66?eUj>&VGI3~vc@m^+u$+j#Un|HDLGXempnhDDQ delta 43 ycmbQp*U7hG66@r-OdOMAfOs#nz+_t%j)@*Tljk#wOy*^ln7o#WV>1&QKO+Dpn+xs$ diff --git a/tests/wasm/network.onnx b/tests/assets/network.onnx similarity index 100% rename from tests/wasm/network.onnx rename to tests/assets/network.onnx diff --git a/tests/wasm/pk.key b/tests/assets/pk.key similarity index 100% rename from tests/wasm/pk.key rename to tests/assets/pk.key diff --git a/tests/assets/proof.json b/tests/assets/proof.json new file mode 100644 index 000000000..6db6c2445 --- /dev/null +++ b/tests/assets/proof.json @@ -0,0 +1 @@ +{"protocol":null,"instances":[["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000"]],"proof":[7,102,87,190,153,229,59,6,250,221,10,63,210,51,26,207,46,194,145,97,237,1,245,42,248,173,49,189,21,0,216,125,34,9,199,125,171,108,12,101,89,75,31,133,179,147,239,237,218,207,81,198,82,129,144,247,68,204,166,44,217,66,221,35,17,33,29,160,129,121,229,44,82,145,52,107,213,158,203,98,129,56,97,204,34,80,245,3,34,5,252,31,167,58,56,2,46,210,50,211,53,35,250,143,122,31,14,84,171,83,208,117,236,3,97,98,59,102,89,161,167,18,194,233,26,45,58,38,42,83,206,156,150,9,69,180,201,190,62,248,116,104,6,168,207,112,166,244,168,160,60,83,250,74,40,63,19,85,44,27,30,165,252,35,212,69,204,145,255,18,204,249,66,245,192,116,111,38,175,106,252,101,33,182,129,200,188,246,224,111,18,116,29,136,21,25,83,69,48,217,153,96,162,102,210,156,188,8,183,175,181,194,45,244,231,154,102,19,18,152,102,167,34,175,2,92,46,210,136,40,78,202,251,77,209,104,254,116,21,37,168,44,135,119,129,216,252,171,70,50,243,103,194,115,18,125,22,42,29,183,17,110,66,169,83,226,46,203,216,91,21,148,167,252,33,157,201,156,20,233,149,244,87,184,248,255,14,144,44,49,13,249,77,141,69,211,180,110,63,67,91,75,235,138,88,93,74,113,253,66,111,192,243,132,182,86,184,22,135,179,2,112,0,135,178,191,162,208,159,58,198,75,119,147,118,211,123,5,216,206,242,48,38,98,156,215,28,70,97,33,224,149,28,145,250,188,1,52,183,235,236,117,6,140,80,29,215,31,254,156,17,10,29,184,124,55,228,139,63,193,3,207,68,16,9,207,169,136,53,34,166,195,225,22,236,10,200,246,61,4,236,31,71,161,12,17,126,135,26,197,8,101,142,82,231,57,44,76,64,86,37,222,181,85,166,186,2,138,108,70,116,45,60,86,220,44,23,240,162,185,141,196,147,50,163,42,197,7,29,215,253,51,30,13,160,202,14,34,89,185,112,183,170,9,43,64,87,86,87,223,238,221,185,181,181,105,132,245,167,217,24,206,84,81,109,69,112,31,14,90,22,99,59,222,83,190,241,72,86,103,39,90,98,201,42,29,5,149,233,120,234,57,42,29,23,75,127,138,84,57,241,193,71,212,213,184,25,163,131,79,55,28,182,52,178,65,193,214,211,84,24,52,155,247,21,200,242,170,146,244,46,164,38,166,5,201,19,214,103,89,20,8,5,173,157,189,211,53,137,20,32,222,97,102,44,188,29,215,253,51,30,13,160,202,14,34,89,185,112,183,170,9,43,64,87,86,87,223,238,221,185,181,181,105,132,245,167,217,24,206,84,81,109,69,112,31,14,90,22,99,59,222,83,190,241,72,86,103,39,90,98,201,42,29,5,149,233,120,234,57,29,215,253,51,30,13,160,202,14,34,89,185,112,183,170,9,43,64,87,86,87,223,238,221,185,181,181,105,132,245,167,217,24,206,84,81,109,69,112,31,14,90,22,99,59,222,83,190,241,72,86,103,39,90,98,201,42,29,5,149,233,120,234,57,29,215,253,51,30,13,160,202,14,34,89,185,112,183,170,9,43,64,87,86,87,223,238,221,185,181,181,105,132,245,167,217,24,206,84,81,109,69,112,31,14,90,22,99,59,222,83,190,241,72,86,103,39,90,98,201,42,29,5,149,233,120,234,57,29,215,253,51,30,13,160,202,14,34,89,185,112,183,170,9,43,64,87,86,87,223,238,221,185,181,181,105,132,245,167,217,24,206,84,81,109,69,112,31,14,90,22,99,59,222,83,190,241,72,86,103,39,90,98,201,42,29,5,149,233,120,234,57,5,97,158,236,112,200,133,205,196,108,135,251,243,47,53,102,214,117,81,123,223,159,154,3,168,145,32,206,56,130,244,126,30,123,25,247,42,138,80,13,204,246,88,185,216,221,11,188,68,43,30,137,242,63,67,148,239,99,199,15,250,236,239,178,26,209,34,200,138,140,206,3,67,51,120,204,73,126,249,188,48,66,238,66,204,200,133,243,45,13,1,207,81,184,238,53,33,84,61,78,74,147,16,193,71,46,210,204,179,17,101,87,102,136,143,222,76,225,178,69,62,245,63,20,142,102,17,94,43,74,104,11,146,4,125,21,142,126,135,189,149,143,205,140,34,111,163,62,109,210,31,204,175,50,7,217,26,191,190,60,37,94,107,29,204,10,222,124,188,245,129,125,217,165,220,135,35,91,49,229,85,96,27,25,99,9,225,32,116,153,215,134,13,183,224,204,233,128,102,238,97,14,254,157,39,96,4,145,139,222,112,155,177,102,13,204,135,28,105,114,71,135,178,11,37,61,49,167,12,140,165,109,159,47,215,57,192,92,118,228,228,54,109,17,225,220,56,103,249,249,180,156,65,141,223,161,38,42,160,63,164,253,134,50,29,64,123,102,101,110,56,229,245,8,229,190,244,56,138,30,64,224,126,14,113,160,14,77,9,45,12,94,228,153,98,165,193,202,194,26,128,181,157,153,242,225,127,254,16,186,93,108,0,238,185,30,194,230,7,12,18,221,99,250,238,234,234,195,200,50,226,31,74,56,205,29,227,45,40,5,203,46,50,5,158,63,195,133,196,246,132,193,3,10,109,191,2,167,137,30,58,26,46,165,32,122,120,160,79,101,189,105,241,63,168,45,233,249,123,111,33,198,5,1,18,221,99,250,238,234,234,195,200,50,226,31,74,56,205,29,227,45,40,5,203,46,50,5,158,63,195,133,196,246,132,193,3,10,109,191,2,167,137,30,58,26,46,165,32,122,120,160,79,101,189,105,241,63,168,45,233,249,123,111,33,198,5,1,36,210,211,5,222,163,212,162,90,216,137,227,17,182,242,62,205,101,157,131,103,67,158,89,10,55,203,240,128,163,67,46,11,84,104,82,21,171,88,223,111,11,21,150,190,181,104,80,103,7,135,27,78,112,81,153,55,27,135,206,199,220,51,99,36,210,211,5,222,163,212,162,90,216,137,227,17,182,242,62,205,101,157,131,103,67,158,89,10,55,203,240,128,163,67,46,11,84,104,82,21,171,88,223,111,11,21,150,190,181,104,80,103,7,135,27,78,112,81,153,55,27,135,206,199,220,51,99,5,87,243,180,208,140,106,184,142,189,134,102,168,43,255,122,164,248,72,102,123,91,180,237,154,185,198,160,149,144,188,148,35,193,184,193,223,79,161,201,109,58,212,63,205,212,88,198,28,177,44,24,246,186,130,79,196,42,127,191,219,98,150,210,30,27,55,193,204,141,174,160,179,0,37,53,36,136,216,198,141,151,234,89,24,169,108,27,187,206,93,252,81,9,114,250,11,86,193,237,94,156,198,129,86,30,189,73,132,76,233,115,138,196,104,103,157,87,116,229,106,78,21,39,106,226,58,77,29,111,130,131,204,12,252,184,28,57,227,239,53,123,25,91,179,75,13,217,59,7,184,36,162,99,114,255,33,93,151,64,31,154,80,215,92,243,125,75,158,129,2,100,62,109,114,145,207,19,64,188,194,238,188,161,105,82,215,187,255,129,66,42,0,4,227,243,247,86,220,10,203,72,59,67,3,196,123,150,4,19,206,36,62,73,89,61,9,86,228,15,142,253,131,156,14,182,251,152,110,37,82,148,89,49,233,165,68,238,94,1,242,10,63,217,215,238,27,1,199,122,153,227,5,252,162,130,5,84,176,32,206,60,5,239,203,180,11,27,6,199,83,154,165,93,110,178,50,238,206,176,69,218,52,216,57,244,188,173,22,16,12,205,152,202,57,69,249,46,210,51,37,153,2,62,41,217,203,156,11,168,90,54,97,133,51,192,111,38,80,219,16,1,222,166,157,12,66,31,148,19,80,244,221,88,178,169,184,221,126,138,53,253,22,130,170,205,119,159,63,236,213,154,48,17,177,98,173,245,156,79,8,196,139,150,246,244,48,154,11,52,70,174,209,194,49,64,211,67,102,141,99,90,158,70,11,45,168,40,110,106,228,61,196,238,177,145,125,1,103,223,97,141,114,176,15,30,190,33,51,177,193,109,105,189,236,96,46,207,101,18,69,57,75,81,149,182,24,233,96,82,188,9,85,198,157,14,136,25,78,168,136,0,5,28,43,248,184,141,37,148,167,49,97,233,139,198,187,25,120,167,13,107,231,166,194,199,101,203,82,173,217,251,156,249,122,114,82,6,12,203,38,87,199,54,5,31,32,49,16,106,205,102,32,166,254,116,189,178,128,32,108,46,244,211,79,66,185,42,58,205,128,138,16,88,244,74,214,73,232,99,4,155,116,240,57,196,80,218,213,15,4,34,143,84,229,48,113,131,78,9,225,194,17,183,11,192,60,161,238,58,29,156,145,100,141,166,247,142,233,184,196,87,175,125,170,235,48,91,201,123,62,33,221,99,177,221,43,39,19,164,127,176,230,133,125,253,148,31,156,219,58,108,152,171,61,204,249,46,189,5,187,215,50,97,223,228,176,1,39,173,107,240,75,53,172,163,139,6,192,61,176,75,42,86,117,250,74,97,25,214,9,111,111,14,128,154,243,34,2,5,28,233,147,56,38,111,161,171,144,119,135,81,240,223,184,72,230,156,128,84,158,22,107,182,165,149,176,230,138,10,206,123,43,109,175,154,150,13,135,23,212,71,68,197,70,18,189,50,107,49,87,20,164,202,92,155,125,15,140,12,251,188,133,162,14,14,143,225,255,95,174,138,38,229,15,251,168,183,155,96,255,223,56,223,246,57,163,220,138,153,171,97,87,224,62,138,24,130,72,20,9,31,78,124,205,14,38,55,70,71,212,71,167,145,164,143,163,107,23,245,186,128,204,1,225,98,30,174,30,59,152,201,17,147,225,50,235,78,28,193,217,239,218,64,94,97,248,187,7,90,57,43,117,143,49,237,87,144,166,187,21,156,106,78,40,241,0,35,195,49,160,107,234,81,224,62,5,8,225,77,235,25,14,19,208,212,166,243,206,249,180,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,35,209,230,242,37,228,69,205,67,153,28,239,220,6,0,195,188,151,40,65,227,90,205,58,252,183,16,238,166,199,77,134,47,230,73,212,139,158,187,8,53,166,234,25,168,58,76,24,116,175,163,11,18,45,217,81,195,120,53,233,31,96,85,242,47,230,73,212,139,158,187,8,53,166,234,25,168,58,76,24,116,175,163,11,18,45,217,81,195,120,53,233,31,96,85,242,12,67,227,37,50,43,130,91,122,84,125,197,38,133,90,47,96,150,174,235,177,51,214,171,202,230,232,176,43,109,178,26,8,108,157,152,171,9,26,58,107,148,231,177,48,183,30,242,168,195,109,162,187,190,64,190,125,146,228,228,205,184,97,197,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,44,15,191,195,193,77,155,13,55,48,113,153,233,41,195,53,125,11,255,167,24,114,218,2,220,72,61,73,218,221,139,11,99,215,229,180,215,209,6,19,123,184,99,138,80,231,157,237,137,32,61,6,29,222,33,124,96,82,28,42,176,241,251,12,176,99,105,181,216,72,247,50,143,35,80,6,204,182,51,189,39,225,145,6,26,231,20,29,218,200,194,17,223,96,20,47,139,109,17,222,86,252,37,134,134,198,119,57,121,40,152,12,25,72,63,139,209,76,145,32,151,188,157,31,22,131,73,23,110,9,219,148,170,204,193,76,230,103,31,40,85,128,202,125,35,189,238,83,31,184,233,210,81,50,92,8,155,33,214,18,61,43,29,168,40,4,71,164,214,18,196,56,203,148,184,221,234,211,183,226,115,105,62,239,199,183,167,20,166,247,21,34,215,1,130,242,85,38,152,103,203,234,214,112,141,213,117,46,99,234,76,87,208,3,251,82,109,175,0,173,100,189,77,28,194,58,222,71,65,217,224,83,53,139,138,60,227,88,45,117,222,99,63,74,212,33,203,168,63,183,221,28,63,134,109,28,80,55,120,86,20,124,205,5,240,63,12,112,181,6,4,172,175,2,169,241,187,67,96,135,100,32,211,126,244,221,107,36,204,131,86,21,144,77,92,240,222,146,77,159,37,236,59,193,67,236,142,185,145,167,168,170,156,245,2,15,204,59,88,46,118,114,111,58,58,230,71,234,166,194,91,23,248,139,81,66,136,193,183,173,194,81,124,110,221,46,231,96,123,130,45,33,241,149,44,132,156,251,101,21,95,71,220,21,155,130,195,243,187,14,140,248,34,251,139,147,47,185,183,37,90,205,31,37,200,111,28,95,103,99,4,171,149,97,147,169,192,77,171,193,149,132,171,167,195,192,178,85,168,200,196,49,213,244,184,16,88,33,137,116,140,151,178,238,15,146,16,248,146,153,108,50,59,130,22,186,242,140,95,177,0,51,238,14,210,71,209,34,61,97,202,128,151,46,145,122,152,217,65,95,248,189,2,136,214,240,171,229,110,145,188,215,182,8,52,243,14,226,145,18,160,41,151,114,188,26,90,85,2,139,127,163,35,144,223,203,157,150,128,65,202,50,67,51,60,242,36,213,44,48,82,33,36,50,182,205,69,229,243,26,72,38,180,225,226,179,27,247,185,198,174,136,165,72,228,38,148,93,31,19,107,238,142,41,199,22,205,245,224,214,71,188,237,216,213,115,228,209,160,78,63,59,69,222,17,250,233,199,111,89,147,73,139,0,103,35,96,82,23,227,150,177,37,241,154,115,86,92,117,175,197,62,82,107,233,230,61,81,252,145,59,216,143,216,174,138,81,31,249,191,189,64,116,209,78,148,76,46,239,141,8,128,34,239,3,66,244,142,162,112,62,185,78,71,128,183,7,36,17,32,17,45,199,45,34,199,4,145,34,163,169,2,182,40,151,49,143,135,207,61,209,229,194,0,38,3,68,214,212,166,112,20,197,7,53,166,81,26,213,22,30,23,143,90,146,71,199,110,123,253,84,11,70,40,179,11,190,248,106,176,231,43,170,1,42,114,249,140,209,20,164,133,251,247,197,84,218,50,249,8,182,131,185,250,2,216,165,109,70,21,158,92,100,202,125,30,69,77,86,47,40,219,238,36,226,31,173,188,66,254,142,224,13,10,51,153,42,67,81,254,91,94,23,126,233,121,142,2,233,107,159,217,115,4,28,161,193,77,100,158,193,130,245,233,12,20,209,131,18,86,35,85,80,169,95,232,203,22,96,31,249,191,189,64,116,209,78,148,76,46,239,141,8,128,34,239,3,66,244,142,162,112,62,185,78,71,128,183,7,36,17,30,69,77,86,47,40,219,238,36,226,31,173,188,66,254,142,224,13,10,51,153,42,67,81,254,91,94,23,126,233,121,142,2,233,107,159,217,115,4,28,161,193,77,100,158,193,130,245,233,12,20,209,131,18,86,35,85,80,169,95,232,203,22,96,31,249,191,189,64,116,209,78,148,76,46,239,141,8,128,34,239,3,66,244,142,162,112,62,185,78,71,128,183,7,36,17,26,185,14,39,29,113,251,188,111,49,60,223,55,168,77,109,159,84,171,170,54,214,30,138,177,101,97,193,47,255,208,51,18,89,132,247,167,124,98,64,133,234,83,109,113,86,74,12,227,9,51,94,187,10,44,224,137,186,225,121,105,161,49,126,31,249,191,189,64,116,209,78,148,76,46,239,141,8,128,34,239,3,66,244,142,162,112,62,185,78,71,128,183,7,36,17,26,185,14,39,29,113,251,188,111,49,60,223,55,168,77,109,159,84,171,170,54,214,30,138,177,101,97,193,47,255,208,51,18,89,132,247,167,124,98,64,133,234,83,109,113,86,74,12,227,9,51,94,187,10,44,224,137,186,225,121,105,161,49,126,31,249,191,189,64,116,209,78,148,76,46,239,141,8,128,34,239,3,66,244,142,162,112,62,185,78,71,128,183,7,36,17,19,240,242,58,204,225,223,144,187,3,145,166,71,107,28,123,131,253,65,142,25,76,124,50,30,39,184,114,62,3,53,139,11,122,182,186,105,40,180,249,42,240,92,199,89,116,89,160,60,58,127,129,83,196,215,93,163,127,133,158,68,156,174,247,46,120,152,224,87,12,202,201,126,6,248,193,57,31,12,179,161,38,94,175,197,89,79,154,29,72,50,28,32,154,137,73,47,6,116,7,31,25,50,207,207,218,19,188,25,184,99,248,210,83,242,134,143,245,11,77,53,80,40,200,153,62,79,165],"hex_proof":"0x076657be99e53b06fadd0a3fd2331acf2ec29161ed01f52af8ad31bd1500d87d2209c77dab6c0c65594b1f85b393efeddacf51c6528190f744cca62cd942dd2311211da08179e52c5291346bd59ecb62813861cc2250f5032205fc1fa73a38022ed232d33523fa8f7a1f0e54ab53d075ec0361623b6659a1a712c2e91a2d3a262a53ce9c960945b4c9be3ef8746806a8cf70a6f4a8a03c53fa4a283f13552c1b1ea5fc23d445cc91ff12ccf942f5c0746f26af6afc6521b681c8bcf6e06f12741d881519534530d99960a266d29cbc08b7afb5c22df4e79a6613129866a722af025c2ed288284ecafb4dd168fe741525a82c877781d8fcab4632f367c273127d162a1db7116e42a953e22ecbd85b1594a7fc219dc99c14e995f457b8f8ff0e902c310df94d8d45d3b46e3f435b4beb8a585d4a71fd426fc0f384b656b81687b302700087b2bfa2d09f3ac64b779376d37b05d8cef23026629cd71c466121e0951c91fabc0134b7ebec75068c501dd71ffe9c110a1db87c37e48b3fc103cf441009cfa9883522a6c3e116ec0ac8f63d04ec1f47a10c117e871ac508658e52e7392c4c405625deb555a6ba028a6c46742d3c56dc2c17f0a2b98dc49332a32ac5071dd7fd331e0da0ca0e2259b970b7aa092b40575657dfeeddb9b5b56984f5a7d918ce54516d45701f0e5a16633bde53bef1485667275a62c92a1d0595e978ea392a1d174b7f8a5439f1c147d4d5b819a3834f371cb634b241c1d6d35418349bf715c8f2aa92f42ea426a605c913d66759140805ad9dbdd335891420de61662cbc1dd7fd331e0da0ca0e2259b970b7aa092b40575657dfeeddb9b5b56984f5a7d918ce54516d45701f0e5a16633bde53bef1485667275a62c92a1d0595e978ea391dd7fd331e0da0ca0e2259b970b7aa092b40575657dfeeddb9b5b56984f5a7d918ce54516d45701f0e5a16633bde53bef1485667275a62c92a1d0595e978ea391dd7fd331e0da0ca0e2259b970b7aa092b40575657dfeeddb9b5b56984f5a7d918ce54516d45701f0e5a16633bde53bef1485667275a62c92a1d0595e978ea391dd7fd331e0da0ca0e2259b970b7aa092b40575657dfeeddb9b5b56984f5a7d918ce54516d45701f0e5a16633bde53bef1485667275a62c92a1d0595e978ea3905619eec70c885cdc46c87fbf32f3566d675517bdf9f9a03a89120ce3882f47e1e7b19f72a8a500dccf658b9d8dd0bbc442b1e89f23f4394ef63c70ffaecefb21ad122c88a8cce03433378cc497ef9bc3042ee42ccc885f32d0d01cf51b8ee3521543d4e4a9310c1472ed2ccb311655766888fde4ce1b2453ef53f148e66115e2b4a680b92047d158e7e87bd958fcd8c226fa33e6dd21fccaf3207d91abfbe3c255e6b1dcc0ade7cbcf5817dd9a5dc87235b31e555601b196309e1207499d7860db7e0cce98066ee610efe9d276004918bde709bb1660dcc871c69724787b20b253d31a70c8ca56d9f2fd739c05c76e4e4366d11e1dc3867f9f9b49c418ddfa1262aa03fa4fd86321d407b66656e38e5f508e5bef4388a1e40e07e0e71a00e4d092d0c5ee49962a5c1cac21a80b59d99f2e17ffe10ba5d6c00eeb91ec2e6070c12dd63faeeeaeac3c832e21f4a38cd1de32d2805cb2e32059e3fc385c4f684c1030a6dbf02a7891e3a1a2ea5207a78a04f65bd69f13fa82de9f97b6f21c6050112dd63faeeeaeac3c832e21f4a38cd1de32d2805cb2e32059e3fc385c4f684c1030a6dbf02a7891e3a1a2ea5207a78a04f65bd69f13fa82de9f97b6f21c6050124d2d305dea3d4a25ad889e311b6f23ecd659d8367439e590a37cbf080a3432e0b54685215ab58df6f0b1596beb568506707871b4e705199371b87cec7dc336324d2d305dea3d4a25ad889e311b6f23ecd659d8367439e590a37cbf080a3432e0b54685215ab58df6f0b1596beb568506707871b4e705199371b87cec7dc33630557f3b4d08c6ab88ebd8666a82bff7aa4f848667b5bb4ed9ab9c6a09590bc9423c1b8c1df4fa1c96d3ad43fcdd458c61cb12c18f6ba824fc42a7fbfdb6296d21e1b37c1cc8daea0b30025352488d8c68d97ea5918a96c1bbbce5dfc510972fa0b56c1ed5e9cc681561ebd49844ce9738ac468679d5774e56a4e15276ae23a4d1d6f8283cc0cfcb81c39e3ef357b195bb34b0dd93b07b824a26372ff215d97401f9a50d75cf37d4b9e8102643e6d7291cf1340bcc2eebca16952d7bbff81422a0004e3f3f756dc0acb483b4303c47b960413ce243e49593d0956e40f8efd839c0eb6fb986e2552945931e9a544ee5e01f20a3fd9d7ee1b01c77a99e305fca2820554b020ce3c05efcbb40b1b06c7539aa55d6eb232eeceb045da34d839f4bcad16100ccd98ca3945f92ed2332599023e29d9cb9c0ba85a36618533c06f2650db1001dea69d0c421f941350f4dd58b2a9b8dd7e8a35fd1682aacd779f3fecd59a3011b162adf59c4f08c48b96f6f4309a0b3446aed1c23140d343668d635a9e460b2da8286e6ae43dc4eeb1917d0167df618d72b00f1ebe2133b1c16d69bdec602ecf651245394b5195b618e96052bc0955c69d0e88194ea88800051c2bf8b88d2594a73161e98bc6bb1978a70d6be7a6c2c765cb52add9fb9cf97a7252060ccb2657c736051f2031106acd6620a6fe74bdb280206c2ef4d34f42b92a3acd808a1058f44ad649e863049b74f039c450dad50f04228f54e53071834e09e1c211b70bc03ca1ee3a1d9c91648da6f78ee9b8c457af7daaeb305bc97b3e21dd63b1dd2b2713a47fb0e6857dfd941f9cdb3a6c98ab3dccf92ebd05bbd73261dfe4b00127ad6bf04b35aca38b06c03db04b2a5675fa4a6119d6096f6f0e809af32202051ce99338266fa1ab90778751f0dfb848e69c80549e166bb6a595b0e68a0ace7b2b6daf9a960d8717d44744c54612bd326b315714a4ca5c9b7d0f8c0cfbbc85a20e0e8fe1ff5fae8a26e50ffba8b79b60ffdf38dff639a3dc8a99ab6157e03e8a18824814091f4e7ccd0e26374647d447a791a48fa36b17f5ba80cc01e1621eae1e3b98c91193e132eb4e1cc1d9efda405e61f8bb075a392b758f31ed5790a6bb159c6a4e28f10023c331a06bea51e03e0508e14deb190e13d0d4a6f3cef9b42d000000000000000000000000000000000000000000000000000000000000000023d1e6f225e445cd43991cefdc0600c3bc972841e35acd3afcb710eea6c74d862fe649d48b9ebb0835a6ea19a83a4c1874afa30b122dd951c37835e91f6055f22fe649d48b9ebb0835a6ea19a83a4c1874afa30b122dd951c37835e91f6055f20c43e325322b825b7a547dc526855a2f6096aeebb133d6abcae6e8b02b6db21a086c9d98ab091a3a6b94e7b130b71ef2a8c36da2bbbe40be7d92e4e4cdb861c500000000000000000000000000000000000000000000000000000000000000001f2c0fbfc3c14d9b0d37307199e929c3357d0bffa71872da02dc483d49dadd8b0b63d7e5b4d7d106137bb8638a50e79ded89203d061dde217c60521c2ab0f1fb0cb06369b5d848f7328f235006ccb633bd27e191061ae7141ddac8c211df60142f8b6d11de56fc258686c677397928980c19483f8bd14c912097bc9d1f168349176e09db94aaccc14ce6671f285580ca7d23bdee531fb8e9d251325c089b21d6123d2b1da8280447a4d612c438cb94b8ddead3b7e273693eefc7b7a714a6f71522d70182f255269867cbead6708dd5752e63ea4c57d003fb526daf00ad64bd4d1cc23ade4741d9e053358b8a3ce3582d75de633f4ad421cba83fb7dd1c3f866d1c50377856147ccd05f03f0c70b50604acaf02a9f1bb4360876420d37ef4dd6b24cc835615904d5cf0de924d9f25ec3bc143ec8eb991a7a8aa9cf5020fcc3b582e76726f3a3ae647eaa6c25b17f88b514288c1b7adc2517c6edd2ee7607b822d21f1952c849cfb65155f47dc159b82c3f3bb0e8cf822fb8b932fb9b7255acd1f25c86f1c5f676304ab956193a9c04dabc19584aba7c3c0b255a8c8c431d5f4b810582189748c97b2ee0f9210f892996c323b8216baf28c5fb10033ee0ed247d1223d61ca80972e917a98d9415ff8bd0288d6f0abe56e91bcd7b60834f30ee29112a0299772bc1a5a55028b7fa32390dfcb9d968041ca3243333cf224d52c3052212432b6cd45e5f31a4826b4e1e2b31bf7b9c6ae88a548e426945d1f136bee8e29c716cdf5e0d647bcedd8d573e4d1a04e3f3b45de11fae9c76f5993498b006723605217e396b125f19a73565c75afc53e526be9e63d51fc913bd88fd8ae8a511ff9bfbd4074d14e944c2eef8d088022ef0342f48ea2703eb94e4780b707241120112dc72d22c7049122a3a902b62897318f87cf3dd1e5c200260344d6d4a67014c50735a6511ad5161e178f5a9247c76e7bfd540b4628b30bbef86ab0e72baa012a72f98cd114a485fbf7c554da32f908b683b9fa02d8a56d46159e5c64ca7d1e454d562f28dbee24e21fadbc42fe8ee00d0a33992a4351fe5b5e177ee9798e02e96b9fd973041ca1c14d649ec182f5e90c14d1831256235550a95fe8cb16601ff9bfbd4074d14e944c2eef8d088022ef0342f48ea2703eb94e4780b70724111e454d562f28dbee24e21fadbc42fe8ee00d0a33992a4351fe5b5e177ee9798e02e96b9fd973041ca1c14d649ec182f5e90c14d1831256235550a95fe8cb16601ff9bfbd4074d14e944c2eef8d088022ef0342f48ea2703eb94e4780b70724111ab90e271d71fbbc6f313cdf37a84d6d9f54abaa36d61e8ab16561c12fffd033125984f7a77c624085ea536d71564a0ce309335ebb0a2ce089bae17969a1317e1ff9bfbd4074d14e944c2eef8d088022ef0342f48ea2703eb94e4780b70724111ab90e271d71fbbc6f313cdf37a84d6d9f54abaa36d61e8ab16561c12fffd033125984f7a77c624085ea536d71564a0ce309335ebb0a2ce089bae17969a1317e1ff9bfbd4074d14e944c2eef8d088022ef0342f48ea2703eb94e4780b707241113f0f23acce1df90bb0391a6476b1c7b83fd418e194c7c321e27b8723e03358b0b7ab6ba6928b4f92af05cc7597459a03c3a7f8153c4d75da37f859e449caef72e7898e0570ccac97e06f8c1391f0cb3a1265eafc5594f9a1d48321c209a89492f0674071f1932cfcfda13bc19b863f8d253f2868ff50b4d355028c8993e4fa5","transcript_type":"EVM","split":null,"pretty_public_inputs":{"rescaled_inputs":[],"inputs":[],"processed_inputs":[],"processed_params":[],"processed_outputs":[],"rescaled_outputs":[["0","0","0","0"]],"outputs":[["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000"]]},"timestamp":1729639678889,"commitment":"KZG"} \ No newline at end of file diff --git a/tests/wasm/proof_aggr.json b/tests/assets/proof_aggr.json similarity index 100% rename from tests/wasm/proof_aggr.json rename to tests/assets/proof_aggr.json diff --git a/tests/wasm/settings.json b/tests/assets/settings.json similarity index 97% rename from tests/wasm/settings.json rename to tests/assets/settings.json index 227c2d71f..a123fc99d 100644 --- a/tests/wasm/settings.json +++ b/tests/assets/settings.json @@ -33,6 +33,7 @@ "total_assignments": 92, "total_const_size": 3, "total_dynamic_col_size": 0, + "max_dynamic_input_len": 0, "num_dynamic_lookups": 0, "num_shuffles": 0, "total_shuffle_col_size": 0, diff --git a/tests/wasm/vk.key b/tests/assets/vk.key similarity index 100% rename from tests/wasm/vk.key rename to tests/assets/vk.key diff --git a/tests/wasm/vk_aggr.key b/tests/assets/vk_aggr.key similarity index 100% rename from tests/wasm/vk_aggr.key rename to tests/assets/vk_aggr.key diff --git a/tests/wasm/witness.json b/tests/assets/witness.json similarity index 100% rename from tests/wasm/witness.json rename to tests/assets/witness.json diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 29dd0c852..c9684e9fd 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,4 +1,4 @@ -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] #[cfg(test)] mod native_tests { @@ -1004,7 +1004,6 @@ mod native_tests { // Global variables to store verifier hashes and identical verifiers lazy_static! { - static ref REUSABLE_VERIFIER_ADDR: std::sync::Mutex> = std::sync::Mutex::new(None); static ref ANVIL_INSTANCE: std::sync::Mutex> = std::sync::Mutex::new(None); } @@ -1138,21 +1137,11 @@ mod native_tests { init_logger(); log::error!("Running kzg_evm_prove_and_verify_reusable_verifier_ for test: {}", test); // default vis - let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "private", "private", "public", &mut REUSABLE_VERIFIER_ADDR.lock().unwrap(), false); + kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "private", "private", "public", false); // public/public vis - let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "public", "private", "public", &mut Some(reusable_verifier_address), false); + kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "public", "private", "public", false); // hashed input - let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "hashed", "private", "public", &mut Some(reusable_verifier_address), false); - - match REUSABLE_VERIFIER_ADDR.try_lock() { - Ok(mut addr) => { - *addr = Some(reusable_verifier_address.clone()); - log::error!("Reusing the same verifier deployed at address: {}", reusable_verifier_address); - } - Err(_) => { - log::error!("Failed to acquire lock on REUSABLE_VERIFIER_ADDR"); - } - } + kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "hashed", "private", "public", false); test_dir.close().unwrap(); } @@ -1171,21 +1160,11 @@ mod native_tests { init_logger(); log::error!("Running kzg_evm_prove_and_verify_reusable_verifier_with_overflow_ for test: {}", test); // default vis - let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "private", "private", "public", &mut REUSABLE_VERIFIER_ADDR.lock().unwrap(), true); + kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "private", "private", "public", true); // public/public vis - let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "public", "private", "public", &mut Some(reusable_verifier_address), true); + kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "public", "private", "public", true); // hashed input - let reusable_verifier_address: String = kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "hashed", "private", "public", &mut Some(reusable_verifier_address), true); - - match REUSABLE_VERIFIER_ADDR.try_lock() { - Ok(mut addr) => { - *addr = Some(reusable_verifier_address.clone()); - log::error!("Reusing the same verifier deployed at address: {}", reusable_verifier_address); - } - Err(_) => { - log::error!("Failed to acquire lock on REUSABLE_VERIFIER_ADDR"); - } - } + kzg_evm_prove_and_verify_reusable_verifier(2, path, test.to_string(), "hashed", "private", "public", true); test_dir.close().unwrap(); } @@ -1201,8 +1180,8 @@ mod native_tests { let path = test_dir.path().to_str().unwrap(); crate::native_tests::mv_test_(path, test); let _anvil_child = crate::native_tests::start_anvil(false, Hardfork::Latest); kzg_evm_prove_and_verify(2, path, test.to_string(), "private", "private", "public"); - // #[cfg(not(feature = "icicle"))] - // run_js_tests(path, test.to_string(), "testBrowserEvmVerify", false); + #[cfg(not(feature = "icicle"))] + run_js_tests(path, test.to_string(), "testBrowserEvmVerify", false); test_dir.close().unwrap(); } @@ -2237,9 +2216,8 @@ mod native_tests { input_visibility: &str, param_visibility: &str, output_visibility: &str, - _reusable_verifier_address: &mut Option, overflow: bool, - ) -> String { + ) { let anvil_url = ANVIL_URL.as_str(); prove_and_verify( @@ -2335,6 +2313,9 @@ mod native_tests { addr }; + let addr_path_arg_vk = format!("--addr-path={}/{}/addr_vk.txt", test_dir, example_name); + let sol_arg_vk: String = format!("--sol-code-path={}/{}/vk.sol", test_dir, example_name); + // create the verifier let addr_path_arg_vk = format!("--addr-path={}/{}/addr_vk.txt", test_dir, example_name); let sol_arg_vk: String = format!("--sol-code-path={}/{}/vk.sol", test_dir, example_name); // create the verifier @@ -2451,9 +2432,6 @@ mod native_tests { i ); } - - // Returned deploy_addr_arg for reusable verifier - deployed_addr_arg } // run js browser evm verify tests for a given example diff --git a/tests/ios/can_verify_aggr.swift b/tests/ios/can_verify_aggr.swift new file mode 100644 index 000000000..b5986058c --- /dev/null +++ b/tests/ios/can_verify_aggr.swift @@ -0,0 +1,39 @@ +// Write a simple swift test +import ezkl +import Foundation + +let pathToFile = "../../../../tests/assets/" + + +func loadFileAsBytes(from path: String) -> Data? { + let url = URL(fileURLWithPath: path) + return try? Data(contentsOf: url) +} + +do { + let proofAggrPath = pathToFile + "proof_aggr.json" + let vkAggrPath = pathToFile + "vk_aggr.key" + let srs1Path = pathToFile + "kzg1.srs" + + guard let proofAggr = loadFileAsBytes(from: proofAggrPath) else { + fatalError("Failed to load proofAggr file") + } + guard let vkAggr = loadFileAsBytes(from: vkAggrPath) else { + fatalError("Failed to load vkAggr file") + } + guard let srs1 = loadFileAsBytes(from: srs1Path) else { + fatalError("Failed to load srs1 file") + } + + let value = try verifyAggr( + proof: proofAggr, + vk: vkAggr, + logrows: 21, + srs: srs1, + commitment: "kzg" + ) + + // should not fail + assert(value == true, "Failed the test") + +} \ No newline at end of file diff --git a/tests/ios/gen_pk_test.swift b/tests/ios/gen_pk_test.swift new file mode 100644 index 000000000..d51173fec --- /dev/null +++ b/tests/ios/gen_pk_test.swift @@ -0,0 +1,42 @@ +// Swift version of gen_pk_test +import ezkl +import Foundation + +func loadFileAsBytes(from path: String) -> Data? { + let url = URL(fileURLWithPath: path) + return try? Data(contentsOf: url) +} + +do { + let pathToFile = "../../../../tests/assets/" + let networkCompiledPath = pathToFile + "model.compiled" + let srsPath = pathToFile + "kzg" + + // Load necessary files + guard let compiledCircuit = loadFileAsBytes(from: networkCompiledPath) else { + fatalError("Failed to load network compiled file") + } + guard let srs = loadFileAsBytes(from: srsPath) else { + fatalError("Failed to load SRS file") + } + + // Generate the vk (Verifying Key) + let vk = try genVk( + compiledCircuit: compiledCircuit, + srs: srs, + compressSelectors: true // Corresponds to the `true` boolean in the Rust code + ) + + // Generate the pk (Proving Key) + let pk = try genPk( + vk: vk, + compiledCircuit: compiledCircuit, + srs: srs + ) + + // Ensure that the proving key is not empty + assert(pk.count > 0, "Proving key generation failed, pk is empty") + +} catch { + fatalError("Test failed with error: \(error)") +} \ No newline at end of file diff --git a/tests/ios/gen_vk_test.swift b/tests/ios/gen_vk_test.swift new file mode 100644 index 000000000..51b10be8a --- /dev/null +++ b/tests/ios/gen_vk_test.swift @@ -0,0 +1,35 @@ +// Swift version of gen_vk_test +import ezkl +import Foundation + +func loadFileAsBytes(from path: String) -> Data? { + let url = URL(fileURLWithPath: path) + return try? Data(contentsOf: url) +} + +do { + let pathToFile = "../../../../tests/assets/" + let networkCompiledPath = pathToFile + "model.compiled" + let srsPath = pathToFile + "kzg" + + // Load necessary files + guard let compiledCircuit = loadFileAsBytes(from: networkCompiledPath) else { + fatalError("Failed to load network compiled file") + } + guard let srs = loadFileAsBytes(from: srsPath) else { + fatalError("Failed to load SRS file") + } + + // Generate the vk (Verifying Key) + let vk = try genVk( + compiledCircuit: compiledCircuit, + srs: srs, + compressSelectors: true // Corresponds to the `true` boolean in the Rust code + ) + + // Ensure that the verifying key is not empty + assert(vk.count > 0, "Verifying key generation failed, vk is empty") + +} catch { + fatalError("Test failed with error: \(error)") +} \ No newline at end of file diff --git a/tests/ios/pk_is_valid_test.swift b/tests/ios/pk_is_valid_test.swift new file mode 100644 index 000000000..deffdc18a --- /dev/null +++ b/tests/ios/pk_is_valid_test.swift @@ -0,0 +1,69 @@ +// Swift version of pk_is_valid_test +import ezkl +import Foundation + +func loadFileAsBytes(from path: String) -> Data? { + let url = URL(fileURLWithPath: path) + return try? Data(contentsOf: url) +} + +do { + let pathToFile = "../../../../tests/assets/" + let networkCompiledPath = pathToFile + "model.compiled" + let srsPath = pathToFile + "kzg" + let witnessPath = pathToFile + "witness.json" + let settingsPath = pathToFile + "settings.json" + + // Load necessary files + guard let compiledCircuit = loadFileAsBytes(from: networkCompiledPath) else { + fatalError("Failed to load network compiled file") + } + guard let srs = loadFileAsBytes(from: srsPath) else { + fatalError("Failed to load SRS file") + } + guard let witness = loadFileAsBytes(from: witnessPath) else { + fatalError("Failed to load witness file") + } + guard let settings = loadFileAsBytes(from: settingsPath) else { + fatalError("Failed to load settings file") + } + + // Generate the vk (Verifying Key) + let vk = try genVk( + compiledCircuit: compiledCircuit, + srs: srs, + compressSelectors: true // Corresponds to the `true` boolean in the Rust code + ) + + // Generate the pk (Proving Key) + let pk = try genPk( + vk: vk, + compiledCircuit: compiledCircuit, + srs: srs + ) + + // Prove using the witness and proving key + let proof = try prove( + witness: witness, + pk: pk, + compiledCircuit: compiledCircuit, + srs: srs + ) + + // Ensure that the proof is not empty + assert(proof.count > 0, "Proof generation failed, proof is empty") + + // Verify the proof + let value = try verify( + proof: proof, + vk: vk, + settings: settings, + srs: srs + ) + + // Ensure that the verification passed + assert(value == true, "Verification failed") + +} catch { + fatalError("Test failed with error: \(error)") +} \ No newline at end of file diff --git a/tests/ios/verify_encode_verifier_calldata.swift b/tests/ios/verify_encode_verifier_calldata.swift new file mode 100644 index 000000000..db9308a81 --- /dev/null +++ b/tests/ios/verify_encode_verifier_calldata.swift @@ -0,0 +1,71 @@ +// Swift version of verify_encode_verifier_calldata test +import ezkl +import Foundation + +func loadFileAsBytes(from path: String) -> Data? { + let url = URL(fileURLWithPath: path) + return try? Data(contentsOf: url) +} + +do { + let pathToFile = "../../../../tests/assets/" + let proofPath = pathToFile + "proof.json" + + guard let proof = loadFileAsBytes(from: proofPath) else { + fatalError("Failed to load proof file") + } + + // Test without vk address + let calldataNoVk = try encodeVerifierCalldata( + proof: proof, + vkAddress: nil + ) + + // Deserialize the proof data + struct Snark: Decodable { + let proof: Data + let instances: Data + } + + let snark = try JSONDecoder().decode(Snark.self, from: proof) + + let flattenedInstances = snark.instances.flatMap { $0 } + let referenceCalldataNoVk = try encodeCalldata( + vk: nil, + proof: snark.proof, + instances: flattenedInstances + ) + + // Check if the encoded calldata matches the reference + assert(calldataNoVk == referenceCalldataNoVk, "Calldata without vk does not match") + + // Test with vk address + let vkAddressString = "0000000000000000000000000000000000000000" + let vkAddressData = Data(hexString: vkAddressString) + + guard vkAddressData.count == 20 else { + fatalError("Invalid VK address length") + } + + let vkAddressArray = [UInt8](vkAddressData) + + // Serialize vkAddress to match JSON serialization in Rust + let serializedVkAddress = try JSONEncoder().encode(vkAddressArray) + + let calldataWithVk = try encodeVerifierCalldata( + proof: proof, + vk: serializedVkAddress + ) + + let referenceCalldataWithVk = try encodeCalldata( + vk: vkAddressArray, + proof: snark.proof, + instances: flattenedInstances + ) + + // Check if the encoded calldata matches the reference + assert(calldataWithVk == referenceCalldataWithVk, "Calldata with vk does not match") + +} catch { + fatalError("Test failed with error: \(error)") +} \ No newline at end of file diff --git a/tests/ios/verify_gen_witness.swift b/tests/ios/verify_gen_witness.swift new file mode 100644 index 000000000..894a1a7ed --- /dev/null +++ b/tests/ios/verify_gen_witness.swift @@ -0,0 +1,45 @@ +// Swift version of verify_gen_witness test +import ezkl +import Foundation + +func loadFileAsBytes(from path: String) -> Data? { + let url = URL(fileURLWithPath: path) + return try? Data(contentsOf: url) +} + +do { + let pathToFile = "../../../../tests/assets/" + let networkCompiledPath = pathToFile + "model.compiled" + let inputPath = pathToFile + "input.json" + let witnessPath = pathToFile + "witness.json" + + // Load necessary files + guard let networkCompiled = loadFileAsBytes(from: networkCompiledPath) else { + fatalError("Failed to load network compiled file") + } + guard let input = loadFileAsBytes(from: inputPath) else { + fatalError("Failed to load input file") + } + guard let referenceWitnessData = loadFileAsBytes(from: witnessPath) else { + fatalError("Failed to load witness file") + } + + // Generate witness using genWitness function + let witnessData = try genWitness( + compiledCircuit: networkCompiled, + input: input + ) + + // Deserialize the witness + struct GraphWitness: Decodable, Equatable {} + let witness = try JSONDecoder().decode(GraphWitness.self, from: witnessData) + + // Deserialize the reference witness + let referenceWitness = try JSONDecoder().decode(GraphWitness.self, from: referenceWitnessData) + + // Check if the witness matches the reference witness + assert(witness == referenceWitness, "Witnesses do not match") + +} catch { + fatalError("Test failed with error: \(error)") +} \ No newline at end of file diff --git a/tests/ios/verify_kzg_commit.swift b/tests/ios/verify_kzg_commit.swift new file mode 100644 index 000000000..0d9cc3d54 --- /dev/null +++ b/tests/ios/verify_kzg_commit.swift @@ -0,0 +1,64 @@ +// Swift version of verify_kzg_commit test +import ezkl +import Foundation + +func loadFileAsBytes(from path: String) -> Data? { + let url = URL(fileURLWithPath: path) + return try? Data(contentsOf: url) +} + +do { + let pathToFile = "../../../../tests/assets/" + let vkPath = pathToFile + "vk.key" + let srsPath = pathToFile + "kzg" + let settingsPath = pathToFile + "settings.json" + + guard let vk = loadFileAsBytes(from: vkPath) else { + fatalError("Failed to load vk file") + } + guard let srs = loadFileAsBytes(from: srsPath) else { + fatalError("Failed to load srs file") + } + guard let settings = loadFileAsBytes(from: settingsPath) else { + fatalError("Failed to load settings file") + } + + // Create a vector of field elements + var message: [UInt64] = [] + for i in 0..<32 { + message.append(UInt64(i)) + } + + // Serialize the message array + let messageData = try JSONEncoder().encode(message) + + // Deserialize settings + struct GraphSettings: Decodable {} + let settingsDecoded = try JSONDecoder().decode(GraphSettings.self, from: settings) + + // Generate commitment + let commitmentData = try kzgCommit( + message: messageData, + vk: vk, + settings: settings, + srs: srs + ) + + // Deserialize the resulting commitment + struct G1Affine: Decodable {} + let commitment = try JSONDecoder().decode([G1Affine].self, from: commitmentData) + + // Reference commitment using params and vk + // For Swift, you'd need to implement or link the corresponding methods like in Rust + let referenceCommitment = try polyCommit( + message: message, + vk: vk, + srs: srs + ) + + // Check if the commitment matches the reference + assert(commitment == referenceCommitment, "Commitments do not match") + +} catch { + fatalError("Test failed with error: \(error)") +} \ No newline at end of file diff --git a/tests/ios/verify_validations.swift b/tests/ios/verify_validations.swift new file mode 100644 index 000000000..a7497755d --- /dev/null +++ b/tests/ios/verify_validations.swift @@ -0,0 +1,103 @@ +// Swift version of verify_validations test +import ezkl +import Foundation + +func loadFileAsBytes(from path: String) -> Data? { + let url = URL(fileURLWithPath: path) + return try? Data(contentsOf: url) +} + +do { + let pathToFile = "../../../../tests/assets/" + let compiledCircuitPath = pathToFile + "model.compiled" + let networkPath = pathToFile + "network.onnx" + let witnessPath = pathToFile + "witness.json" + let inputPath = pathToFile + "input.json" + let proofPath = pathToFile + "proof.json" + let vkPath = pathToFile + "vk.key" + let pkPath = pathToFile + "pk.key" + let settingsPath = pathToFile + "settings.json" + let srsPath = pathToFile + "kzg" + + // Load necessary files + guard let compiledCircuit = loadFileAsBytes(from: compiledCircuitPath) else { + fatalError("Failed to load network compiled file") + } + guard let network = loadFileAsBytes(from: networkPath) else { + fatalError("Failed to load network file") + } + guard let witness = loadFileAsBytes(from: witnessPath) else { + fatalError("Failed to load witness file") + } + guard let input = loadFileAsBytes(from: inputPath) else { + fatalError("Failed to load input file") + } + guard let proof = loadFileAsBytes(from: proofPath) else { + fatalError("Failed to load proof file") + } + guard let vk = loadFileAsBytes(from: vkPath) else { + fatalError("Failed to load vk file") + } + guard let pk = loadFileAsBytes(from: pkPath) else { + fatalError("Failed to load pk file") + } + guard let settings = loadFileAsBytes(from: settingsPath) else { + fatalError("Failed to load settings file") + } + guard let srs = loadFileAsBytes(from: srsPath) else { + fatalError("Failed to load srs file") + } + + // Witness validation (should fail for network compiled) + let witnessValidationResult1 = try? witnessValidation(witness:compiledCircuit) + assert(witnessValidationResult1 == nil, "Witness validation should fail for network compiled") + + // Witness validation (should pass for witness) + let witnessValidationResult2 = try? witnessValidation(witness:witness) + assert(witnessValidationResult2 != nil, "Witness validation should pass for witness") + + // Compiled circuit validation (should fail for onnx network) + let circuitValidationResult1 = try? compiledCircuitValidation(compiledCircuit:network) + assert(circuitValidationResult1 == nil, "Compiled circuit validation should fail for onnx network") + + // Compiled circuit validation (should pass for compiled network) + let circuitValidationResult2 = try? compiledCircuitValidation(compiledCircuit:compiledCircuit) + assert(circuitValidationResult2 != nil, "Compiled circuit validation should pass for compiled network") + + // Input validation (should fail for witness) + let inputValidationResult1 = try? inputValidation(input:witness) + assert(inputValidationResult1 == nil, "Input validation should fail for witness") + + // Input validation (should pass for input) + let inputValidationResult2 = try? inputValidation(input:input) + assert(inputValidationResult2 != nil, "Input validation should pass for input") + + // Proof validation (should fail for witness) + let proofValidationResult1 = try? proofValidation(proof:witness) + assert(proofValidationResult1 == nil, "Proof validation should fail for witness") + + // Proof validation (should pass for proof) + let proofValidationResult2 = try? proofValidation(proof:proof) + assert(proofValidationResult2 != nil, "Proof validation should pass for proof") + + // Verifying key (vk) validation (should pass) + let vkValidationResult = try? vkValidation(vk:vk, settings:settings) + assert(vkValidationResult != nil, "VK validation should pass for vk") + + // Proving key (pk) validation (should pass) + let pkValidationResult = try? pkValidation(pk:pk, settings:settings) + assert(pkValidationResult != nil, "PK validation should pass for pk") + + // Settings validation (should fail for proof) + let settingsValidationResult1 = try? settingsValidation(settings:proof) + assert(settingsValidationResult1 == nil, "Settings validation should fail for proof") + + // Settings validation (should pass for settings) + let settingsValidationResult2 = try? settingsValidation(settings:settings) + assert(settingsValidationResult2 != nil, "Settings validation should pass for settings") + + // SRS validation (should pass) + let srsValidationResult = try? srsValidation(srs:srs) + assert(srsValidationResult != nil, "SRS validation should pass for srs") + +} \ No newline at end of file diff --git a/tests/ios_integration_tests.rs b/tests/ios_integration_tests.rs new file mode 100644 index 000000000..756851fbf --- /dev/null +++ b/tests/ios_integration_tests.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "ios-bindings-test")] +uniffi::build_foreign_language_testcases!( + "tests/ios/can_verify_aggr.swift", + "tests/ios/verify_gen_witness.swift", + "tests/ios/gen_pk_test.swift", + "tests/ios/gen_vk_test.swift", + "tests/ios/pk_is_valid_test.swift", + "tests/ios/verify_validations.swift", + // "tests/ios/verify_encode_verifier_calldata.swift", // TODO - the function requires rust dependencies to test + // "tests/ios/verify_kzg_commit.swift", // TODO - the function is not exported and requires rust dependencies to test +); diff --git a/tests/py_integration_tests.rs b/tests/py_integration_tests.rs index 70ea5433a..3d3dcb2e6 100644 --- a/tests/py_integration_tests.rs +++ b/tests/py_integration_tests.rs @@ -1,4 +1,4 @@ -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(feature = "ezkl", not(target_arch = "wasm32")))] #[cfg(test)] mod py_tests { @@ -123,7 +123,8 @@ mod py_tests { } } - const TESTS: [&str; 33] = [ + const TESTS: [&str; 34] = [ + "ezkl_demo_batch.ipynb", "proof_splitting.ipynb", // 0 "variance.ipynb", "mnist_gan.ipynb", diff --git a/tests/wasm.rs b/tests/wasm.rs index 3c1f9c1d8..f08a5ae06 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -1,6 +1,12 @@ #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] #[cfg(test)] mod wasm32 { + use ezkl::bindings::wasm::{ + bufferToVecOfFelt, compiledCircuitValidation, encodeVerifierCalldata, feltToBigEndian, + feltToFloat, feltToInt, feltToLittleEndian, genPk, genVk, genWitness, inputValidation, + kzgCommit, pkValidation, poseidonHash, proofValidation, prove, settingsValidation, + srsValidation, u8_array_to_u128_le, verify, verifyAggr, vkValidation, witnessValidation, + }; use ezkl::circuit::modules::polycommit::PolyCommitChip; use ezkl::circuit::modules::poseidon::spec::{PoseidonSpec, POSEIDON_RATE, POSEIDON_WIDTH}; use ezkl::circuit::modules::poseidon::PoseidonChip; @@ -9,12 +15,6 @@ mod wasm32 { use ezkl::graph::GraphCircuit; use ezkl::graph::{GraphSettings, GraphWitness}; use ezkl::pfsys; - use ezkl::wasm::{ - bufferToVecOfFelt, compiledCircuitValidation, encodeVerifierCalldata, feltToBigEndian, - feltToFloat, feltToInt, feltToLittleEndian, genPk, genVk, genWitness, inputValidation, - kzgCommit, pkValidation, poseidonHash, proofValidation, prove, settingsValidation, - srsValidation, u8_array_to_u128_le, verify, verifyAggr, vkValidation, witnessValidation, - }; use halo2_proofs::plonk::VerifyingKey; use halo2_proofs::poly::kzg::commitment::KZGCommitmentScheme; use halo2_proofs::poly::kzg::commitment::ParamsKZG; @@ -28,18 +28,18 @@ mod wasm32 { wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - pub const WITNESS: &[u8] = include_bytes!("../tests/wasm/witness.json"); - pub const NETWORK_COMPILED: &[u8] = include_bytes!("../tests/wasm/model.compiled"); - pub const NETWORK: &[u8] = include_bytes!("../tests/wasm/network.onnx"); - pub const INPUT: &[u8] = include_bytes!("../tests/wasm/input.json"); - pub const PROOF: &[u8] = include_bytes!("../tests/wasm/proof.json"); - pub const PROOF_AGGR: &[u8] = include_bytes!("../tests/wasm/proof_aggr.json"); - pub const SETTINGS: &[u8] = include_bytes!("../tests/wasm/settings.json"); - pub const PK: &[u8] = include_bytes!("../tests/wasm/pk.key"); - pub const VK: &[u8] = include_bytes!("../tests/wasm/vk.key"); - pub const VK_AGGR: &[u8] = include_bytes!("../tests/wasm/vk_aggr.key"); - pub const SRS: &[u8] = include_bytes!("../tests/wasm/kzg"); - pub const SRS1: &[u8] = include_bytes!("../tests/wasm/kzg1.srs"); + pub const WITNESS: &[u8] = include_bytes!("assets/witness.json"); + pub const NETWORK_COMPILED: &[u8] = include_bytes!("assets/model.compiled"); + pub const NETWORK: &[u8] = include_bytes!("assets/network.onnx"); + pub const INPUT: &[u8] = include_bytes!("assets/input.json"); + pub const PROOF: &[u8] = include_bytes!("assets/proof.json"); + pub const PROOF_AGGR: &[u8] = include_bytes!("assets/proof_aggr.json"); + pub const SETTINGS: &[u8] = include_bytes!("assets/settings.json"); + pub const PK: &[u8] = include_bytes!("assets/pk.key"); + pub const VK: &[u8] = include_bytes!("assets/vk.key"); + pub const VK_AGGR: &[u8] = include_bytes!("assets/vk_aggr.key"); + pub const SRS: &[u8] = include_bytes!("assets/kzg"); + pub const SRS1: &[u8] = include_bytes!("assets/kzg1.srs"); #[wasm_bindgen_test] async fn can_verify_aggr() { diff --git a/tests/wasm/proof.json b/tests/wasm/proof.json deleted file mode 100644 index 86740a513..000000000 --- a/tests/wasm/proof.json +++ /dev/null @@ -1 +0,0 @@ -{"protocol":null,"instances":[["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000000"]],"proof":[10,24,139,13,210,89,222,197,128,121,114,51,235,60,238,15,130,118,61,94,252,63,117,61,140,72,161,55,119,83,66,223,22,215,181,217,46,151,185,35,35,146,54,140,26,17,241,36,234,173,171,14,227,201,125,211,234,217,171,109,111,220,254,16,25,226,61,59,189,206,219,149,183,239,146,245,137,125,161,255,201,4,115,31,20,147,230,171,45,25,6,78,40,82,190,38,16,219,163,30,157,183,210,73,76,132,145,236,64,69,44,123,13,173,168,7,88,183,149,237,154,111,239,32,136,41,254,0,1,170,100,206,193,101,74,240,105,77,217,160,105,46,101,208,14,108,96,3,123,90,9,157,8,214,83,126,99,99,149,97,40,217,59,182,197,127,194,50,56,233,175,76,12,165,209,246,194,97,34,8,51,51,109,32,242,167,179,44,103,169,22,35,45,224,70,2,69,197,34,99,70,246,198,64,186,213,182,163,245,153,43,72,110,147,72,219,99,109,66,226,233,135,74,18,32,198,254,66,85,184,130,129,161,101,96,152,102,104,95,32,13,110,197,117,128,48,214,8,103,220,40,151,193,44,246,224,12,73,158,7,156,44,114,216,16,145,185,229,120,90,101,192,246,37,236,184,247,23,92,223,82,17,79,110,118,222,165,193,29,177,46,205,212,18,125,142,235,134,221,30,124,130,192,1,8,18,219,190,178,2,85,63,81,210,220,5,182,233,0,133,19,148,105,3,207,24,169,129,152,16,223,22,63,193,98,125,66,123,237,28,2,236,235,133,229,9,78,168,11,60,59,99,0,88,73,159,241,250,184,122,100,65,149,27,224,94,102,39,71,131,135,165,167,153,37,229,54,90,94,89,179,109,161,137,9,207,169,136,53,34,166,195,225,22,236,10,200,246,61,4,236,31,71,161,12,17,126,135,26,197,8,101,142,82,231,57,44,76,64,86,37,222,181,85,166,186,2,138,108,70,116,45,60,86,220,44,23,240,162,185,141,196,147,50,163,42,197,7,29,215,253,51,30,13,160,202,14,34,89,185,112,183,170,9,43,64,87,86,87,223,238,221,185,181,181,105,132,245,167,217,24,206,84,81,109,69,112,31,14,90,22,99,59,222,83,190,241,72,86,103,39,90,98,201,42,29,5,149,233,120,234,57,42,29,23,75,127,138,84,57,241,193,71,212,213,184,25,163,131,79,55,28,182,52,178,65,193,214,211,84,24,52,155,247,21,200,242,170,146,244,46,164,38,166,5,201,19,214,103,89,20,8,5,173,157,189,211,53,137,20,32,222,97,102,44,188,29,215,253,51,30,13,160,202,14,34,89,185,112,183,170,9,43,64,87,86,87,223,238,221,185,181,181,105,132,245,167,217,24,206,84,81,109,69,112,31,14,90,22,99,59,222,83,190,241,72,86,103,39,90,98,201,42,29,5,149,233,120,234,57,29,215,253,51,30,13,160,202,14,34,89,185,112,183,170,9,43,64,87,86,87,223,238,221,185,181,181,105,132,245,167,217,24,206,84,81,109,69,112,31,14,90,22,99,59,222,83,190,241,72,86,103,39,90,98,201,42,29,5,149,233,120,234,57,29,215,253,51,30,13,160,202,14,34,89,185,112,183,170,9,43,64,87,86,87,223,238,221,185,181,181,105,132,245,167,217,24,206,84,81,109,69,112,31,14,90,22,99,59,222,83,190,241,72,86,103,39,90,98,201,42,29,5,149,233,120,234,57,29,215,253,51,30,13,160,202,14,34,89,185,112,183,170,9,43,64,87,86,87,223,238,221,185,181,181,105,132,245,167,217,24,206,84,81,109,69,112,31,14,90,22,99,59,222,83,190,241,72,86,103,39,90,98,201,42,29,5,149,233,120,234,57,36,225,38,205,240,204,130,230,196,88,117,95,175,177,110,114,108,183,167,245,107,166,186,43,148,229,153,89,81,47,178,83,42,231,140,41,30,141,66,135,209,192,6,201,197,190,20,7,94,121,121,93,118,186,184,98,35,185,239,178,181,51,131,218,30,141,96,220,134,211,149,123,144,113,166,64,174,53,227,3,7,220,147,15,131,195,207,82,119,249,170,88,203,207,206,188,18,149,242,3,88,174,58,127,67,230,83,16,29,76,28,93,249,128,35,92,104,230,239,92,19,226,37,136,59,178,208,251,17,248,239,169,201,155,13,182,121,231,182,40,242,130,98,1,75,44,46,245,133,253,117,220,205,150,16,5,167,31,22,35,8,44,254,181,146,80,128,255,168,80,80,193,141,7,180,124,25,215,51,127,91,54,4,238,41,236,170,33,253,123,241,39,36,119,60,40,110,41,19,25,219,118,40,242,1,0,110,155,156,228,238,52,184,144,114,77,244,34,143,82,121,75,200,78,22,165,170,47,152,113,183,99,78,125,167,104,204,33,212,60,224,93,224,135,209,242,150,118,223,145,220,6,32,21,104,17,27,91,225,238,217,58,250,216,232,214,8,62,4,112,17,174,237,174,108,79,36,243,246,140,121,111,105,29,55,206,25,126,41,235,19,105,69,94,110,25,249,102,163,228,116,249,184,142,5,7,227,148,212,138,69,86,172,112,155,220,62,190,190,170,36,59,202,0,96,147,151,158,220,107,95,170,97,215,78,234,236,29,115,46,226,178,174,241,199,58,247,228,217,16,233,26,0,126,192,149,213,146,104,222,251,168,117,27,252,38,130,127,121,150,138,61,243,162,112,184,45,219,112,125,254,74,79,154,36,59,202,0,96,147,151,158,220,107,95,170,97,215,78,234,236,29,115,46,226,178,174,241,199,58,247,228,217,16,233,26,0,126,192,149,213,146,104,222,251,168,117,27,252,38,130,127,121,150,138,61,243,162,112,184,45,219,112,125,254,74,79,154,6,124,66,146,215,217,237,195,177,1,176,142,133,255,230,181,8,144,59,36,96,30,105,83,46,229,62,84,47,80,10,224,32,193,167,179,33,134,217,129,72,59,201,234,189,211,92,18,247,142,14,96,73,221,196,106,6,195,213,56,235,203,248,218,6,124,66,146,215,217,237,195,177,1,176,142,133,255,230,181,8,144,59,36,96,30,105,83,46,229,62,84,47,80,10,224,32,193,167,179,33,134,217,129,72,59,201,234,189,211,92,18,247,142,14,96,73,221,196,106,6,195,213,56,235,203,248,218,5,248,147,46,148,157,33,167,125,9,106,129,180,123,76,218,166,121,148,131,77,34,149,19,184,58,176,133,49,240,222,247,46,31,216,20,247,144,255,117,149,33,154,117,222,137,205,209,33,44,255,118,210,32,50,32,234,20,22,247,184,58,205,136,21,183,48,86,43,208,65,185,175,203,175,245,31,223,8,238,63,102,55,110,145,10,155,70,4,97,44,237,217,137,126,155,37,136,102,13,105,194,170,96,58,157,212,39,130,65,181,0,92,242,19,30,58,3,114,53,164,127,180,33,199,31,199,85,33,138,239,4,131,3,218,16,68,65,231,120,239,148,183,3,68,84,82,87,55,126,92,152,76,118,255,227,210,178,180,137,1,58,69,45,203,164,188,161,252,252,238,90,14,160,247,12,105,208,146,72,163,6,147,16,64,113,185,128,135,16,35,101,7,190,97,79,181,121,69,100,228,29,25,30,162,236,97,242,161,121,215,174,181,109,50,215,155,122,106,211,151,55,245,67,46,186,40,51,65,183,59,170,78,141,117,230,36,255,117,8,234,164,116,232,130,188,140,164,164,31,174,105,249,182,71,173,47,2,58,217,70,53,124,216,225,59,191,239,91,84,46,35,233,113,233,135,94,102,119,211,206,135,18,78,182,54,29,31,33,121,200,210,251,201,85,190,204,173,127,249,191,129,222,20,148,7,236,162,116,190,212,192,234,31,127,116,158,145,121,50,14,90,38,155,105,255,147,0,214,163,220,97,89,66,43,120,54,97,31,92,176,45,254,5,104,117,10,41,241,11,246,67,13,193,63,148,102,11,73,251,94,49,251,203,203,241,3,172,81,48,71,18,245,204,216,169,75,20,209,227,202,169,125,74,4,86,255,73,53,173,0,164,172,205,215,219,159,201,10,219,191,132,185,156,69,23,148,103,7,249,91,109,19,48,49,89,29,5,213,202,180,57,134,176,99,203,157,217,131,80,225,191,164,82,2,30,102,78,164,12,195,121,135,144,36,191,144,39,38,124,126,167,125,251,179,66,188,51,6,146,89,145,178,153,88,238,252,17,122,160,115,72,12,91,56,119,49,45,110,200,16,189,11,117,98,151,179,233,78,148,238,159,80,198,194,85,221,174,175,29,35,121,99,150,232,17,150,13,133,123,205,61,26,132,58,20,185,68,74,69,38,20,152,80,45,88,243,59,192,135,96,167,32,154,39,105,28,123,128,200,218,69,13,20,29,98,43,247,32,184,217,197,227,108,142,241,106,113,194,132,125,170,82,168,165,210,219,168,93,46,131,12,204,121,117,121,44,50,133,117,138,44,59,198,238,230,206,191,117,58,171,42,138,51,113,181,248,207,29,232,172,111,11,132,103,255,231,109,22,170,116,108,195,91,144,51,49,156,57,202,96,219,95,119,137,80,138,220,81,130,73,157,20,139,254,51,204,23,8,139,4,158,91,4,102,209,78,82,247,74,62,204,8,54,231,160,55,60,214,95,66,206,97,158,127,120,210,113,22,213,236,218,12,41,79,119,197,243,129,182,215,124,10,87,216,173,7,100,35,175,22,24,52,247,237,171,127,68,202,255,230,252,138,142,18,67,115,99,73,12,197,221,150,29,95,168,122,242,247,110,70,128,135,0,32,11,203,83,230,227,71,223,234,145,172,211,3,143,44,23,142,58,154,157,156,59,80,191,248,125,49,196,235,157,71,227,149,42,181,166,72,53,228,38,6,120,104,245,14,229,96,20,67,20,20,38,73,36,198,201,42,248,133,92,151,17,168,226,52,143,154,213,70,57,185,191,235,18,8,160,40,101,247,214,180,5,15,249,166,196,93,187,126,69,137,157,216,252,49,96,205,212,103,79,129,198,198,94,1,181,213,163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,38,191,137,133,143,30,17,9,249,125,254,184,167,219,122,72,8,35,194,117,71,242,138,236,188,124,225,183,247,248,34,119,17,68,35,92,141,54,201,237,190,229,164,127,74,82,205,20,132,207,3,85,23,52,231,68,74,194,7,52,43,173,179,58,17,68,35,92,141,54,201,237,190,229,164,127,74,82,205,20,132,207,3,85,23,52,231,68,74,194,7,52,43,173,179,58,48,69,26,145,162,100,51,146,40,222,245,182,153,120,222,187,105,150,193,204,202,132,99,242,64,238,189,32,162,63,137,122,10,146,230,155,214,213,9,106,208,98,120,229,7,107,40,213,28,175,77,158,156,212,176,164,129,221,92,125,28,25,105,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,206,24,84,17,164,29,223,0,93,127,124,242,210,94,76,90,23,122,216,49,98,233,67,139,25,17,185,255,215,245,206,33,89,240,41,66,69,199,50,248,114,96,72,185,109,45,11,136,218,182,245,178,23,179,45,16,9,26,212,220,145,254,130,11,4,92,127,19,149,83,24,140,136,161,123,232,246,147,64,100,45,95,71,241,203,181,178,189,209,163,3,247,185,242,41,13,126,136,135,70,128,61,249,145,241,242,158,229,113,155,223,29,237,142,33,199,74,212,149,48,69,33,204,167,171,50,67,16,29,174,187,70,231,180,86,221,170,56,239,186,166,79,31,249,0,147,45,69,95,145,47,60,66,24,121,2,33,219,47,9,170,64,49,31,140,92,30,193,129,235,167,50,22,208,35,237,241,85,121,146,133,198,111,120,16,187,182,147,29,213,22,42,228,42,136,36,69,226,136,101,216,120,176,215,96,184,236,43,220,238,197,171,199,151,209,246,94,194,9,19,93,225,23,22,65,2,161,233,202,98,232,140,89,1,173,150,40,185,153,157,12,63,154,137,123,107,27,52,142,24,144,213,228,230,147,7,40,123,184,230,199,123,109,146,41,251,65,54,71,30,136,93,143,177,101,184,110,155,145,89,45,72,194,133,60,203,248,25,238,210,133,6,47,29,28,70,203,166,189,10,89,27,191,52,6,51,127,22,88,226,56,29,140,235,184,184,38,47,161,35,180,182,7,110,128,108,138,30,255,137,2,164,101,198,120,179,197,156,138,143,252,108,95,237,26,231,73,112,121,86,30,47,115,8,180,40,148,169,108,25,212,211,132,208,16,94,162,181,33,137,152,88,34,193,159,90,245,34,4,49,143,90,155,0,233,24,35,194,160,148,2,241,21,253,2,62,59,202,92,45,210,17,233,233,191,213,93,130,85,241,64,184,37,160,103,29,140,255,103,101,123,34,217,200,7,106,162,153,111,86,166,158,212,121,25,136,234,218,229,112,74,100,209,85,113,117,118,9,166,100,250,131,191,144,81,142,11,151,211,231,238,20,170,193,149,121,18,181,42,104,37,134,208,225,56,36,234,171,148,34,7,125,69,119,216,9,145,250,245,227,22,113,2,106,105,83,217,226,153,215,57,16,59,155,33,101,93,31,155,51,254,11,165,63,255,209,69,114,211,205,71,22,82,192,76,242,53,34,132,62,186,159,23,193,241,9,218,81,171,96,167,131,189,34,17,243,207,243,165,116,7,50,155,168,36,140,19,238,119,29,193,90,58,255,244,132,210,102,157,137,190,159,224,187,100,31,255,158,11,190,129,13,254,130,16,151,10,186,1,81,47,15,138,140,114,0,56,157,255,223,143,234,156,2,201,155,41,41,246,50,58,136,202,128,166,44,186,126,199,178,232,29,158,151,203,140,242,23,42,18,29,242,24,244,79,110,10,157,212,8,5,174,31,203,51,237,63,18,183,134,55,135,66,20,49,191,254,37,48,201,156,198,33,38,48,196,191,237,77,76,97,33,198,162,233,124,233,232,247,70,120,190,244,236,152,139,97,210,182,23,168,115,47,37,157,6,14,9,17,75,148,134,180,18,19,92,56,41,172,146,87,88,220,221,65,142,230,44,243,178,236,154,17,1,167,32,100,91,163,82,239,226,156,229,234,33,230,176,231,131,159,136,212,36,232,231,210,12,75,113,109,52,155,159,220,252,122,6,33,52,235,159,52,164,32,189,73,40,196,85,162,45,117,145,113,171,111,209,17,60,123,226,133,40,2,117,135,208,49,157,246,14,255,177,6,58,164,98,183,41,246,50,58,136,202,128,166,44,186,126,199,178,232,29,158,151,203,140,242,23,42,18,29,242,24,244,79,110,10,157,212,33,230,176,231,131,159,136,212,36,232,231,210,12,75,113,109,52,155,159,220,252,122,6,33,52,235,159,52,164,32,189,73,40,196,85,162,45,117,145,113,171,111,209,17,60,123,226,133,40,2,117,135,208,49,157,246,14,255,177,6,58,164,98,183,41,246,50,58,136,202,128,166,44,186,126,199,178,232,29,158,151,203,140,242,23,42,18,29,242,24,244,79,110,10,157,212,8,86,192,54,184,126,39,70,181,140,35,202,54,43,79,212,134,119,69,89,61,191,53,33,168,191,183,123,96,175,165,181,23,82,220,3,156,67,112,73,107,77,57,137,23,65,46,79,93,208,15,45,143,148,163,245,37,23,0,84,194,229,90,171,41,246,50,58,136,202,128,166,44,186,126,199,178,232,29,158,151,203,140,242,23,42,18,29,242,24,244,79,110,10,157,212,8,86,192,54,184,126,39,70,181,140,35,202,54,43,79,212,134,119,69,89,61,191,53,33,168,191,183,123,96,175,165,181,23,82,220,3,156,67,112,73,107,77,57,137,23,65,46,79,93,208,15,45,143,148,163,245,37,23,0,84,194,229,90,171,41,246,50,58,136,202,128,166,44,186,126,199,178,232,29,158,151,203,140,242,23,42,18,29,242,24,244,79,110,10,157,212,11,42,239,207,100,225,135,65,215,166,61,34,85,233,61,97,129,146,139,44,21,178,235,128,146,92,6,117,104,146,202,30,24,151,62,245,151,6,139,5,154,206,74,207,154,224,96,184,17,241,93,249,103,68,228,109,40,54,207,136,111,241,63,97,37,213,216,60,19,109,99,130,24,39,194,217,181,255,175,210,105,178,118,146,226,134,167,85,194,7,9,230,2,3,176,220,2,95,167,62,219,167,12,145,158,230,196,96,186,59,69,9,186,223,54,118,230,252,75,23,42,0,26,248,17,64,6,159],"hex_proof":"0x0a188b0dd259dec580797233eb3cee0f82763d5efc3f753d8c48a137775342df16d7b5d92e97b9232392368c1a11f124eaadab0ee3c97dd3ead9ab6d6fdcfe1019e23d3bbdcedb95b7ef92f5897da1ffc904731f1493e6ab2d19064e2852be2610dba31e9db7d2494c8491ec40452c7b0dada80758b795ed9a6fef208829fe0001aa64cec1654af0694dd9a0692e65d00e6c60037b5a099d08d6537e6363956128d93bb6c57fc23238e9af4c0ca5d1f6c261220833336d20f2a7b32c67a916232de0460245c5226346f6c640bad5b6a3f5992b486e9348db636d42e2e9874a1220c6fe4255b88281a165609866685f200d6ec5758030d60867dc2897c12cf6e00c499e079c2c72d81091b9e5785a65c0f625ecb8f7175cdf52114f6e76dea5c11db12ecdd4127d8eeb86dd1e7c82c0010812dbbeb202553f51d2dc05b6e9008513946903cf18a9819810df163fc1627d427bed1c02eceb85e5094ea80b3c3b630058499ff1fab87a6441951be05e6627478387a5a79925e5365a5e59b36da18909cfa9883522a6c3e116ec0ac8f63d04ec1f47a10c117e871ac508658e52e7392c4c405625deb555a6ba028a6c46742d3c56dc2c17f0a2b98dc49332a32ac5071dd7fd331e0da0ca0e2259b970b7aa092b40575657dfeeddb9b5b56984f5a7d918ce54516d45701f0e5a16633bde53bef1485667275a62c92a1d0595e978ea392a1d174b7f8a5439f1c147d4d5b819a3834f371cb634b241c1d6d35418349bf715c8f2aa92f42ea426a605c913d66759140805ad9dbdd335891420de61662cbc1dd7fd331e0da0ca0e2259b970b7aa092b40575657dfeeddb9b5b56984f5a7d918ce54516d45701f0e5a16633bde53bef1485667275a62c92a1d0595e978ea391dd7fd331e0da0ca0e2259b970b7aa092b40575657dfeeddb9b5b56984f5a7d918ce54516d45701f0e5a16633bde53bef1485667275a62c92a1d0595e978ea391dd7fd331e0da0ca0e2259b970b7aa092b40575657dfeeddb9b5b56984f5a7d918ce54516d45701f0e5a16633bde53bef1485667275a62c92a1d0595e978ea391dd7fd331e0da0ca0e2259b970b7aa092b40575657dfeeddb9b5b56984f5a7d918ce54516d45701f0e5a16633bde53bef1485667275a62c92a1d0595e978ea3924e126cdf0cc82e6c458755fafb16e726cb7a7f56ba6ba2b94e59959512fb2532ae78c291e8d4287d1c006c9c5be14075e79795d76bab86223b9efb2b53383da1e8d60dc86d3957b9071a640ae35e30307dc930f83c3cf5277f9aa58cbcfcebc1295f20358ae3a7f43e653101d4c1c5df980235c68e6ef5c13e225883bb2d0fb11f8efa9c99b0db679e7b628f28262014b2c2ef585fd75dccd961005a71f1623082cfeb5925080ffa85050c18d07b47c19d7337f5b3604ee29ecaa21fd7bf12724773c286e291319db7628f201006e9b9ce4ee34b890724df4228f52794bc84e16a5aa2f9871b7634e7da768cc21d43ce05de087d1f29676df91dc06201568111b5be1eed93afad8e8d6083e047011aeedae6c4f24f3f68c796f691d37ce197e29eb1369455e6e19f966a3e474f9b88e0507e394d48a4556ac709bdc3ebebeaa243bca006093979edc6b5faa61d74eeaec1d732ee2b2aef1c73af7e4d910e91a007ec095d59268defba8751bfc26827f79968a3df3a270b82ddb707dfe4a4f9a243bca006093979edc6b5faa61d74eeaec1d732ee2b2aef1c73af7e4d910e91a007ec095d59268defba8751bfc26827f79968a3df3a270b82ddb707dfe4a4f9a067c4292d7d9edc3b101b08e85ffe6b508903b24601e69532ee53e542f500ae020c1a7b32186d981483bc9eabdd35c12f78e0e6049ddc46a06c3d538ebcbf8da067c4292d7d9edc3b101b08e85ffe6b508903b24601e69532ee53e542f500ae020c1a7b32186d981483bc9eabdd35c12f78e0e6049ddc46a06c3d538ebcbf8da05f8932e949d21a77d096a81b47b4cdaa67994834d229513b83ab08531f0def72e1fd814f790ff7595219a75de89cdd1212cff76d2203220ea1416f7b83acd8815b730562bd041b9afcbaff51fdf08ee3f66376e910a9b4604612cedd9897e9b2588660d69c2aa603a9dd4278241b5005cf2131e3a037235a47fb421c71fc755218aef048303da104441e778ef94b70344545257377e5c984c76ffe3d2b2b489013a452dcba4bca1fcfcee5a0ea0f70c69d09248a30693104071b9808710236507be614fb5794564e41d191ea2ec61f2a179d7aeb56d32d79b7a6ad39737f5432eba283341b73baa4e8d75e624ff7508eaa474e882bc8ca4a41fae69f9b647ad2f023ad946357cd8e13bbfef5b542e23e971e9875e6677d3ce87124eb6361d1f2179c8d2fbc955beccad7ff9bf81de149407eca274bed4c0ea1f7f749e9179320e5a269b69ff9300d6a3dc6159422b7836611f5cb02dfe0568750a29f10bf6430dc13f94660b49fb5e31fbcbcbf103ac51304712f5ccd8a94b14d1e3caa97d4a0456ff4935ad00a4accdd7db9fc90adbbf84b99c4517946707f95b6d133031591d05d5cab43986b063cb9dd98350e1bfa452021e664ea40cc379879024bf9027267c7ea77dfbb342bc3306925991b29958eefc117aa073480c5b3877312d6ec810bd0b756297b3e94e94ee9f50c6c255ddaeaf1d23796396e811960d857bcd3d1a843a14b9444a45261498502d58f33bc08760a7209a27691c7b80c8da450d141d622bf720b8d9c5e36c8ef16a71c2847daa52a8a5d2dba85d2e830ccc7975792c3285758a2c3bc6eee6cebf753aab2a8a3371b5f8cf1de8ac6f0b8467ffe76d16aa746cc35b9033319c39ca60db5f7789508adc5182499d148bfe33cc17088b049e5b0466d14e52f74a3ecc0836e7a0373cd65f42ce619e7f78d27116d5ecda0c294f77c5f381b6d77c0a57d8ad076423af161834f7edab7f44caffe6fc8a8e12437363490cc5dd961d5fa87af2f76e46808700200bcb53e6e347dfea91acd3038f2c178e3a9a9d9c3b50bff87d31c4eb9d47e3952ab5a64835e426067868f50ee56014431414264924c6c92af8855c9711a8e2348f9ad54639b9bfeb1208a02865f7d6b4050ff9a6c45dbb7e45899dd8fc3160cdd4674f81c6c65e01b5d5a3000000000000000000000000000000000000000000000000000000000000000026bf89858f1e1109f97dfeb8a7db7a480823c27547f28aecbc7ce1b7f7f822771144235c8d36c9edbee5a47f4a52cd1484cf03551734e7444ac207342badb33a1144235c8d36c9edbee5a47f4a52cd1484cf03551734e7444ac207342badb33a30451a91a264339228def5b69978debb6996c1ccca8463f240eebd20a23f897a0a92e69bd6d5096ad06278e5076b28d51caf4d9e9cd4b0a481dd5c7d1c196940000000000000000000000000000000000000000000000000000000000000000020ce185411a41ddf005d7f7cf2d25e4c5a177ad83162e9438b1911b9ffd7f5ce2159f0294245c732f8726048b96d2d0b88dab6f5b217b32d10091ad4dc91fe820b045c7f139553188c88a17be8f69340642d5f47f1cbb5b2bdd1a303f7b9f2290d7e888746803df991f1f29ee5719bdf1ded8e21c74ad495304521cca7ab3243101daebb46e7b456ddaa38efbaa64f1ff900932d455f912f3c4218790221db2f09aa40311f8c5c1ec181eba73216d023edf155799285c66f7810bbb6931dd5162ae42a882445e28865d878b0d760b8ec2bdceec5abc797d1f65ec209135de117164102a1e9ca62e88c5901ad9628b9999d0c3f9a897b6b1b348e1890d5e4e69307287bb8e6c77b6d9229fb4136471e885d8fb165b86e9b91592d48c2853ccbf819eed285062f1d1c46cba6bd0a591bbf3406337f1658e2381d8cebb8b8262fa123b4b6076e806c8a1eff8902a465c678b3c59c8a8ffc6c5fed1ae7497079561e2f7308b42894a96c19d4d384d0105ea2b52189985822c19f5af52204318f5a9b00e91823c2a09402f115fd023e3bca5c2dd211e9e9bfd55d8255f140b825a0671d8cff67657b22d9c8076aa2996f56a69ed4791988eadae5704a64d15571757609a664fa83bf90518e0b97d3e7ee14aac1957912b52a682586d0e13824eaab9422077d4577d80991faf5e31671026a6953d9e299d739103b9b21655d1f9b33fe0ba53fffd14572d3cd471652c04cf23522843eba9f17c1f109da51ab60a783bd2211f3cff3a57407329ba8248c13ee771dc15a3afff484d2669d89be9fe0bb641fff9e0bbe810dfe8210970aba01512f0f8a8c7200389dffdf8fea9c02c99b2929f6323a88ca80a62cba7ec7b2e81d9e97cb8cf2172a121df218f44f6e0a9dd40805ae1fcb33ed3f12b7863787421431bffe2530c99cc6212630c4bfed4d4c6121c6a2e97ce9e8f74678bef4ec988b61d2b617a8732f259d060e09114b9486b412135c3829ac925758dcdd418ee62cf3b2ec9a1101a720645ba352efe29ce5ea21e6b0e7839f88d424e8e7d20c4b716d349b9fdcfc7a062134eb9f34a420bd4928c455a22d759171ab6fd1113c7be28528027587d0319df60effb1063aa462b729f6323a88ca80a62cba7ec7b2e81d9e97cb8cf2172a121df218f44f6e0a9dd421e6b0e7839f88d424e8e7d20c4b716d349b9fdcfc7a062134eb9f34a420bd4928c455a22d759171ab6fd1113c7be28528027587d0319df60effb1063aa462b729f6323a88ca80a62cba7ec7b2e81d9e97cb8cf2172a121df218f44f6e0a9dd40856c036b87e2746b58c23ca362b4fd4867745593dbf3521a8bfb77b60afa5b51752dc039c4370496b4d398917412e4f5dd00f2d8f94a3f525170054c2e55aab29f6323a88ca80a62cba7ec7b2e81d9e97cb8cf2172a121df218f44f6e0a9dd40856c036b87e2746b58c23ca362b4fd4867745593dbf3521a8bfb77b60afa5b51752dc039c4370496b4d398917412e4f5dd00f2d8f94a3f525170054c2e55aab29f6323a88ca80a62cba7ec7b2e81d9e97cb8cf2172a121df218f44f6e0a9dd40b2aefcf64e18741d7a63d2255e93d6181928b2c15b2eb80925c06756892ca1e18973ef597068b059ace4acf9ae060b811f15df96744e46d2836cf886ff13f6125d5d83c136d63821827c2d9b5ffafd269b27692e286a755c20709e60203b0dc025fa73edba70c919ee6c460ba3b4509badf3676e6fc4b172a001af81140069f","transcript_type":"EVM","split":null,"pretty_public_inputs":{"rescaled_inputs":[],"inputs":[],"processed_inputs":[],"processed_params":[],"processed_outputs":[],"rescaled_outputs":[["0","0","0","0"]],"outputs":[["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000"]]},"timestamp":1726430885098,"commitment":"KZG"} \ No newline at end of file From 41d3233223a93be394e4efe832bafb70b446300a Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 28 Oct 2024 20:01:00 +0800 Subject: [PATCH 46/46] h2 sol verifier package update. --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 8c575c575..d1d2905f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2397,7 +2397,7 @@ dependencies = [ [[package]] name = "halo2_solidity_verifier" version = "0.1.0" -source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=vka-log#76b37db6e78558767362024f9770caa249e2b49d" +source = "git+https://github.com/alexander-camuto/halo2-solidity-verifier?branch=vka-log#c319e229ad677ee4c7d95bdae45c2958350cfd14" dependencies = [ "askama", "blake2b_simd",