diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index bb7bdf680..297005dd6 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -538,6 +538,12 @@ jobs: # # now dump the contents of the file into a file called kaggle.json # echo $KAGGLE_API_KEY > /home/ubuntu/.kaggle/kaggle.json # chmod 600 /home/ubuntu/.kaggle/kaggle.json + - name: Simple tutorial (public network and outputs) + run: source .env/bin/activate; cargo nextest run py_tests::tests::run_notebook_::tests_20_expects + - name: Simple tutorial (public inputs and outputs) + run: source .env/bin/activate; cargo nextest run py_tests::tests::run_notebook_::tests_19_expects + - name: Simple tutorial (all public) + run: source .env/bin/activate; cargo nextest run py_tests::tests::run_notebook_::tests_4_expects - name: NBEATS tutorial run: source .env/bin/activate; cargo nextest run py_tests::tests::nbeats_ - name: SVM @@ -572,8 +578,6 @@ jobs: run: source .env/bin/activate; cargo nextest run py_tests::tests::run_notebook_::tests_2_expects - name: Hashed tutorial run: source .env/bin/activate; cargo nextest run py_tests::tests::run_notebook_::tests_3_expects - - name: Simple tutorial - run: source .env/bin/activate; cargo nextest run py_tests::tests::run_notebook_::tests_4_expects - name: Data attestation tutorial run: source .env/bin/activate; cargo nextest run py_tests::tests::run_notebook_::tests_5_expects - name: Variance tutorial diff --git a/README.md b/README.md index b89440b76..31105c293 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,16 @@ EZKL > "I ran this publicly available neural network on some private data and it produced this output" +[![Notebook](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/zkonduit/ezkl/blob/main/examples/notebooks/simple_demo_public_network_output.ipynb) + > "I ran my private neural network on some public data and it produced this output" +[![Notebook](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/zkonduit/ezkl/blob/main/examples/notebooks/simple_demo_public_input_output.ipynb) + > "I correctly ran this publicly available neural network on some public data and it produced this output" +[![Notebook](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/zkonduit/ezkl/blob/main/examples/notebooks/simple_demo_all_public.ipynb) + In the backend we use [Halo2](https://github.com/privacy-scaling-explorations/halo2) as a proof system. The generated proofs can then be used on-chain to verify computation, only the Ethereum Virtual Machine (EVM) is supported at the moment. diff --git a/examples/notebooks/simple_demo_all_public.ipynb b/examples/notebooks/simple_demo_all_public.ipynb new file mode 100644 index 000000000..84f1bddd3 --- /dev/null +++ b/examples/notebooks/simple_demo_all_public.ipynb @@ -0,0 +1,290 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "cf69bb3f-94e6-4dba-92cd-ce08df117d67", + "metadata": {}, + "source": [ + "## EZKL Jupyter Notebook Demo \n", + "\n", + "Here we demonstrate the use of the EZKL package in a Jupyter notebook whereby all components of the circuit are public or pre-committed to. This is the simplest case of using EZKL (proof of computation)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95613ee9", + "metadata": {}, + "outputs": [], + "source": [ + "# check if notebook is in colab\n", + "try:\n", + " # install ezkl\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", + "\n", + "# here we create and (potentially train a model)\n", + "\n", + "# make sure you have the dependencies required here already installed\n", + "from torch import nn\n", + "import ezkl\n", + "import os\n", + "import json\n", + "import torch\n", + "\n", + "\n", + "# Defines the model\n", + "# we got convs, we got relu, we got linear layers\n", + "# What else could one want ????\n", + "\n", + "class MyModel(nn.Module):\n", + " def __init__(self):\n", + " super(MyModel, self).__init__()\n", + "\n", + " self.conv1 = nn.Conv2d(in_channels=1, out_channels=2, kernel_size=5, stride=2)\n", + " self.conv2 = nn.Conv2d(in_channels=2, out_channels=3, kernel_size=5, stride=2)\n", + "\n", + " self.relu = nn.ReLU()\n", + "\n", + " self.d1 = nn.Linear(48, 48)\n", + " self.d2 = nn.Linear(48, 10)\n", + "\n", + " def forward(self, x):\n", + " # 32x1x28x28 => 32x32x26x26\n", + " x = self.conv1(x)\n", + " x = self.relu(x)\n", + " x = self.conv2(x)\n", + " x = self.relu(x)\n", + "\n", + " # flatten => 32 x (32*26*26)\n", + " x = x.flatten(start_dim = 1)\n", + "\n", + " # 32 x (32*26*26) => 32x128\n", + " x = self.d1(x)\n", + " x = self.relu(x)\n", + "\n", + " # logits => 32x10\n", + " logits = self.d2(x)\n", + "\n", + " return logits\n", + "\n", + "\n", + "circuit = MyModel()\n", + "\n", + "# Train the model as you like here (skipped for brevity)\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b37637c4", + "metadata": {}, + "outputs": [], + "source": [ + "model_path = os.path.join('network.onnx')\n", + "compiled_model_path = os.path.join('network.compiled')\n", + "pk_path = os.path.join('test.pk')\n", + "vk_path = os.path.join('test.vk')\n", + "settings_path = os.path.join('settings.json')\n", + "srs_path = os.path.join('kzg.srs')\n", + "witness_path = os.path.join('witness.json')\n", + "data_path = os.path.join('input.json')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82db373a", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "# After training, export to onnx (network.onnx) and create a data file (input.json)\n", + "x = 0.1*torch.rand(1,*[1, 28, 28], requires_grad=True)\n", + "\n", + "# Flips the neural net into inference mode\n", + "circuit.eval()\n", + "\n", + " # Export the model\n", + "torch.onnx.export(circuit, # 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", + "data = dict(input_data = [data_array])\n", + "\n", + " # Serialize data into file:\n", + "json.dump( data, open(data_path, 'w' ))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d5e374a2", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "py_run_args = ezkl.PyRunArgs()\n", + "py_run_args.input_visibility = \"public\"\n", + "py_run_args.output_visibility = \"public\"\n", + "py_run_args.param_visibility = \"fixed\" # \"fixed\" for params means that the committed to params are used for all proofs\n", + "\n", + "res = ezkl.gen_settings(model_path, settings_path, py_run_args=py_run_args)\n", + "assert res == True\n", + "\n", + "res = await ezkl.calibrate_settings(data_path, model_path, settings_path, \"resources\")\n", + "assert res == True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3aa4f090", + "metadata": {}, + "outputs": [], + "source": [ + "res = ezkl.compile_circuit(model_path, compiled_model_path, settings_path)\n", + "assert res == True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b74dcee", + "metadata": {}, + "outputs": [], + "source": [ + "# srs path\n", + "res = ezkl.get_srs(srs_path, settings_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18c8b7c7", + "metadata": {}, + "outputs": [], + "source": [ + "# now generate the witness file \n", + "\n", + "res = ezkl.gen_witness(data_path, compiled_model_path, witness_path)\n", + "assert os.path.isfile(witness_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1c561a8", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# HERE WE SETUP THE CIRCUIT PARAMS\n", + "# WE GOT KEYS\n", + "# WE GOT CIRCUIT PARAMETERS\n", + "# EVERYTHING ANYONE HAS EVER NEEDED FOR ZK\n", + "\n", + "\n", + "\n", + "res = ezkl.setup(\n", + " compiled_model_path,\n", + " vk_path,\n", + " pk_path,\n", + " srs_path,\n", + " )\n", + "\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)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c384cbc8", + "metadata": {}, + "outputs": [], + "source": [ + "# GENERATE A PROOF\n", + "\n", + "\n", + "proof_path = os.path.join('test.pf')\n", + "\n", + "res = ezkl.prove(\n", + " witness_path,\n", + " compiled_model_path,\n", + " pk_path,\n", + " proof_path,\n", + " srs_path,\n", + " \"single\",\n", + " )\n", + "\n", + "print(res)\n", + "assert os.path.isfile(proof_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76f00d41", + "metadata": {}, + "outputs": [], + "source": [ + "# VERIFY IT\n", + "\n", + "res = ezkl.verify(\n", + " proof_path,\n", + " settings_path,\n", + " vk_path,\n", + " srs_path,\n", + " )\n", + "\n", + "assert res == True\n", + "print(\"verified\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/notebooks/simple_demo.ipynb b/examples/notebooks/simple_demo_public_input_output.ipynb similarity index 93% rename from examples/notebooks/simple_demo.ipynb rename to examples/notebooks/simple_demo_public_input_output.ipynb index 1872bf1aa..64bc691bc 100644 --- a/examples/notebooks/simple_demo.ipynb +++ b/examples/notebooks/simple_demo_public_input_output.ipynb @@ -6,7 +6,9 @@ "id": "cf69bb3f-94e6-4dba-92cd-ce08df117d67", "metadata": {}, "source": [ - "## EZKL Jupyter Notebook Demo \n" + "## EZKL Jupyter Notebook Demo \n", + "\n", + "Here we demonstrate how to use the EZKL package to run a private network on public data to produce a public output.\n" ] }, { @@ -142,9 +144,13 @@ "metadata": {}, "outputs": [], "source": [ - "!RUST_LOG=trace\n", - "# TODO: Dictionary outputs\n", - "res = ezkl.gen_settings(model_path, settings_path)\n", + "py_run_args = ezkl.PyRunArgs()\n", + "py_run_args.input_visibility = \"public\"\n", + "py_run_args.output_visibility = \"public\"\n", + "py_run_args.param_visibility = \"private\" # private by default\n", + "\n", + "res = ezkl.gen_settings(model_path, settings_path, py_run_args=py_run_args)\n", + "\n", "assert res == True\n", "\n", "res = await ezkl.calibrate_settings(data_path, model_path, settings_path, \"resources\")\n", @@ -276,7 +282,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.9.15" } }, "nbformat": 4, diff --git a/examples/notebooks/simple_demo_public_network_output.ipynb b/examples/notebooks/simple_demo_public_network_output.ipynb new file mode 100644 index 000000000..268f50a2e --- /dev/null +++ b/examples/notebooks/simple_demo_public_network_output.ipynb @@ -0,0 +1,290 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "cf69bb3f-94e6-4dba-92cd-ce08df117d67", + "metadata": {}, + "source": [ + "## EZKL Jupyter Notebook Demo \n", + "\n", + "Here we demonstrate how to use the EZKL package to run a publicly known / committted to network on some private data, producing a public output.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95613ee9", + "metadata": {}, + "outputs": [], + "source": [ + "# check if notebook is in colab\n", + "try:\n", + " # install ezkl\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", + "\n", + "# here we create and (potentially train a model)\n", + "\n", + "# make sure you have the dependencies required here already installed\n", + "from torch import nn\n", + "import ezkl\n", + "import os\n", + "import json\n", + "import torch\n", + "\n", + "\n", + "# Defines the model\n", + "# we got convs, we got relu, we got linear layers\n", + "# What else could one want ????\n", + "\n", + "class MyModel(nn.Module):\n", + " def __init__(self):\n", + " super(MyModel, self).__init__()\n", + "\n", + " self.conv1 = nn.Conv2d(in_channels=1, out_channels=2, kernel_size=5, stride=2)\n", + " self.conv2 = nn.Conv2d(in_channels=2, out_channels=3, kernel_size=5, stride=2)\n", + "\n", + " self.relu = nn.ReLU()\n", + "\n", + " self.d1 = nn.Linear(48, 48)\n", + " self.d2 = nn.Linear(48, 10)\n", + "\n", + " def forward(self, x):\n", + " # 32x1x28x28 => 32x32x26x26\n", + " x = self.conv1(x)\n", + " x = self.relu(x)\n", + " x = self.conv2(x)\n", + " x = self.relu(x)\n", + "\n", + " # flatten => 32 x (32*26*26)\n", + " x = x.flatten(start_dim = 1)\n", + "\n", + " # 32 x (32*26*26) => 32x128\n", + " x = self.d1(x)\n", + " x = self.relu(x)\n", + "\n", + " # logits => 32x10\n", + " logits = self.d2(x)\n", + "\n", + " return logits\n", + "\n", + "\n", + "circuit = MyModel()\n", + "\n", + "# Train the model as you like here (skipped for brevity)\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b37637c4", + "metadata": {}, + "outputs": [], + "source": [ + "model_path = os.path.join('network.onnx')\n", + "compiled_model_path = os.path.join('network.compiled')\n", + "pk_path = os.path.join('test.pk')\n", + "vk_path = os.path.join('test.vk')\n", + "settings_path = os.path.join('settings.json')\n", + "srs_path = os.path.join('kzg.srs')\n", + "witness_path = os.path.join('witness.json')\n", + "data_path = os.path.join('input.json')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82db373a", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "# After training, export to onnx (network.onnx) and create a data file (input.json)\n", + "x = 0.1*torch.rand(1,*[1, 28, 28], requires_grad=True)\n", + "\n", + "# Flips the neural net into inference mode\n", + "circuit.eval()\n", + "\n", + " # Export the model\n", + "torch.onnx.export(circuit, # 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", + "data = dict(input_data = [data_array])\n", + "\n", + " # Serialize data into file:\n", + "json.dump( data, open(data_path, 'w' ))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d5e374a2", + "metadata": {}, + "outputs": [], + "source": [ + "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", + "\n", + "assert res == True\n", + "\n", + "res = await ezkl.calibrate_settings(data_path, model_path, settings_path, \"resources\")\n", + "assert res == True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3aa4f090", + "metadata": {}, + "outputs": [], + "source": [ + "res = ezkl.compile_circuit(model_path, compiled_model_path, settings_path)\n", + "assert res == True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b74dcee", + "metadata": {}, + "outputs": [], + "source": [ + "# srs path\n", + "res = ezkl.get_srs(srs_path, settings_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18c8b7c7", + "metadata": {}, + "outputs": [], + "source": [ + "# now generate the witness file \n", + "\n", + "res = ezkl.gen_witness(data_path, compiled_model_path, witness_path)\n", + "assert os.path.isfile(witness_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1c561a8", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# HERE WE SETUP THE CIRCUIT PARAMS\n", + "# WE GOT KEYS\n", + "# WE GOT CIRCUIT PARAMETERS\n", + "# EVERYTHING ANYONE HAS EVER NEEDED FOR ZK\n", + "\n", + "\n", + "\n", + "res = ezkl.setup(\n", + " compiled_model_path,\n", + " vk_path,\n", + " pk_path,\n", + " srs_path,\n", + " )\n", + "\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)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c384cbc8", + "metadata": {}, + "outputs": [], + "source": [ + "# GENERATE A PROOF\n", + "\n", + "\n", + "proof_path = os.path.join('test.pf')\n", + "\n", + "res = ezkl.prove(\n", + " witness_path,\n", + " compiled_model_path,\n", + " pk_path,\n", + " proof_path,\n", + " srs_path,\n", + " \"single\",\n", + " )\n", + "\n", + "print(res)\n", + "assert os.path.isfile(proof_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76f00d41", + "metadata": {}, + "outputs": [], + "source": [ + "# VERIFY IT\n", + "\n", + "res = ezkl.verify(\n", + " proof_path,\n", + " settings_path,\n", + " vk_path,\n", + " srs_path,\n", + " )\n", + "\n", + "assert res == True\n", + "print(\"verified\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/py_integration_tests.rs b/tests/py_integration_tests.rs index 766085465..5d36f29c4 100644 --- a/tests/py_integration_tests.rs +++ b/tests/py_integration_tests.rs @@ -115,13 +115,13 @@ mod py_tests { } } - const TESTS: [&str; 19] = [ + const TESTS: [&str; 21] = [ "mnist_gan.ipynb", // "mnist_vae.ipynb", "keras_simple_demo.ipynb", "encrypted_vis.ipynb", "hashed_vis.ipynb", - "simple_demo.ipynb", + "simple_demo_all_public.ipynb", "data_attest.ipynb", "variance.ipynb", "mean_postgres.ipynb", @@ -136,6 +136,8 @@ mod py_tests { "xgboost.ipynb", "lightgbm.ipynb", "svm.ipynb", + "simple_demo_public_input_output.ipynb", + "simple_demo_public_network_output.ipynb", ]; macro_rules! test_func { @@ -148,7 +150,7 @@ mod py_tests { use super::*; - seq!(N in 0..=18 { + seq!(N in 0..=20 { #(#[test_case(TESTS[N])])* fn run_notebook_(test: &str) {