diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6199bf1da..3d219e524 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -248,8 +248,6 @@ jobs: run: cargo nextest run --release --verbose tests::mock_kzg_output_::t --test-threads 32 - name: kzg inputs + params + outputs run: cargo nextest run --release --verbose tests::mock_kzg_all_::t --test-threads 32 - - name: encrypted inputs + kzg params + hashed output - run: cargo nextest run --release --verbose tests::mock_hashed_output_kzg_params_::t --test-threads 32 - name: Mock fixed inputs run: cargo nextest run --release --verbose tests::mock_fixed_inputs_ --test-threads 32 - name: Mock fixed outputs @@ -266,18 +264,6 @@ jobs: run: cargo nextest run --release --verbose tests::mock_hashed_all_::t --test-threads 32 - name: hashed inputs + fixed params run: cargo nextest run --release --verbose tests::mock_hashed_output_fixed_params_::t --test-threads 32 - - name: encrypted inputs - run: cargo nextest run --release --verbose tests::mock_encrypted_input_::t --test-threads 32 - - name: encrypted params - run: cargo nextest run --release --verbose tests::mock_encrypted_params_::t --test-threads 32 - - name: encrypted outputs - run: cargo nextest run --release --verbose tests::mock_encrypted_output_::t --test-threads 32 - - name: encrypted inputs + params - run: cargo nextest run --release --verbose tests::mock_encrypted_input_params_::t --test-threads 32 - - name: encrypted inputs + params + outputs - run: cargo nextest run --release --verbose tests::mock_encrypted_all_::t --test-threads 32 - - name: encrypted inputs + hashed params - run: cargo nextest run --release --verbose tests::mock_encrypted_input_hashed_params_::t --test-threads 32 - name: MNIST Gan Mock run: cargo nextest run --release --verbose tests::large_mock_::large_tests_4_expects -- --include-ignored - name: NanoGPT Mock @@ -419,8 +405,6 @@ jobs: run: cargo nextest run --release --verbose tests::kzg_prove_and_verify_fixed_params - name: KZG prove and verify tests (hashed outputs) run: cargo nextest run --release --verbose tests::kzg_prove_and_verify_hashed - - name: KZG prove and verify tests (encrypted outputs) - run: cargo nextest run --release --verbose tests::kzg_prove_and_verify_encrypted prove-and-verify-tests-gpu: runs-on: GPU @@ -456,8 +440,6 @@ jobs: run: cargo nextest run --release --verbose tests::kzg_prove_and_verify_fixed_params --features icicle --test-threads 2 - name: KZG prove and verify tests (hashed outputs) run: cargo nextest run --release --verbose tests::kzg_prove_and_verify_hashed --features icicle --test-threads 2 - - name: KZG prove and verify tests (encrypted outputs) - run: cargo nextest run --release --verbose tests::kzg_prove_and_verify_encrypted --features icicle --test-threads 2 fuzz-tests: runs-on: ubuntu-latest-32-cores @@ -554,8 +536,6 @@ jobs: run: cargo install --git https://github.com/foundry-rs/foundry --rev 95a93cd397f25f3f8d49d2851eb52bc2d52dd983 --profile local --locked anvil --force - name: KZG prove and verify aggr tests run: cargo nextest run --release --verbose tests_evm::kzg_evm_aggr_prove_and_verify_::t --test-threads 4 -- --include-ignored - - name: KZG prove and verify aggr tests (encrypted input) - run: cargo nextest run --release --verbose tests_evm::kzg_evm_aggr_prove_and_verify_encrypted --test-threads 4 -- --include-ignored examples: runs-on: ubuntu-latest-32-cores @@ -688,9 +668,8 @@ jobs: # - name: Postgres tutorials # run: source .env/bin/activate; cargo nextest run py_tests::tests::postgres_ --test-threads 1 - name: All notebooks - run: source .env/bin/activate; cargo nextest run py_tests::tests::run_notebook_ --test-threads 1 + run: source .env/bin/activate; cargo nextest run py_tests::tests::run_notebook_ --test-threads 1 - name: NBEATS tutorial run: source .env/bin/activate; cargo nextest run py_tests::tests::nbeats_ - name: Voice tutorial run: source .env/bin/activate; cargo nextest run py_tests::tests::voice_ - diff --git a/Cargo.toml b/Cargo.toml index ec15f3c63..325e81d5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,11 +110,6 @@ harness = false name = "poseidon" harness = false -[[bench]] -name = "elgamal" -harness = false - - [[bench]] name = "accum_einsum_matmul" harness = false diff --git a/benches/elgamal.rs b/benches/elgamal.rs deleted file mode 100644 index 8e2199dbd..000000000 --- a/benches/elgamal.rs +++ /dev/null @@ -1,119 +0,0 @@ -use ark_std::test_rng; -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; -use ezkl::circuit::modules::elgamal::{ElGamalConfig, ElGamalGadget, ElGamalVariables}; -use ezkl::circuit::modules::Module; -use ezkl::circuit::*; -use ezkl::pfsys::create_keys; -use ezkl::pfsys::create_proof_circuit_kzg; -use ezkl::pfsys::srs::gen_srs; -use ezkl::pfsys::TranscriptType; -use ezkl::tensor::*; -use halo2_proofs::circuit::Value; -use halo2_proofs::poly::kzg::commitment::KZGCommitmentScheme; -use halo2_proofs::poly::kzg::strategy::SingleStrategy; -use halo2_proofs::{ - arithmetic::Field, - circuit::{Layouter, SimpleFloorPlanner}, - plonk::{Circuit, ConstraintSystem, Error}, -}; -use halo2curves::bn256::{Bn256, Fr}; -use rand::rngs::OsRng; - -#[derive(Clone, Debug)] -struct EncryptytionCircuit { - message: ValTensor, - variables: ElGamalVariables, -} - -impl Circuit for EncryptytionCircuit { - type Config = ElGamalConfig; - type FloorPlanner = SimpleFloorPlanner; - type Params = (); - - fn without_witnesses(&self) -> Self { - self.clone() - } - - fn configure(cs: &mut ConstraintSystem) -> Self::Config { - ElGamalGadget::configure(cs, ()) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let mut chip = ElGamalGadget::new(config); - chip.load_variables(self.variables.clone()); - let sk: Tensor> = - Tensor::new(Some(&[Value::known(self.variables.sk).into()]), &[1]).unwrap(); - chip.layout(&mut layouter, &[self.message.clone(), sk.into()], 0)?; - Ok(()) - } -} - -fn runelgamal(c: &mut Criterion) { - let mut group = c.benchmark_group("elgamal"); - - for size in [784, 2352, 12288].iter() { - let mut rng = test_rng(); - - let k = (ElGamalGadget::num_rows(*size) as f32).log2().ceil() as u32; - let params = gen_srs::>(k); - - let var = ElGamalVariables::gen_random(&mut rng); - - let message = (0..*size).map(|_| Fr::random(OsRng)).collect::>(); - - let run_inputs = (message.clone(), var.clone()); - let public_inputs: Vec> = ElGamalGadget::run(run_inputs).unwrap(); - - let mut message: Tensor> = - message.into_iter().map(|m| Value::known(m).into()).into(); - message.reshape(&[1, *size]).unwrap(); - - let circuit = EncryptytionCircuit { - message: message.into(), - variables: var, - }; - - group.throughput(Throughput::Elements(*size as u64)); - group.bench_with_input(BenchmarkId::new("pk", size), &size, |b, &_| { - b.iter(|| { - create_keys::, Fr, EncryptytionCircuit>( - &circuit, ¶ms, - ) - .unwrap(); - }); - }); - - let pk = - create_keys::, Fr, EncryptytionCircuit>(&circuit, ¶ms) - .unwrap(); - - group.throughput(Throughput::Elements(*size as u64)); - group.bench_with_input(BenchmarkId::new("prove", size), &size, |b, &_| { - b.iter(|| { - let prover = create_proof_circuit_kzg( - circuit.clone(), - ¶ms, - Some(public_inputs[0].clone()), - &pk, - TranscriptType::EVM, - SingleStrategy::new(¶ms), - CheckMode::UNSAFE, - None, - ); - prover.unwrap(); - }); - }); - } - group.finish(); -} - -criterion_group! { - name = benches; - config = Criterion::default().with_plots().sample_size(10); - targets = runelgamal -} -criterion_main!(benches); diff --git a/examples/notebooks/encrypted_vis.ipynb b/examples/notebooks/encrypted_vis.ipynb deleted file mode 100644 index 8327ac5b9..000000000 --- a/examples/notebooks/encrypted_vis.ipynb +++ /dev/null @@ -1,537 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# encrypted-ezkl\n", - "\n", - "Here's an example leveraging EZKL whereby the inputs to the model are encrypted using el-gamal encryption (then hashed). \n", - "\n", - "In this setup:\n", - "- the hashes of the enecrypted values are known to the prover and verifier\n", - "- the hashes serve as \"public inputs\" (a.k.a instances) to the circuit along with the \n", - "- the encrypted values can be safely shared between the prover and verifier, without the verifier learning the unencrypted values\n", - "- We leave the outputs of the model as public as well (known to the verifier and prover).\n", - "\n", - "Summary of el gamal: \n", - "\n", - "### 1. Key generation\n", - "\n", - "We use the G1 group of bn256 of order $q$ and generator $g$\n", - "Choose an integer x randomly from $\\{ 1 , … , q − 1 \\}$\n", - "Compute $h:=g^x$\n", - "$x$ is the private key and $h$ + group information form the public key. \n", - "\n", - "### 2. Encryption \n", - "\n", - "We can encrypt a message using the public key: \n", - "Choose an integer y randomly from $\\{ 1 , … , q − 1 \\}$\n", - "Compute ${\\displaystyle s:=h^{y}}$. This is called the shared secret.\n", - "Compute ${\\displaystyle c_{1}:=g^{y}}$.\n", - "Compute ${\\displaystyle c_{2}:=m + poseidon(s.x, s.y)}$. \n", - "We use `poseidon` hashing to reduce the (effectively) two dimensional coordinates of $s$ ($s.x$, $s.y$) in G1 to a single field element and we also go from G1Affine to the native field of the SNARK `Fr`(scalar field of G1), as the message is also in `Fr`\n", - "\n", - "The ciphertext is the pair $(c_1, c_2)$. \n", - "\n", - "### 3. Decryption \n", - "\n", - "Decryption then is as follows: \n", - "\n", - "Compute ${\\displaystyle s:=c_{1}^{x}}$. Since ${\\displaystyle c_{1}=g^{y}}$, ${\\displaystyle c_{1}^{x}=g^{xy}=h^{y}}$. \n", - "Now Compute ${\\displaystyle m:=c_2 - poseidon(s.x, s.y)}$.\n", - "\n", - "**Other perspective**: s, is as a [one-time pad](https://en.wikipedia.org/wiki/One-time_pad) for encrypting the message\n", - "\n", - "### 4. Elements of the Circuit\n", - "\n", - "1. The private key x is publicly committed using a poseidon hash. The circuit verifies that the prover knows a private key that corresponds to this publicly committed to hash. This assumes that the prover has generated both the public and private keys and is planning to send the decryption key to another user. \n", - "\n", - "2. Verifies that given \n", - " - public inputs $poseidon(c_2), c_1, poseidon(x)$\n", - " - private inputs $m, x$\n", - "-> $x$ is indeed the decryption key\n", - "-> m encrypted using $s$ is indeed $c_2$ \n", - "-> $c_1$ was properly derived\n", - "\n", - "\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First we import the necessary dependencies and set up logging to be as informative as possible. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "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", - "from torch import nn\n", - "import ezkl\n", - "import os\n", - "import json\n", - "import logging\n", - "\n", - "# uncomment for more descriptive logging \n", - "# FORMAT = '%(levelname)s %(name)s %(asctime)-15s %(filename)s:%(lineno)d %(message)s'\n", - "# logging.basicConfig(format=FORMAT)\n", - "# logging.getLogger().setLevel(logging.DEBUG)\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we define our model. It is a humble model with but a conv layer and a $ReLU$ non-linearity, but it is a model nonetheless" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import torch\n", - "# Defines the model\n", - "# we got convs, we got relu, \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=3, out_channels=1, kernel_size=5, stride=4)\n", - " self.relu = nn.ReLU()\n", - "\n", - " def forward(self, x):\n", - " x = self.conv1(x)\n", - " x = self.relu(x)\n", - "\n", - " return x\n", - "\n", - "\n", - "circuit = MyModel()\n", - "\n", - "# this is where you'd train your model\n", - "\n", - "\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We omit training for purposes of this demonstration. We've marked where training would happen in the cell above. \n", - "Now we export the model to onnx and create a corresponding (randomly generated) input file.\n", - "\n", - "You can replace the random `x` with real data if you so wish. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x = torch.rand(1,*[3, 8, 8], 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", - " \"network.onnx\", # 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(\"input.json\", 'w' ))\n", - "\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is where the magic happens. We define our `PyRunArgs` objects which contains the visibility parameters for out model. \n", - "- `input_visibility` defines the visibility of the model inputs\n", - "- `param_visibility` defines the visibility of the model weights and constants and parameters \n", - "- `output_visibility` defines the visibility of the model outputs\n", - "\n", - "There are currently 6 visibility settings:\n", - "- `public`: known to both the verifier and prover (a subtle nuance is that this may not be the case for model parameters but until we have more rigorous theoretical results we don't want to make strong claims as to this). \n", - "- `private`: known only to the prover\n", - "- `fixed`: known to the prover and verifier (as a commit), but not modifiable by the prover.\n", - "- `hashed`: the hash pre-image is known to the prover, the prover and verifier know the hash. The prover proves that the they know the pre-image to the hash. \n", - "- `encrypted`: the non-encrypted element and the secret key used for decryption are known to the prover. The prover and the verifier know the encrypted element, the public key used to encrypt, and the hash of the decryption hey. The prover proves that they know the pre-image of the hashed decryption key and that this key can in fact decrypt the encrypted message.\n", - "- `kzgcommit`: unblinded advice column which generates a kzg commitment. This doesn't appear in the instances of the circuit and must instead be inserted directly within the proof bytes. \n", - "\n", - "Here we create the following setup:\n", - "- `input_visibility`: \"encrypted\"\n", - "- `param_visibility`: \"public\"\n", - "- `output_visibility`: public\n", - "\n", - "We encourage you to play around with other setups :) \n", - "\n", - "Shoutouts: \n", - "\n", - "- [summa-solvency](https://github.com/summa-dev/summa-solvency) for their help with the poseidon hashing chip. \n", - "- [timeofey](https://github.com/timoftime) for providing inspiration in our developement of the el-gamal encryption circuit in Halo2. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import ezkl\n", - "\n", - "model_path = os.path.join('network.onnx')\n", - "compiled_model_path = os.path.join('network.compiled')\n", - "\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", - "\n", - "data_path = os.path.join('input.json')\n", - "\n", - "run_args = ezkl.PyRunArgs()\n", - "run_args.input_visibility = \"encrypted\"\n", - "run_args.param_visibility = \"fixed\"\n", - "run_args.output_visibility = \"public\"\n", - "run_args.variables = [(\"batch_size\", 1)]\n", - "\n", - "\n", - "\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we generate a settings file. This file basically instantiates a bunch of parameters that determine their circuit shape, size etc... Because of the way we represent nonlinearities in the circuit (using Halo2's [lookup tables](https://zcash.github.io/halo2/design/proving-system/lookup.html)), it is often best to _calibrate_ this settings file as some data can fall out of range of these lookups.\n", - "\n", - "You can pass a dataset for calibration that will be representative of real inputs you might find if and when you deploy the prover. Here we create a dummy calibration dataset for demonstration purposes. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!RUST_LOG=trace\n", - "# TODO: Dictionary outputs\n", - "res = ezkl.gen_settings(model_path, settings_path, py_run_args=run_args)\n", - "assert res == True" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# generate a bunch of dummy calibration data\n", - "cal_data = {\n", - " \"input_data\": [torch.cat((x, torch.rand(10, *[3, 8, 8]))).flatten().tolist()],\n", - "}\n", - "\n", - "cal_path = os.path.join('val_data.json')\n", - "# save as json file\n", - "with open(cal_path, \"w\") as f:\n", - " json.dump(cal_data, f)\n", - "\n", - "res = ezkl.calibrate_settings(cal_path, model_path, settings_path, \"resources\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "res = ezkl.compile_circuit(model_path, compiled_model_path, settings_path)\n", - "assert res == True" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As we use Halo2 with KZG-commitments we need an SRS string from (preferably) a multi-party trusted setup ceremony. For an overview of the procedures for such a ceremony check out [this page](https://blog.ethereum.org/2023/01/16/announcing-kzg-ceremony). The `get_srs` command retrieves a correctly sized SRS given the calibrated settings file from [here](https://github.com/han0110/halo2-kzg-srs). \n", - "\n", - "These SRS were generated with [this](https://github.com/privacy-scaling-explorations/perpetualpowersoftau) ceremony. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "res = ezkl.get_srs( settings_path)\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now need to generate the (partial) circuit witness. These are the model outputs (and any hashes) that are generated when feeding the previously generated `input.json` through the circuit / model. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!export RUST_BACKTRACE=1\n", - "\n", - "witness_path = \"witness.json\"\n", - "\n", - "res = ezkl.gen_witness(data_path, compiled_model_path, witness_path)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As a sanity check you can \"mock prove\" (i.e check that all the constraints of the circuit match without generate a full proof). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "\n", - "res = ezkl.mock(witness_path, compiled_model_path)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here we setup verifying and proving keys for the circuit. As the name suggests the proving key is needed for ... proving and the verifying key is needed for ... verifying. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# HERE WE SETUP THE CIRCUIT PARAMS\n", - "# WE GOT KEYS\n", - "# WE GOT CIRCUIT PARAMETERS\n", - "# EVERYTHING ANYONE HAS EVER NEEDED FOR ZK\n", - "res = ezkl.setup(\n", - " compiled_model_path,\n", - " vk_path,\n", - " pk_path,\n", - " \n", - " )\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)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we generate a full proof. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# GENERATE A PROOF\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", - " \n", - " \"single\",\n", - " )\n", - "\n", - "\n", - "print(res)\n", - "assert os.path.isfile(proof_path)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And verify it as a sanity check. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# VERIFY IT\n", - "\n", - "res = ezkl.verify(\n", - " proof_path,\n", - " settings_path,\n", - " vk_path,\n", - " \n", - " )\n", - "\n", - "assert res == True\n", - "print(\"verified\")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now create an EVM / `.sol` verifier that can be deployed on chain to verify submitted proofs using a view function." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "abi_path = 'test.abi'\n", - "sol_code_path = 'test.sol'\n", - "\n", - "res = ezkl.create_evm_verifier(\n", - " vk_path,\n", - " \n", - " settings_path,\n", - " sol_code_path,\n", - " abi_path,\n", - " )\n", - "assert res == True\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Verify on the evm" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Make sure anvil is running locally first\n", - "# run with $ anvil -p 3030\n", - "# we use the default anvil node here\n", - "import json\n", - "\n", - "address_path = os.path.join(\"address.json\")\n", - "\n", - "ezkl.deploy_evm(\n", - " address_path,\n", - " sol_code_path,\n", - " 'http://127.0.0.1:3030')\n", - "\n", - "with open(address_path, 'r') as file:\n", - " addr = file.read().rstrip()\n", - "\n", - "res = ezkl.verify_evm(\n", - " addr,\n", - " proof_path,\n", - " \"http://127.0.0.1:3030\"\n", - ")\n", - "assert res == True\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "ezkl", - "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" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/src/circuit/modules/elgamal.rs b/src/circuit/modules/elgamal.rs deleted file mode 100644 index c93c1a1df..000000000 --- a/src/circuit/modules/elgamal.rs +++ /dev/null @@ -1,881 +0,0 @@ -/* -An easy-to-use implementation of the ElGamal Encryption in the form of a Halo2 Chip. -Huge thank you to https://github.com/timoftime/ for providing the inspiration and launching point for this <3 . -*/ - -/// -mod add_chip; - -use crate::circuit::modules::poseidon::spec::PoseidonSpec; -use crate::tensor::{Tensor, ValTensor, ValType}; -use add_chip::{AddChip, AddConfig, AddInstruction}; -use ark_std::rand::{CryptoRng, RngCore}; -use halo2_proofs::arithmetic::Field; -use halo2_proofs::circuit::{AssignedCell, Chip, Layouter, Value}; -use halo2_proofs::plonk; -use halo2_proofs::plonk::{Advice, Column, ConstraintSystem, Error, Instance}; -use halo2_wrong_ecc::integer::rns::{Common, Integer, Rns}; -use halo2_wrong_ecc::maingate::{ - MainGate, MainGateConfig, RangeChip, RangeConfig, RangeInstructions, RegionCtx, -}; -use halo2_wrong_ecc::{AssignedPoint, BaseFieldEccChip, EccConfig}; -use halo2curves::bn256::{Fq, Fr, G1Affine, G1}; -use halo2curves::group::cofactor::CofactorCurveAffine; -use halo2curves::group::{Curve, Group}; -use halo2curves::CurveAffine; -use serde::{Deserialize, Serialize}; -use std::ops::{Mul, MulAssign}; -use std::rc::Rc; -use std::vec; - -use super::poseidon::{PoseidonChip, PoseidonConfig}; -use super::Module; - -// Absolute offsets for public inputs. -const C1_X: usize = 0; -const C1_Y: usize = 1; -const SK_H: usize = 2; -const C2_H: usize = 3; - -/// -const NUMBER_OF_LIMBS: usize = 4; -const BIT_LEN_LIMB: usize = 64; -/// The number of instance columns used by the ElGamal circuit. -pub const NUM_INSTANCE_COLUMNS: usize = 1; - -/// The poseidon hash width. -pub const POSEIDON_WIDTH: usize = 2; -/// The poseidon hash rate. -pub const POSEIDON_RATE: usize = 1; -/// The poseidon len -pub const POSEIDON_LEN: usize = 2; - -#[derive(Debug)] -/// A chip implementing ElGamal encryption. -pub struct ElGamalChip { - /// The configuration for this chip. - pub config: ElGamalConfig, - /// The ECC chip. - ecc: BaseFieldEccChip, - /// The Poseidon hash chip. - poseidon: PoseidonChip, - /// The addition chip. - add: AddChip, -} - -#[derive(Debug, Clone)] -/// Configuration for the ElGamal chip. -pub struct ElGamalConfig { - main_gate_config: MainGateConfig, - range_config: RangeConfig, - poseidon_config: PoseidonConfig, - add_config: AddConfig, - plaintext_col: Column, - /// The column used for the instance. - pub instance: Column, - /// The config has been initialized. - pub initialized: bool, -} - -impl ElGamalConfig { - fn config_range(&self, layouter: &mut impl Layouter) -> Result<(), Error> { - let range_chip = RangeChip::::new(self.range_config.clone()); - range_chip.load_table(layouter)?; - Ok(()) - } - - fn ecc_chip_config(&self) -> EccConfig { - EccConfig::new(self.range_config.clone(), self.main_gate_config.clone()) - } -} - -impl Chip for ElGamalChip { - type Config = ElGamalConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} - -impl ElGamalChip { - /// Create a new `ElGamalChip`. - pub fn new(p: ElGamalConfig) -> ElGamalChip { - ElGamalChip { - ecc: BaseFieldEccChip::new(p.ecc_chip_config()), - poseidon: PoseidonChip::new(p.poseidon_config.clone()), - add: AddChip::construct(p.add_config.clone()), - config: p, - } - } - - /// Configure the chip. - fn configure(meta: &mut ConstraintSystem) -> ElGamalConfig { - let main_gate_config = MainGate::::configure(meta); - let advices = main_gate_config.advices(); - let main_fixed_columns = main_gate_config.fixed(); - let instance = main_gate_config.instance(); - - let rc_a = main_fixed_columns[3..5].try_into().unwrap(); - let rc_b = [meta.fixed_column(), meta.fixed_column()]; - - meta.enable_constant(rc_b[0]); - - let rns = Rns::::construct(); - - let overflow_bit_lens = rns.overflow_lengths(); - let composition_bit_lens = vec![BIT_LEN_LIMB / NUMBER_OF_LIMBS]; - - let range_config = RangeChip::::configure( - meta, - &main_gate_config, - composition_bit_lens, - overflow_bit_lens, - ); - - let poseidon_config = - PoseidonChip::::configure_with_cols( - meta, - advices[0], - rc_a, - rc_b, - advices[1..3].try_into().unwrap(), - None, - ); - - let add_config = AddChip::configure(meta, advices[0], advices[1], advices[2]); - - let plaintext_col = advices[1]; - - ElGamalConfig { - poseidon_config, - main_gate_config, - range_config, - add_config, - plaintext_col, - instance, - initialized: false, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -/// The variables used in the ElGamal circuit. -pub struct ElGamalVariables { - /// The randomness used in the encryption. - pub r: Fr, - /// The public key. - pub pk: G1Affine, - /// The secret key. - pub sk: Fr, - /// The window size used in the ECC chip. - pub window_size: usize, - /// The auxiliary generator used in the ECC chip. - pub aux_generator: G1Affine, -} - -impl Default for ElGamalVariables { - fn default() -> Self { - Self { - r: Fr::zero(), - pk: G1Affine::identity(), - sk: Fr::zero(), - window_size: 4, - aux_generator: G1Affine::identity(), - } - } -} - -impl ElGamalVariables { - /// Create new variables. - pub fn new(r: Fr, pk: G1Affine, sk: Fr, window_size: usize, aux_generator: G1Affine) -> Self { - Self { - r, - pk, - sk, - window_size, - aux_generator, - } - } - - /// Generate random variables. - pub fn gen_random(mut rng: &mut R) -> Self { - // get a random element from the scalar field - let sk = Fr::random(&mut rng); - - // compute secret_key*generator to derive the public key - // With BN256, we create the private key from a random number. This is a private key value (sk - // and a public key mapped to the G2 curve:: pk=sk.G2 - let mut pk = G1::generator(); - pk.mul_assign(sk); - - Self { - r: Fr::random(&mut rng), - pk: pk.to_affine(), - sk, - window_size: 4, - aux_generator: ::CurveExt::random(rng).to_affine(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -/// The cipher returned from the ElGamal encryption. -pub struct ElGamalCipher { - /// c1 := r*G - pub c1: G1, - /// c2 := m*s - pub c2: Vec, -} - -#[derive(Debug, Clone)] -/// A gadget implementing ElGamal encryption. -pub struct ElGamalGadget { - /// The configuration for this gadget. - pub config: ElGamalConfig, - /// The variables used in this gadget. - variables: Option, -} - -impl ElGamalGadget { - /// Load the variables into the gadget. - pub fn load_variables(&mut self, variables: ElGamalVariables) { - self.variables = Some(variables); - } - - fn rns() -> Rc> { - let rns = Rns::::construct(); - Rc::new(rns) - } - - /// Encrypt a message using the public key. - pub fn encrypt(pk: G1Affine, msg: Vec, r: Fr) -> ElGamalCipher { - let g = G1Affine::generator(); - let c1 = g.mul(&r); - - let coords = pk.mul(&r).to_affine().coordinates().unwrap(); - - let x = Integer::from_fe(*coords.x(), Self::rns()); - let y = Integer::from_fe(*coords.y(), Self::rns()); - - let dh = PoseidonChip::::run( - [x.native(), y.native()].to_vec(), - ) - .unwrap()[0][0]; - - let mut c2 = vec![]; - - for m in &msg { - c2.push(m + dh); - } - - ElGamalCipher { c1, c2 } - } - - /// Hash the msssage to be used as a public input. - pub fn hash_encrypted_msg(msg: Vec) -> Fr { - PoseidonChip::::run(msg).unwrap() - [0][0] - } - - /// Hash the secret key to be used as a public input. - pub fn hash_sk(sk: Fr) -> Fr { - PoseidonChip::::run(vec![sk, sk]) - .unwrap()[0][0] - } - - /// Decrypt a ciphertext using the secret key. - pub fn decrypt(cipher: &ElGamalCipher, sk: Fr) -> Vec { - let c1 = cipher.c1; - let c2 = cipher.c2.clone(); - - let s = c1.mul(sk).to_affine().coordinates().unwrap(); - - let x = Integer::from_fe(*s.x(), Self::rns()); - let y = Integer::from_fe(*s.y(), Self::rns()); - - let dh = PoseidonChip::::run( - [x.native(), y.native()].to_vec(), - ) - .unwrap()[0][0]; - - let mut msg = vec![]; - for encrypted_m in &c2 { - msg.push(encrypted_m - dh); - } - - msg - } - - /// Get the public inputs for the circuit. - pub fn get_instances(cipher: &ElGamalCipher, sk_hash: Fr) -> Vec> { - let mut c1_and_sk = cipher - .c1 - .to_affine() - .coordinates() - .map(|c| { - let x = Integer::from_fe(*c.x(), Self::rns()); - let y = Integer::from_fe(*c.y(), Self::rns()); - - vec![x.native(), y.native()] - }) - .unwrap(); - - c1_and_sk.push(sk_hash); - - c1_and_sk.push(Self::hash_encrypted_msg(cipher.c2.clone())); - - vec![c1_and_sk] - } - - pub(crate) fn verify_encrypted_msg_hash( - &self, - mut layouter: impl Layouter, - config: &ElGamalConfig, - encrypted_msg: &[AssignedCell], - ) -> Result, plonk::Error> { - let chip = ElGamalChip::new(config.clone()); - - // compute dh = poseidon_hash(randomness*pk) - let encrypted_msg_hash = { - let poseidon_message = - Tensor::from(encrypted_msg.iter().map(|m| ValType::from(m.clone()))); - - chip.poseidon.layout( - &mut layouter.namespace(|| "Poseidon hash (encrypted_msg)"), - &[poseidon_message.into()], - 0, - )? - }; - - match &encrypted_msg_hash - .get_inner_tensor() - .map_err(|_| plonk::Error::Synthesis)?[0] - { - ValType::PrevAssigned(v) => Ok(v.clone()), - _ => { - log::error!("poseidon hash should be an assigned value"); - Err(plonk::Error::Synthesis) - } - } - } - - /// Hash the secret key to be used as a public input. - pub(crate) fn verify_sk_hash( - &self, - mut layouter: impl Layouter, - config: &ElGamalConfig, - sk: &AssignedCell, - ) -> Result, plonk::Error> { - let chip = ElGamalChip::new(config.clone()); - - // compute dh = poseidon_hash(randomness*pk) - let sk_hash = { - let poseidon_message = - Tensor::from([ValType::from(sk.clone()), ValType::from(sk.clone())].into_iter()); - - chip.poseidon.layout( - &mut layouter.namespace(|| "Poseidon hash (sk)"), - &[poseidon_message.into()], - 0, - )? - }; - - let sk_hash = match &sk_hash - .get_inner_tensor() - .map_err(|_| plonk::Error::Synthesis)?[0] - { - ValType::PrevAssigned(v) => v.clone(), - _ => { - log::error!("poseidon hash should be an assigned value"); - return Err(plonk::Error::Synthesis); - } - }; - - Ok(sk_hash) - } - - pub(crate) fn verify_secret( - &self, - mut layouter: impl Layouter, - config: &ElGamalConfig, - sk: &AssignedCell, - ) -> Result<[AssignedPoint; 2], plonk::Error> { - let mut chip = ElGamalChip::new(config.clone()); - - let g = G1Affine::generator(); - - let variables = match self.variables { - Some(ref variables) => variables, - None => { - log::error!("variables not loaded"); - return Err(plonk::Error::Synthesis); - } - }; - - // compute s = randomness*pk - let s = variables.pk.mul(variables.r).to_affine(); - let c1 = g.mul(variables.r).to_affine(); - - layouter.assign_region( - || "obtain_s", - |region| { - let offset = 0; - let ctx = &mut RegionCtx::new(region, offset); - - chip.ecc - .assign_aux_generator(ctx, Value::known(variables.aux_generator))?; - chip.ecc.assign_aux(ctx, variables.window_size, 1)?; - - let s = chip.ecc.assign_point(ctx, Value::known(s)).unwrap(); - // compute c1 = randomness*generator - let c1 = chip.ecc.assign_point(ctx, Value::known(c1)).unwrap(); - - let s_from_sk = chip.ecc.mul(ctx, &c1, sk, variables.window_size).unwrap(); - - chip.ecc.assert_equal(ctx, &s, &s_from_sk)?; - - Ok([s, c1]) - }, - ) - } - - pub(crate) fn verify_encryption( - &self, - mut layouter: impl Layouter, - config: &ElGamalConfig, - m: &AssignedCell, - s: &AssignedPoint, - ) -> Result, plonk::Error> { - let chip = ElGamalChip::new(config.clone()); - - // compute dh = poseidon_hash(randomness*pk) - let dh = { - let poseidon_message = Tensor::from( - [ - ValType::from(s.x().native().clone()), - ValType::from(s.y().native().clone()), - ] - .into_iter(), - ); - - chip.poseidon.layout( - &mut layouter.namespace(|| "Poseidon hasher"), - &[poseidon_message.into()], - 0, - )? - }; - - let dh = match &dh.get_inner_tensor().map_err(|_| plonk::Error::Synthesis)?[0] { - ValType::PrevAssigned(v) => v.clone(), - _ => { - log::error!("poseidon hash should be an assigned value"); - return Err(plonk::Error::Synthesis); - } - }; - - // compute c2 = poseidon_hash(nk, rho) + psi. - let c2 = chip.add.add( - layouter.namespace(|| "c2 = poseidon_hash(randomness*pk) + m"), - &dh, - m, - )?; - - Ok(c2) - } -} - -impl Module for ElGamalGadget { - type Config = ElGamalConfig; - type InputAssignments = (Vec>, AssignedCell); - type RunInputs = (Vec, ElGamalVariables); - type Params = (); - - fn new(config: Self::Config) -> Self { - Self { - config, - variables: None, - } - } - - fn configure(meta: &mut ConstraintSystem, _: Self::Params) -> Self::Config { - ElGamalChip::configure(meta) - } - - fn name(&self) -> &'static str { - "ElGamal" - } - - fn instance_increment_input(&self) -> Vec { - // in order - // 1. c1, sk_hash, c2_hash - vec![4] - } - - fn run(input: Self::RunInputs) -> Result>, Box> { - let start_time = instant::Instant::now(); - - let (input, var) = input; - let len = input.len(); - - let cipher = Self::encrypt(var.pk, input, var.r); - // keep 1 empty (maingate instance variable). - let mut public_inputs: Vec> = vec![]; - public_inputs.extend(Self::get_instances(&cipher, Self::hash_sk(var.sk))); - - log::trace!("run (N={:?}) took: {:?}", len, start_time.elapsed()); - - Ok(public_inputs) - } - - fn layout_inputs( - &self, - layouter: &mut impl Layouter, - inputs: &[ValTensor], - ) -> Result { - assert_eq!(inputs.len(), 2); - let message = inputs[0].clone(); - let sk = inputs[1].clone(); - - let start_time = instant::Instant::now(); - let (msg_var, sk_var) = layouter.assign_region( - || "plaintext", - |mut region| { - let msg_var: Result>, Error> = match &message { - ValTensor::Value { inner: v, .. } => v - .iter() - .enumerate() - .map(|(i, value)| match value { - ValType::Value(v) => region.assign_advice( - || format!("load message_{}", i), - self.config.plaintext_col, - i, - || *v, - ), - ValType::PrevAssigned(v) | ValType::AssignedConstant(v, ..) => { - Ok(v.clone()) - } - ValType::Constant(f) => region.assign_advice_from_constant( - || format!("load message_{}", i), - self.config.plaintext_col, - i, - *f, - ), - e => { - log::error!("wrong input type: {:?}", e); - Err(Error::Synthesis) - } - }) - .collect(), - ValTensor::Instance { - dims, - inner: col, - idx, - initial_offset, - .. - } => { - // this should never ever fail - let num_elems = dims[*idx].iter().product::(); - (0..num_elems) - .map(|i| { - region.assign_advice_from_instance( - || "pub input anchor", - *col, - initial_offset + i, - self.config.plaintext_col, - i, - ) - }) - .collect() - } - }; - - let sk = match sk.get_inner_tensor().unwrap()[0] { - ValType::Value(v) => v, - _ => { - log::error!("wrong input type"); - return Err(Error::Synthesis); - } - }; - - let msg_var = msg_var?; - - let sk_var = region.assign_advice( - || "sk", - self.config.plaintext_col, - msg_var.len(), - || sk, - )?; - - Ok((msg_var, sk_var)) - }, - )?; - let duration = start_time.elapsed(); - log::trace!("layout inputs took: {:?}", duration); - - Ok((msg_var, sk_var)) - } - - fn layout( - &self, - layouter: &mut impl Layouter, - inputs: &[ValTensor], - row_offset: usize, - ) -> Result, Error> { - let start_time = instant::Instant::now(); - - // if all equivalent to 0, then we are in the first row of the circuit - if !self.config.initialized { - self.config.config_range(layouter).unwrap(); - } - - let (msg_var, sk_var) = self.layout_inputs(layouter, inputs)?; - - let [s, c1] = self.verify_secret( - layouter.namespace(|| "verify_secret"), - &self.config, - &sk_var, - )?; - - // Force the public input to be the hash of the secret key so that we can ascertain decryption can happen - let sk_hash = self.verify_sk_hash( - layouter.namespace(|| "verify_sk_hash"), - &self.config, - &sk_var, - )?; - - layouter - .constrain_instance( - c1.x().native().cell(), - self.config.instance, - C1_X + row_offset, - ) - .and(layouter.constrain_instance( - c1.y().native().cell(), - self.config.instance, - C1_Y + row_offset, - )) - .and(layouter.constrain_instance( - sk_hash.cell(), - self.config.instance, - SK_H + row_offset, - ))?; - - let c2: Result>, _> = msg_var - .iter() - .map(|m| { - self.verify_encryption( - layouter.namespace(|| "verify_encryption"), - &self.config, - m, - &s, - ) - }) - .collect(); - - let c2 = c2?; - - let c2_hash = self.verify_encrypted_msg_hash( - layouter.namespace(|| "verify_c2_hash"), - &self.config, - &c2, - )?; - - layouter.constrain_instance(c2_hash.cell(), self.config.instance, C2_H + row_offset)?; - - let mut assigned_input: Tensor> = - msg_var.iter().map(|e| ValType::from(e.clone())).into(); - - assigned_input.reshape(inputs[0].dims()).map_err(|e| { - log::error!("reshape failed: {:?}", e); - Error::Synthesis - })?; - - log::trace!( - "layout (N={:?}) took: {:?}", - msg_var.len(), - start_time.elapsed() - ); - - Ok(assigned_input.into()) - } - - fn num_rows(input_len: usize) -> usize { - // this was determined by running the circuit and looking at the number of constraints - // in the test called hash_for_a_range_of_input_sizes, then regressing in python to find the slope - // ```python - // import numpy as np - // x = [1, 2, 3, 512, 513, 514] - // y = [75424, 75592, 75840, 161017, 161913, 162000] - // def fit_above(x, y) : - // x0, y0 = x[0] - 1, y[0] - // x -= x0 - // y -= y0 - // def error_function_2(b, x, y) : - // a = np.min((y - b) / x) - // return np.sum((y - a * x - b)**2) - // b = scipy.optimize.minimize(error_function_2, [0], args=(x, y)).x[0] - // a = np.max((y - b) / x) - // return a, b - a * x0 + y0 - // a, b = fit_above(x, y) - // plt.plot(x, y, 'o') - // plt.plot(x, a*x + b, '-') - // plt.show() - // for (x_i, y_i) in zip(x,y): - // assert y_i <= a*x_i + b - // print(a, b) - // ``` - const NUM_CONSTRAINTS_SLOPE: usize = 196; - const NUM_CONSTRAINTS_INTERCEPT: usize = 75257; - - // check if even or odd - input_len * NUM_CONSTRAINTS_SLOPE + NUM_CONSTRAINTS_INTERCEPT - } -} - -#[cfg(test)] -mod tests { - use crate::circuit::modules::ModulePlanner; - - use super::*; - use ark_std::test_rng; - use halo2_proofs::{dev::MockProver, plonk::Circuit}; - - struct EncryptionCircuit { - message: ValTensor, - variables: ElGamalVariables, - } - - impl Circuit for EncryptionCircuit { - type Config = ElGamalConfig; - type FloorPlanner = ModulePlanner; - type Params = (); - - fn without_witnesses(&self) -> Self { - let empty_val: Vec> = vec![Value::::unknown().into()]; - let message: Tensor> = empty_val.into_iter().into(); - - let variables = ElGamalVariables::default(); - - Self { - message: message.into(), - variables, - } - } - - fn configure(meta: &mut ConstraintSystem) -> ElGamalConfig { - ElGamalGadget::configure(meta, ()) - } - - fn synthesize( - &self, - config: ElGamalConfig, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let mut chip = ElGamalGadget::new(config); - chip.load_variables(self.variables.clone()); - let sk: Tensor> = - Tensor::new(Some(&[Value::known(self.variables.sk).into()]), &[1]).unwrap(); - chip.layout(&mut layouter, &[self.message.clone(), sk.into()], 0)?; - Ok(()) - } - } - - #[test] - // this is for backwards compatibility with the old format - fn test_variables_serialization_round_trip() { - let mut rng = test_rng(); - - let var = ElGamalVariables::gen_random(&mut rng); - - let mut buf = vec![]; - serde_json::to_writer(&mut buf, &var).unwrap(); - - let var2 = serde_json::from_reader(&buf[..]).unwrap(); - - assert_eq!(var, var2); - } - - #[test] - pub fn test_encrypt_decrypt() { - let mut rng = test_rng(); - - let var = ElGamalVariables::gen_random(&mut rng); - - let mut msg = vec![]; - // - for _ in 0..32 { - msg.push(Fr::random(&mut rng)); - } - - let cipher = ElGamalGadget::encrypt(var.pk, msg.clone(), var.r); - - let decrypted_msg = ElGamalGadget::decrypt(&cipher, var.sk); - - assert_eq!(decrypted_msg, msg); - } - - #[test] - pub fn test_circuit() { - let mut rng = test_rng(); - - let var = ElGamalVariables::gen_random(&mut rng); - - let mut msg = vec![]; - // - for _ in 0..2 { - msg.push(Fr::random(&mut rng)); - } - - let run_inputs = (msg.clone(), var.clone()); - let public_inputs: Vec> = ElGamalGadget::run(run_inputs).unwrap(); - - let message: Tensor> = msg.into_iter().map(|m| Value::known(m).into()).into(); - - let circuit = EncryptionCircuit { - message: message.into(), - variables: var, - }; - - let res = MockProver::run(17, &circuit, public_inputs).unwrap(); - res.assert_satisfied_par(); - } - - #[test] - #[ignore] - pub fn test_circuit_range_of_input_sizes() { - let mut rng = test_rng(); - - #[cfg(not(target_arch = "wasm32"))] - env_logger::init(); - - // - for i in [1, 2, 3, 512, 513, 514, 1024] { - println!("i is {} ----------------------------------------", i); - - let var = ElGamalVariables::gen_random(&mut rng); - let mut msg = vec![]; - for _ in 0..i { - msg.push(Fr::random(&mut rng)); - } - - let run_inputs = (msg.clone(), var.clone()); - let public_inputs: Vec> = ElGamalGadget::run(run_inputs).unwrap(); - - let message: Tensor> = - msg.into_iter().map(|m| Value::known(m).into()).into(); - - let circuit = EncryptionCircuit { - message: message.into(), - variables: var, - }; - - let res = MockProver::run(19, &circuit, public_inputs).unwrap(); - res.assert_satisfied_par(); - } - } -} diff --git a/src/circuit/modules/elgamal/add_chip.rs b/src/circuit/modules/elgamal/add_chip.rs deleted file mode 100644 index ef777cb1f..000000000 --- a/src/circuit/modules/elgamal/add_chip.rs +++ /dev/null @@ -1,92 +0,0 @@ -use halo2_proofs::{ - circuit::{AssignedCell, Chip, Layouter}, - plonk::{self, Advice, Column, ConstraintSystem, Constraints, Selector}, - poly::Rotation, -}; -use halo2curves::bn256::Fr; -use halo2curves::ff::PrimeField; - -/// An instruction set for adding two circuit words (field elements). -pub(crate) trait AddInstruction: Chip { - /// Constraints `a + b` and returns the sum. - fn add( - &self, - layouter: impl Layouter, - a: &AssignedCell, - b: &AssignedCell, - ) -> Result, plonk::Error>; -} - -#[derive(Clone, Debug)] -pub struct AddConfig { - a: Column, - b: Column, - c: Column, - q_add: Selector, -} - -/// A chip implementing a single addition constraint `c = a + b` on a single row. -#[derive(Clone, Debug)] -pub struct AddChip { - config: AddConfig, -} - -impl Chip for AddChip { - type Config = AddConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} - -impl AddChip { - pub fn configure( - meta: &mut ConstraintSystem, - a: Column, - b: Column, - c: Column, - ) -> AddConfig { - let q_add = meta.selector(); - meta.create_gate("Field element addition: c = a + b", |meta| { - let q_add = meta.query_selector(q_add); - let a = meta.query_advice(a, Rotation::cur()); - let b = meta.query_advice(b, Rotation::cur()); - let c = meta.query_advice(c, Rotation::cur()); - - Constraints::with_selector(q_add, Some(a + b - c)) - }); - - AddConfig { a, b, c, q_add } - } - - pub fn construct(config: AddConfig) -> Self { - Self { config } - } -} - -impl AddInstruction for AddChip { - fn add( - &self, - mut layouter: impl Layouter, - a: &AssignedCell, - b: &AssignedCell, - ) -> Result, plonk::Error> { - layouter.assign_region( - || "c = a + b", - |mut region| { - self.config.q_add.enable(&mut region, 0)?; - - a.copy_advice(|| "copy a", &mut region, self.config.a, 0)?; - b.copy_advice(|| "copy b", &mut region, self.config.b, 0)?; - - let scalar_val = a.value().zip(b.value()).map(|(a, b)| a + b); - region.assign_advice(|| "c", self.config.c, 0, || scalar_val) - }, - ) - } -} diff --git a/src/circuit/modules/mod.rs b/src/circuit/modules/mod.rs index 9b31df8fa..bb957bdb5 100644 --- a/src/circuit/modules/mod.rs +++ b/src/circuit/modules/mod.rs @@ -1,9 +1,6 @@ /// pub mod poseidon; -/// -pub mod elgamal; - /// pub mod kzg; diff --git a/src/eth.rs b/src/eth.rs index b4759ffea..60b376276 100644 --- a/src/eth.rs +++ b/src/eth.rs @@ -1,5 +1,5 @@ use crate::graph::input::{CallsToAccount, FileSourceInner, GraphData}; -use crate::graph::modules::{ELGAMAL_INSTANCES, POSEIDON_INSTANCES}; +use crate::graph::modules::POSEIDON_INSTANCES; use crate::graph::DataSource; #[cfg(not(target_arch = "wasm32"))] use crate::graph::GraphSettings; @@ -145,8 +145,6 @@ pub async fn deploy_da_verifier_via_solidity( if settings.run_args.input_visibility.is_hashed() { instance_shapes.push(POSEIDON_INSTANCES) - } else if settings.run_args.input_visibility.is_encrypted() { - instance_shapes.push(ELGAMAL_INSTANCES) } else if settings.run_args.input_visibility.is_public() { for idx in 0..settings.model_input_scales.len() { let shape = &settings.model_instance_shapes[idx]; @@ -155,16 +153,12 @@ pub async fn deploy_da_verifier_via_solidity( } } - if settings.run_args.param_visibility.is_hashed() - || settings.run_args.param_visibility.is_encrypted() - { + if settings.run_args.param_visibility.is_hashed() { return Err(Box::new(EvmVerificationError::InvalidVisibility)); } if settings.run_args.output_visibility.is_hashed() { instance_shapes.push(POSEIDON_INSTANCES) - } else if settings.run_args.output_visibility.is_encrypted() { - instance_shapes.push(ELGAMAL_INSTANCES) } else if settings.run_args.output_visibility.is_public() { for idx in model_instance_offset..model_instance_offset + settings.model_output_scales.len() { @@ -177,9 +171,7 @@ pub async fn deploy_da_verifier_via_solidity( let mut contract_instance_offset = 0; if let DataSource::OnChain(source) = input.input_data { - if settings.run_args.input_visibility.is_hashed_public() - | settings.run_args.input_visibility.is_encrypted() - { + if settings.run_args.input_visibility.is_hashed_public() { // set scales 1.0 scales.extend(vec![0; instance_shapes[instance_idx]]); instance_idx += 1; @@ -204,9 +196,7 @@ pub async fn deploy_da_verifier_via_solidity( } if let Some(DataSource::OnChain(source)) = input.output_data { - if settings.run_args.output_visibility.is_hashed_public() - | settings.run_args.output_visibility.is_encrypted() - { + if settings.run_args.output_visibility.is_hashed_public() { // set scales 1.0 scales.extend(vec![0; instance_shapes[instance_idx]]); } else { diff --git a/src/graph/mod.rs b/src/graph/mod.rs index 9e0590268..dc5d02fee 100644 --- a/src/graph/mod.rs +++ b/src/graph/mod.rs @@ -20,9 +20,7 @@ use itertools::Itertools; #[cfg(not(target_arch = "wasm32"))] use self::input::OnChainSource; use self::input::{FileSource, GraphData}; -use self::modules::{ - GraphModules, ModuleConfigs, ModuleForwardResult, ModuleSettings, ModuleSizes, -}; +use self::modules::{GraphModules, ModuleConfigs, ModuleForwardResult, ModuleSizes}; use crate::circuit::lookup::LookupOp; use crate::circuit::modules::ModulePlanner; use crate::circuit::table::{Table, RESERVED_BLINDING_ROWS_PAD}; @@ -342,9 +340,6 @@ impl ToPyObject for GraphWitness { if let Some(processed_inputs_poseidon_hash) = &processed_inputs.poseidon_hash { insert_poseidon_hash_pydict(dict_inputs, processed_inputs_poseidon_hash).unwrap(); } - if let Some(processed_inputs_elgamal) = &processed_inputs.elgamal { - insert_elgamal_results_pydict(py, dict_inputs, processed_inputs_elgamal).unwrap(); - } if let Some(processed_inputs_kzg_commit) = &processed_inputs.kzg_commit { insert_kzg_commit_pydict(dict_inputs, processed_inputs_kzg_commit).unwrap(); } @@ -356,9 +351,6 @@ impl ToPyObject for GraphWitness { if let Some(processed_params_poseidon_hash) = &processed_params.poseidon_hash { insert_poseidon_hash_pydict(dict_params, processed_params_poseidon_hash).unwrap(); } - if let Some(processed_params_elgamal) = &processed_params.elgamal { - insert_elgamal_results_pydict(py, dict_params, processed_params_elgamal).unwrap(); - } if let Some(processed_params_kzg_commit) = &processed_params.kzg_commit { insert_kzg_commit_pydict(dict_inputs, processed_params_kzg_commit).unwrap(); } @@ -370,9 +362,6 @@ impl ToPyObject for GraphWitness { if let Some(processed_outputs_poseidon_hash) = &processed_outputs.poseidon_hash { insert_poseidon_hash_pydict(dict_outputs, processed_outputs_poseidon_hash).unwrap(); } - if let Some(processed_outputs_elgamal) = &processed_outputs.elgamal { - insert_elgamal_results_pydict(py, dict_outputs, processed_outputs_elgamal).unwrap(); - } if let Some(processed_outputs_kzg_commit) = &processed_outputs.kzg_commit { insert_kzg_commit_pydict(dict_inputs, processed_outputs_kzg_commit).unwrap(); } @@ -407,48 +396,6 @@ fn insert_kzg_commit_pydict(pydict: &PyDict, commits: &Vec>) -> Re Ok(()) } -#[cfg(feature = "python-bindings")] -use modules::ElGamalResult; -#[cfg(feature = "python-bindings")] -fn insert_elgamal_results_pydict( - py: Python, - pydict: &PyDict, - elgamal_results: &ElGamalResult, -) -> Result<(), PyErr> { - let results_dict = PyDict::new(py); - let cipher_text: Vec> = elgamal_results - .ciphertexts - .iter() - .map(|v| { - v.iter() - .map(field_to_vecu64_montgomery) - .collect::>() - }) - .collect::>>(); - results_dict.set_item("ciphertexts", cipher_text)?; - - let encrypted_messages: Vec> = elgamal_results - .encrypted_messages - .iter() - .map(|v| { - v.iter() - .map(field_to_vecu64_montgomery) - .collect::>() - }) - .collect::>>(); - results_dict.set_item("encrypted_messages", encrypted_messages)?; - - let variables: crate::python::PyElGamalVariables = elgamal_results.variables.clone().into(); - - results_dict.set_item("variables", variables)?; - - pydict.set_item("elgamal", results_dict)?; - - Ok(()) - - //elgamal -} - /// model parameters #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] pub struct GraphSettings { @@ -549,11 +496,8 @@ impl GraphSettings { /// if any visibility is encrypted or hashed pub fn module_requires_fixed(&self) -> bool { - self.run_args.input_visibility.is_encrypted() - || self.run_args.input_visibility.is_hashed() - || self.run_args.output_visibility.is_encrypted() + self.run_args.input_visibility.is_hashed() || self.run_args.output_visibility.is_hashed() - || self.run_args.param_visibility.is_encrypted() || self.run_args.param_visibility.is_hashed() } @@ -588,8 +532,6 @@ pub struct GraphCircuit { pub core: CoreCircuit, /// The witness data for the model. pub graph_witness: GraphWitness, - /// The settings of the model's modules. - pub module_settings: ModuleSettings, } impl GraphCircuit { @@ -683,7 +625,6 @@ impl GraphCircuit { } // dummy module settings, must load from GraphData after - let module_settings = ModuleSettings::default(); let mut settings = model.gen_params(run_args, CheckMode::UNSAFE)?; let mut num_params = 0; @@ -714,7 +655,6 @@ impl GraphCircuit { Ok(GraphCircuit { core, graph_witness: GraphWitness::new(inputs, vec![]), - module_settings, }) } @@ -732,7 +672,6 @@ impl GraphCircuit { } // dummy module settings, must load from GraphData after - let module_settings = ModuleSettings::default(); settings.check_mode = check_mode; @@ -744,7 +683,6 @@ impl GraphCircuit { Ok(GraphCircuit { core, graph_witness: GraphWitness::new(inputs, vec![]), - module_settings, }) } @@ -755,8 +693,6 @@ impl GraphCircuit { ) -> Result<(), Box> { self.graph_witness = data.clone(); // load the module settings - self.module_settings = ModuleSettings::from(data); - Ok(()) } @@ -1555,7 +1491,6 @@ impl Circuit for GraphCircuit { &mut input_outlets, input_visibility, &mut instance_offset, - &self.module_settings.input, )?; // replace inputs with the outlets for (i, outlet) in outlets.iter().enumerate() { @@ -1568,7 +1503,6 @@ impl Circuit for GraphCircuit { &mut inputs, input_visibility, &mut instance_offset, - &self.module_settings.input, )?; } @@ -1605,7 +1539,6 @@ impl Circuit for GraphCircuit { &mut flattened_params, param_visibility, &mut instance_offset, - &self.module_settings.params, )?; let shapes = self.model().const_shapes(); @@ -1658,7 +1591,6 @@ impl Circuit for GraphCircuit { &mut output_outlets, &self.settings().run_args.output_visibility, &mut instance_offset, - &self.module_settings.output, )?; // replace outputs with the outlets @@ -1672,7 +1604,6 @@ impl Circuit for GraphCircuit { &mut outputs, &self.settings().run_args.output_visibility, &mut instance_offset, - &self.module_settings.output, )?; } diff --git a/src/graph/modules.rs b/src/graph/modules.rs index 046853a35..20905f015 100644 --- a/src/graph/modules.rs +++ b/src/graph/modules.rs @@ -1,17 +1,15 @@ -use crate::circuit::modules::elgamal::{ElGamalConfig, ElGamalGadget, ElGamalVariables}; use crate::circuit::modules::kzg::{KZGChip, KZGConfig}; use crate::circuit::modules::poseidon::spec::{PoseidonSpec, POSEIDON_RATE, POSEIDON_WIDTH}; use crate::circuit::modules::poseidon::{PoseidonChip, PoseidonConfig}; use crate::circuit::modules::Module; -use crate::tensor::{Tensor, ValTensor, ValType}; -use halo2_proofs::circuit::{Layouter, Value}; +use crate::tensor::{Tensor, ValTensor}; +use halo2_proofs::circuit::Layouter; use halo2_proofs::plonk::{Column, ConstraintSystem, Error, Instance, VerifyingKey}; use halo2_proofs::poly::kzg::commitment::ParamsKZG; use halo2curves::bn256::{Bn256, Fr as Fp, G1Affine}; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use super::GraphWitness; use super::{VarVisibility, Visibility}; /// poseidon len to hash in tree @@ -35,8 +33,6 @@ pub struct ModuleConfigs { kzg: Vec, /// Poseidon poseidon: Option, - /// ElGamal - elgamal: Option, /// Instance pub instance: Option>, } @@ -64,16 +60,6 @@ impl ModuleConfigs { visibility: VarVisibility, module_size: ModuleSizes, ) { - if (visibility.input.is_encrypted() - || visibility.output.is_encrypted() - || visibility.params.is_encrypted()) - && module_size.elgamal.1[0] > 0 - { - let elgamal = ElGamalGadget::configure(cs, ()); - self.instance = Some(elgamal.instance); - self.elgamal = Some(elgamal); - }; - if (visibility.input.is_hashed() || visibility.output.is_hashed() || visibility.params.is_hashed()) @@ -103,84 +89,11 @@ impl ModuleConfigs { } } -#[derive(Clone, Debug, Serialize, Deserialize)] -/// Module variable settings -pub struct ModuleVarSettings { - /// - elgamal: Option, -} - -impl ModuleVarSettings { - /// Create new module variable settings - pub fn new(elgamal: ElGamalVariables) -> Self { - ModuleVarSettings { - elgamal: Some(elgamal), - } - } -} - -impl Default for ModuleVarSettings { - fn default() -> Self { - let dummy_elgamal = ElGamalVariables::default(); - ModuleVarSettings { - elgamal: Some(dummy_elgamal), - } - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -/// Module input settings -pub struct ModuleSettings { - /// - pub input: ModuleVarSettings, - /// - pub params: ModuleVarSettings, - /// - pub output: ModuleVarSettings, -} - -impl From<&GraphWitness> for ModuleSettings { - fn from(graph_input: &GraphWitness) -> Self { - let mut settings = Self::default(); - - if let Some(processed_inputs) = &graph_input.processed_inputs { - if let Some(elgamal_result) = &processed_inputs.elgamal { - settings.input = ModuleVarSettings::new(elgamal_result.variables.clone()); - } - } - if let Some(processed_params) = &graph_input.processed_params { - if let Some(elgamal_result) = &processed_params.elgamal { - settings.params = ModuleVarSettings::new(elgamal_result.variables.clone()); - } - } - if let Some(processed_outputs) = &graph_input.processed_outputs { - if let Some(elgamal_result) = &processed_outputs.elgamal { - settings.output = ModuleVarSettings::new(elgamal_result.variables.clone()); - } - } - - settings - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -/// Result from ElGamal -pub struct ElGamalResult { - /// ElGamal variables - pub variables: ElGamalVariables, - /// ElGamal ciphertexts - pub ciphertexts: Vec>, - /// ElGamal encrypted message - pub encrypted_messages: Vec>, -} - /// Result from a forward pass #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct ModuleForwardResult { /// The inputs of the forward pass for poseidon pub poseidon_hash: Option>, - /// The outputs of the forward pass for ElGamal - pub elgamal: Option, /// The outputs of the forward pass for KZG pub kzg_commit: Option>>, } @@ -195,8 +108,6 @@ impl ModuleForwardResult { .into_iter() .map(|x| vec![x]) .collect() - } else if vis.is_encrypted() { - self.elgamal.clone().unwrap().encrypted_messages } else { vec![] } @@ -206,8 +117,6 @@ impl ModuleForwardResult { pub fn get_instances(&self) -> Vec> { if let Some(poseidon) = &self.poseidon_hash { poseidon.iter().map(|x| vec![*x]).collect() - } else if let Some(elgamal) = &self.elgamal { - elgamal.ciphertexts.clone() } else { vec![] } @@ -219,7 +128,6 @@ impl ModuleForwardResult { pub struct ModuleSizes { kzg: Vec, poseidon: (usize, Vec), - elgamal: (usize, Vec), } impl ModuleSizes { @@ -231,26 +139,17 @@ impl ModuleSizes { 0, vec![0; crate::circuit::modules::poseidon::NUM_INSTANCE_COLUMNS], ), - elgamal: ( - 0, - vec![0; crate::circuit::modules::elgamal::NUM_INSTANCE_COLUMNS], - ), } } /// Get the number of constraints pub fn max_constraints(&self) -> usize { - self.poseidon.0.max(self.elgamal.0) + self.poseidon.0 } /// Get the number of instances pub fn num_instances(&self) -> Vec { // concat - self.poseidon - .1 - .iter() - .chain(self.elgamal.1.iter()) - .copied() - .collect_vec() + self.poseidon.1.clone() } } @@ -287,11 +186,6 @@ impl GraphModules { sizes.poseidon.0 += ModulePoseidon::num_rows(total_len); // 1 constraints for hash sizes.poseidon.1[0] += 1; - } else if visibility.is_encrypted() { - // add the 1 time fixed cost of maingate + ecc chips - sizes.elgamal.0 += ElGamalGadget::num_rows(total_len); - // 4 constraints for each ciphertext c1, c2, and sk - sizes.elgamal.1[0] += 4; } } } @@ -341,7 +235,6 @@ impl GraphModules { values: &mut [ValTensor], element_visibility: &Visibility, instance_offset: &mut usize, - module_settings: &ModuleVarSettings, ) -> Result<(), Error> { if element_visibility.is_kzgcommit() && !values.is_empty() { // concat values and sk to get the inputs @@ -388,39 +281,7 @@ impl GraphModules { log::error!("Poseidon config not initialized"); return Err(Error::Synthesis); } - // If the module is encrypted, then we need to encrypt the inputs - } else if element_visibility.is_encrypted() && !values.is_empty() { - if let Some(config) = &mut configs.elgamal { - // reserve module 1 for elgamal modules - layouter.assign_region(|| "_enter_module_1", |_| Ok(()))?; - // create the module - let mut chip = ElGamalGadget::new(config.clone()); - // load the variables - let variables = module_settings.elgamal.as_ref().unwrap().clone(); - chip.load_variables(variables.clone()); - // load the sk: - let sk: Tensor> = - Tensor::new(Some(&[Value::known(variables.sk).into()]), &[1]).unwrap(); - // concat values and sk to get the inputs - let mut inputs = values - .iter_mut() - .map(|x| vec![x.clone(), sk.clone().into()]) - .collect_vec(); - // layout the module - inputs.iter_mut().for_each(|x| { - Self::layout_module(&chip, layouter, x, instance_offset).unwrap(); - chip.config.initialized = true; - }); - // replace the inputs with the outputs - values.iter_mut().enumerate().for_each(|(i, x)| { - x.clone_from(&inputs[i][0]); - }); - - config.initialized = true; - } else { - log::error!("ElGamal config not initialized"); - return Err(Error::Synthesis); - } + // If the module is encrypted, then we need to encrypt the inputs } Ok(()) @@ -433,9 +294,7 @@ impl GraphModules { vk: Option<&VerifyingKey>, srs: Option<&ParamsKZG>, ) -> Result> { - let mut rng = &mut rand::thread_rng(); let mut poseidon_hash = None; - let mut elgamal = None; let mut kzg_commit = None; if element_visibility.is_hashed() { @@ -471,30 +330,8 @@ impl GraphModules { } } - if element_visibility.is_encrypted() { - let variables = ElGamalVariables::gen_random(&mut rng); - let ciphertexts = inputs.iter().fold(vec![], |mut acc, x| { - let res = ElGamalGadget::run((x.to_vec(), variables.clone())).unwrap(); - acc.extend(res); - acc - }); - - let encrypted_messages = inputs.iter().fold(vec![], |mut acc, x| { - let res = ElGamalGadget::encrypt(variables.pk, x.to_vec(), variables.r).c2; - acc.push(res); - acc - }); - - elgamal = Some(ElGamalResult { - variables, - ciphertexts, - encrypted_messages, - }); - } - Ok(ModuleForwardResult { poseidon_hash, - elgamal, kzg_commit, }) } diff --git a/src/graph/vars.rs b/src/graph/vars.rs index b73a39a86..bee112e72 100644 --- a/src/graph/vars.rs +++ b/src/graph/vars.rs @@ -36,8 +36,6 @@ pub enum Visibility { }, /// Mark an item as publicly committed to (KZG commitment sent in the proof submitted for verification) KZGCommit, - /// Mark an item as encrypted (public key and encrypted message sent in the proof submitted for verificatio) - Encrypted, /// assigned as a constant in the circuit Fixed, } @@ -67,7 +65,6 @@ impl<'a> From<&'a str> for Visibility { hash_is_public: true, outlets: vec![], }, - "encrypted" => Visibility::Encrypted, _ => { log::error!("Invalid value for Visibility: {}", s); log::warn!("Defaulting to private"); @@ -101,7 +98,6 @@ impl IntoPy for Visibility { format!("hashed/private/{}", outlets).to_object(py) } } - Visibility::Encrypted => "encrypted".to_object(py), } } } @@ -143,7 +139,6 @@ impl<'source> FromPyObject<'source> for Visibility { outlets: vec![], }), "fixed" => Ok(Visibility::Fixed), - "encrypted" => Ok(Visibility::Encrypted), _ => Err(PyValueError::new_err("Invalid value for Visibility")), } } @@ -194,15 +189,10 @@ impl Visibility { } false } - #[allow(missing_docs)] - pub fn is_encrypted(&self) -> bool { - matches!(&self, Visibility::Encrypted) - } + #[allow(missing_docs)] pub fn requires_processing(&self) -> bool { - matches!(&self, Visibility::Encrypted) - | matches!(&self, Visibility::Hashed { .. }) - | matches!(&self, Visibility::KZGCommit) + matches!(&self, Visibility::Hashed { .. }) | matches!(&self, Visibility::KZGCommit) } #[allow(missing_docs)] pub fn overwrites_inputs(&self) -> Vec { @@ -220,7 +210,6 @@ impl std::fmt::Display for Visibility { Visibility::Public => write!(f, "public"), Visibility::Fixed => write!(f, "fixed"), Visibility::Hashed { .. } => write!(f, "hashed"), - Visibility::Encrypted => write!(f, "encrypted"), } } } @@ -311,9 +300,6 @@ impl VarVisibility { & !output_vis.is_hashed() & !params_vis.is_hashed() & !input_vis.is_hashed() - & !output_vis.is_encrypted() - & !params_vis.is_encrypted() - & !input_vis.is_encrypted() & !output_vis.is_kzgcommit() & !params_vis.is_kzgcommit() & !input_vis.is_kzgcommit() diff --git a/src/python.rs b/src/python.rs index 64f4e1fca..059a496ed 100644 --- a/src/python.rs +++ b/src/python.rs @@ -1,4 +1,3 @@ -use crate::circuit::modules::elgamal::{ElGamalCipher, ElGamalVariables}; use crate::circuit::modules::kzg::KZGChip; use crate::circuit::modules::poseidon::{ spec::{PoseidonSpec, POSEIDON_RATE, POSEIDON_WIDTH}, @@ -26,8 +25,6 @@ use pyo3::exceptions::{PyIOError, PyRuntimeError}; use pyo3::prelude::*; use pyo3::wrap_pyfunction; use pyo3_log; -use rand::rngs::StdRng; -use rand::SeedableRng; use snark_verifier::util::arithmetic::PrimeField; use std::str::FromStr; use std::{fs::File, path::PathBuf}; @@ -136,125 +133,6 @@ impl pyo3::ToPyObject for PyG1Affine { } } -/// pyclass containing the struct used for ElgamalCipher -#[pyclass] -#[derive(Debug, Clone)] -pub struct PyElGamalCipher { - #[pyo3(get, set)] - /// - c1: PyG1, - #[pyo3(get, set)] - /// - c2: Vec, -} - -impl From for ElGamalCipher { - fn from(py_elgamal_cipher: PyElGamalCipher) -> Self { - ElGamalCipher { - c1: py_elgamal_cipher.c1.into(), - c2: py_elgamal_cipher - .c2 - .iter() - .map(crate::pfsys::vecu64_to_field_montgomery::) - .collect::>(), - } - } -} - -impl From for PyElGamalCipher { - fn from(elgamal_cipher: ElGamalCipher) -> Self { - PyElGamalCipher { - c1: elgamal_cipher.c1.into(), - c2: elgamal_cipher - .c2 - .iter() - .map(crate::pfsys::field_to_vecu64_montgomery::) - .collect::>(), - } - } -} - -/// pyclass containing the struct used for ElgamalVariables -#[pyclass] -#[derive(Debug, Clone)] -pub struct PyElGamalVariables { - #[pyo3(get, set)] - r: PyFelt, - #[pyo3(get, set)] - pk: PyG1Affine, - #[pyo3(get, set)] - sk: PyFelt, - #[pyo3(get, set)] - window_size: usize, - #[pyo3(get, set)] - aux_generator: PyG1Affine, -} - -impl From for ElGamalVariables { - fn from(py_elgamal_variables: PyElGamalVariables) -> Self { - ElGamalVariables { - r: crate::pfsys::vecu64_to_field_montgomery::(&py_elgamal_variables.r), - pk: G1Affine { - x: crate::pfsys::vecu64_to_field_montgomery::(&py_elgamal_variables.pk.x), - y: crate::pfsys::vecu64_to_field_montgomery::(&py_elgamal_variables.pk.y), - }, - sk: crate::pfsys::vecu64_to_field_montgomery::(&py_elgamal_variables.sk), - window_size: py_elgamal_variables.window_size, - aux_generator: G1Affine { - x: crate::pfsys::vecu64_to_field_montgomery::( - &py_elgamal_variables.aux_generator.x, - ), - y: crate::pfsys::vecu64_to_field_montgomery::( - &py_elgamal_variables.aux_generator.y, - ), - }, - } - } -} - -impl From for PyElGamalVariables { - fn from(elgamal_variables: ElGamalVariables) -> Self { - PyElGamalVariables { - r: crate::pfsys::field_to_vecu64_montgomery::(&elgamal_variables.r), - pk: PyG1Affine { - x: crate::pfsys::field_to_vecu64_montgomery::(&elgamal_variables.pk.x), - y: crate::pfsys::field_to_vecu64_montgomery::(&elgamal_variables.pk.y), - }, - sk: crate::pfsys::field_to_vecu64_montgomery::(&elgamal_variables.sk), - window_size: elgamal_variables.window_size, - aux_generator: PyG1Affine { - x: crate::pfsys::field_to_vecu64_montgomery::( - &elgamal_variables.aux_generator.x, - ), - y: crate::pfsys::field_to_vecu64_montgomery::( - &elgamal_variables.aux_generator.y, - ), - }, - } - } -} - -impl pyo3::ToPyObject for PyElGamalVariables { - fn to_object(&self, py: pyo3::Python) -> pyo3::PyObject { - let variables_dict = pyo3::types::PyDict::new(py); - - variables_dict.set_item("r", self.r.to_object(py)).unwrap(); - variables_dict - .set_item("pk", self.pk.to_object(py)) - .unwrap(); - variables_dict - .set_item("sk", self.sk.to_object(py)) - .unwrap(); - variables_dict - .set_item("window_size", self.window_size.to_object(py)) - .unwrap(); - variables_dict - .set_item("aux_generator", self.aux_generator.to_object(py)) - .unwrap(); - variables_dict.into() - } -} - /// pyclass containing the struct used for run_args #[pyclass] #[derive(Clone)] @@ -507,60 +385,6 @@ fn swap_proof_commitments(proof_path: PathBuf, witness_path: PathBuf) -> PyResul Ok(()) } -/// Encrypt using elgamal -#[pyfunction(signature = ( - pk, message, r - ))] -pub fn elgamal_encrypt( - pk: PyG1Affine, - message: Vec, - r: PyFelt, -) -> PyResult { - let pk: G1Affine = pk.into(); - let message = message - .iter() - .map(crate::pfsys::vecu64_to_field_montgomery::) - .collect::>(); - let r = crate::pfsys::vecu64_to_field_montgomery::(&r); - - let output = crate::circuit::modules::elgamal::ElGamalGadget::encrypt(pk, message, r); - Ok(output.into()) -} - -/// Decrypt using elgamal -#[pyfunction(signature = ( - cipher, sk - ))] -pub fn elgamal_decrypt(cipher: PyElGamalCipher, sk: PyFelt) -> PyResult> { - let sk: Fr = crate::pfsys::vecu64_to_field_montgomery::(&sk); - - let output = crate::circuit::modules::elgamal::ElGamalGadget::decrypt(&cipher.into(), sk); - - let output = output - .iter() - .map(crate::pfsys::field_to_vecu64_montgomery::) - .collect::>(); - - Ok(output) -} - -/// Generates random elgamal variables from a random seed value in browser. -/// Make sure input seed comes a secure source of randomness -#[pyfunction(signature = ( - rng - ))] -pub fn elgamal_gen_random(rng: Vec) -> PyResult { - let seed: &[u8] = &rng; - let mut rng = StdRng::from_seed( - seed.try_into() - .map_err(|_| PyIOError::new_err("Failed to create random seed"))?, - ); - - let output = crate::circuit::modules::elgamal::ElGamalVariables::gen_random(&mut rng); - - Ok(output.into()) -} - /// Generates a vk from a pk for a model circuit and saves it to a file #[pyfunction(signature = ( path_to_pk, @@ -1200,8 +1024,6 @@ fn ezkl(_py: Python<'_>, m: &PyModule) -> PyResult<()> { // NOTE: DeployVerifierEVM and SendProofEVM will be implemented in python in pyezkl pyo3_log::init(); m.add_class::()?; - m.add_class::()?; - m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; @@ -1211,9 +1033,6 @@ fn ezkl(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(kzg_commit, m)?)?; m.add_function(wrap_pyfunction!(swap_proof_commitments, m)?)?; m.add_function(wrap_pyfunction!(poseidon_hash, m)?)?; - m.add_function(wrap_pyfunction!(elgamal_encrypt, m)?)?; - m.add_function(wrap_pyfunction!(elgamal_decrypt, m)?)?; - m.add_function(wrap_pyfunction!(elgamal_gen_random, m)?)?; m.add_function(wrap_pyfunction!(float_to_vecu64, m)?)?; m.add_function(wrap_pyfunction!(buffer_to_felts, m)?)?; m.add_function(wrap_pyfunction!(gen_vk_from_pk_aggr, m)?)?; diff --git a/src/wasm.rs b/src/wasm.rs index dbeaa19f2..6534a3cad 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -1,4 +1,3 @@ -use crate::circuit::modules::elgamal::ElGamalCipher; use crate::circuit::modules::poseidon::spec::{PoseidonSpec, POSEIDON_RATE, POSEIDON_WIDTH}; use crate::circuit::modules::poseidon::PoseidonChip; use crate::circuit::modules::Module; @@ -16,8 +15,6 @@ use halo2_proofs::poly::kzg::{ use halo2_solidity_verifier::encode_calldata; use halo2curves::bn256::{Bn256, Fr, G1Affine}; use halo2curves::ff::{FromUniformBytes, PrimeField}; -use rand::rngs::StdRng; -use rand::SeedableRng; use crate::tensor::TensorType; use wasm_bindgen::prelude::*; @@ -197,63 +194,6 @@ pub fn poseidonHash( )?)) } -/// Generates random elgamal variables from a random seed value in browser. -/// Make sure input seed comes a secure source of randomness -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn elgamalGenRandom(rng: wasm_bindgen::Clamped>) -> Result, JsError> { - let seed: &[u8] = &rng; - let mut rng = StdRng::from_seed( - seed.try_into() - .map_err(|e| JsError::new(&format!("{}", e)))?, - ); - - let output = crate::circuit::modules::elgamal::ElGamalVariables::gen_random(&mut rng); - - serde_json::to_vec(&output) - .map_err(|e| JsError::new(&format!("Failed to serialize elgamal variables: {}", e))) -} - -/// Encrypt using elgamal in browser. Input message -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn elgamalEncrypt( - pk: wasm_bindgen::Clamped>, - message: wasm_bindgen::Clamped>, - r: wasm_bindgen::Clamped>, -) -> Result, JsError> { - let pk: G1Affine = serde_json::from_slice(&pk[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize pk: {}", e)))?; - let message: Vec = serde_json::from_slice(&message[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize message: {}", e)))?; - let r: Fr = serde_json::from_slice(&r[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize r: {}", e)))?; - - let output = crate::circuit::modules::elgamal::ElGamalGadget::encrypt(pk, message, r); - - serde_json::to_vec(&output) - .map_err(|e| JsError::new(&format!("Failed to serialize cipher {}", e))) -} - -/// Decrypt using elgamal in browser. Input message -#[wasm_bindgen] -#[allow(non_snake_case)] -pub fn elgamalDecrypt( - cipher: wasm_bindgen::Clamped>, - sk: wasm_bindgen::Clamped>, -) -> Result, JsError> { - let sk: Fr = serde_json::from_slice(&sk[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize sk: {}", e)))?; - - let cipher: ElGamalCipher = serde_json::from_slice(&cipher[..]) - .map_err(|e| JsError::new(&format!("Failed to deserialize cipher: {}", e)))?; - - let output = crate::circuit::modules::elgamal::ElGamalGadget::decrypt(&cipher, sk); - - serde_json::to_vec(&output) - .map_err(|e| JsError::new(&format!("Failed to serialize decrypted cipher: {}", e))) -} - /// Generate a witness file from input.json, compiled model and a settings.json file. #[wasm_bindgen] #[allow(non_snake_case)] diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index f9c4cd68f..906f7268d 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -623,14 +623,6 @@ mod native_tests { test_dir.close().unwrap(); } - #(#[test_case(TESTS[N])])* - fn mock_encrypted_input_(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); - mock(path, test.to_string(), "encrypted", "private", "public", 1, "resources", None); - test_dir.close().unwrap(); - } #(#[test_case(TESTS[N])])* fn mock_hashed_params_(test: &str) { @@ -651,15 +643,6 @@ mod native_tests { test_dir.close().unwrap(); } - #(#[test_case(TESTS[N])])* - fn mock_encrypted_params_(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); - mock(path, test.to_string(), "private", "hashed", "public", 1, "resources", None); - test_dir.close().unwrap(); - } - #(#[test_case(TESTS[N])])* fn mock_hashed_output_(test: &str) { crate::native_tests::init_binary(); @@ -694,34 +677,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); - mock(path, test.to_string(), "encrypted", "kzgcommit", "hashed", 1, "resources", None); - test_dir.close().unwrap(); - } - - #(#[test_case(TESTS[N])])* - fn mock_encrypted_output_(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); - mock(path, test.to_string(), "public", "private", "encrypted", 1, "resources", None); - test_dir.close().unwrap(); - } - - #(#[test_case(TESTS[N])])* - fn mock_encrypted_input_params_(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); - mock(path, test.to_string(), "encrypted", "encrypted", "public", 1, "resources", None); - test_dir.close().unwrap(); - } - - #(#[test_case(TESTS[N])])* - fn mock_encrypted_all_(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); - mock(path, test.to_string(), "encrypted", "encrypted", "encrypted", 1, "resources", None); + mock(path, test.to_string(), "public", "kzgcommit", "hashed", 1, "resources", None); test_dir.close().unwrap(); } @@ -736,16 +692,6 @@ mod native_tests { } - - #(#[test_case(TESTS[N])])* - fn mock_encrypted_input_hashed_params_(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); - mock(path, test.to_string(), "encrypted", "hashed", "public", 1, "resources", None); - test_dir.close().unwrap(); - } - #(#[test_case(TESTS[N])])* fn mock_hashed_input_output_(test: &str) { crate::native_tests::init_binary(); @@ -856,15 +802,6 @@ mod native_tests { test_dir.close().unwrap(); } - #(#[test_case(TESTS[N])])* - fn kzg_prove_and_verify_encrypted_output(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); - kzg_prove_and_verify(path, test.to_string(), "safe", "private", "private", "encrypted", 1, None, false, "single"); - test_dir.close().unwrap(); - } - #(#[test_case(TESTS[N])])* fn kzg_fuzz_(test: &str) { crate::native_tests::init_binary(); @@ -1029,18 +966,6 @@ mod native_tests { test_dir.close().unwrap(); } - // these take a particularly long time to run - #[test] - #[ignore] - fn kzg_evm_aggr_prove_and_verify_encrypted_input_() { - let test = "1l_mlp"; - 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_aggr_prove_and_verify(path, test.to_string(), "encrypted", "private", "public"); - test_dir.close().unwrap(); - } }); @@ -1059,19 +984,6 @@ mod native_tests { } - #[test] - #[ignore] - fn kzg_evm_prove_and_verify_encrypted_input_() { - let test = "1l_mlp"; - 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(path, test.to_string(), "encrypted", "private", "public"); - #[cfg(not(feature = "icicle"))] - run_js_tests(path, test.to_string(), "testBrowserEvmVerify"); - test_dir.close().unwrap(); - } #(#[test_case(TESTS_EVM[N])])* fn kzg_evm_hashed_input_prove_and_verify_(test: &str) { diff --git a/tests/py_integration_tests.rs b/tests/py_integration_tests.rs index 0bca83971..0a97f59e7 100644 --- a/tests/py_integration_tests.rs +++ b/tests/py_integration_tests.rs @@ -117,13 +117,12 @@ mod py_tests { } } - const TESTS: [&str; 33] = [ + const TESTS: [&str; 32] = [ "proof_splitting.ipynb", "mnist_gan_proof_splitting.ipynb", "mnist_gan.ipynb", // "mnist_vae.ipynb", "keras_simple_demo.ipynb", - "encrypted_vis.ipynb", "hashed_vis.ipynb", "simple_demo_all_public.ipynb", "data_attest.ipynb", @@ -164,7 +163,7 @@ mod py_tests { use super::*; - seq!(N in 0..=32 { + seq!(N in 0..=31 { #(#[test_case(TESTS[N])])* fn run_notebook_(test: &str) { diff --git a/tests/python/binding_tests.py b/tests/python/binding_tests.py index 05126fbe3..1951bfd45 100644 --- a/tests/python/binding_tests.py +++ b/tests/python/binding_tests.py @@ -62,25 +62,6 @@ def test_poseidon_hash(): res[0]) == "0x0da7e5e5c8877242fa699f586baf770d731defd54f952d4adeb85047a0e32f45" -def test_elgamal(): - """ - Test for elgamal encryption and decryption - """ - message = [1.0, 2.0, 3.0, 4.0] - felt_message = [ezkl.float_to_vecu64(x, 7) for x in message] - - # list of len 32 - rng = [0 for _ in range(32)] - - variables = ezkl.elgamal_gen_random(rng) - encrypted_message = ezkl.elgamal_encrypt( - variables.pk, felt_message, variables.r) - decrypted_message = ezkl.elgamal_decrypt(encrypted_message, variables.sk) - assert decrypted_message == felt_message - - recovered_message = [ezkl.vecu64_to_float(x, 7) for x in decrypted_message] - assert recovered_message == message - def test_field_serialization(): """ diff --git a/tests/wasm.rs b/tests/wasm.rs index 9e1f4d928..37fee3a47 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -1,8 +1,6 @@ #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] #[cfg(test)] mod wasm32 { - use ark_std::test_rng; - use ezkl::circuit::modules::elgamal::ElGamalVariables; use ezkl::circuit::modules::poseidon::spec::{PoseidonSpec, POSEIDON_RATE, POSEIDON_WIDTH}; use ezkl::circuit::modules::poseidon::PoseidonChip; use ezkl::circuit::modules::Module; @@ -10,16 +8,13 @@ mod wasm32 { use ezkl::graph::GraphWitness; use ezkl::pfsys; use ezkl::wasm::{ - bufferToVecOfVecU64, compiledCircuitValidation, elgamalDecrypt, elgamalEncrypt, - elgamalGenRandom, encodeVerifierCalldata, genPk, genVk, genWitness, inputValidation, - pkValidation, poseidonHash, printProofHex, proofValidation, prove, settingsValidation, - srsValidation, u8_array_to_u128_le, vecU64ToFelt, vecU64ToFloat, vecU64ToInt, verify, - vkValidation, witnessValidation, + bufferToVecOfVecU64, compiledCircuitValidation, encodeVerifierCalldata, genPk, genVk, + genWitness, inputValidation, pkValidation, poseidonHash, printProofHex, proofValidation, + prove, settingsValidation, srsValidation, u8_array_to_u128_le, vecU64ToFelt, vecU64ToFloat, + vecU64ToInt, verify, vkValidation, witnessValidation, }; use halo2_solidity_verifier::encode_calldata; use halo2curves::bn256::{Fr, G1Affine}; - use rand::rngs::StdRng; - use rand::SeedableRng; use snark_verifier::util::arithmetic::PrimeField; #[cfg(feature = "web")] pub use wasm_bindgen_rayon::init_thread_pool; @@ -146,62 +141,6 @@ mod wasm32 { assert_eq!(field_elements[1], reference_field_element_high); } - #[wasm_bindgen_test] - async fn verify_elgamal_gen_random_wasm() { - // Generate a seed value - let seed = [0u8; 32]; - - // Convert the seed to a wasm-friendly format - let wasm_seed = wasm_bindgen::Clamped(seed.to_vec()); - - // Use the seed to generate ElGamal variables via WASM function - let wasm_output = elgamalGenRandom(wasm_seed).map_err(|_| "failed").unwrap(); - - let wasm_vars: ElGamalVariables = serde_json::from_slice(&wasm_output[..]).unwrap(); - - // Use the same seed to generate ElGamal variables directly - let mut rng_from_seed = StdRng::from_seed(seed); - let direct_vars = ElGamalVariables::gen_random(&mut rng_from_seed); - - // Check if both variables are the same - assert_eq!(direct_vars, wasm_vars) - } - - #[wasm_bindgen_test] - async fn verify_elgamal_wasm() { - let mut rng = test_rng(); - - let var = ElGamalVariables::gen_random(&mut rng); - - let mut message: Vec = vec![]; - for i in 0..32 { - message.push(Fr::from(i as u64)); - } - - let pk = serde_json::to_vec(&var.pk).unwrap(); - let message_ser = serde_json::to_vec(&message).unwrap(); - let r = serde_json::to_vec(&var.r).unwrap(); - - let cipher = elgamalEncrypt( - wasm_bindgen::Clamped(pk.clone()), - wasm_bindgen::Clamped(message_ser.clone()), - wasm_bindgen::Clamped(r.clone()), - ) - .map_err(|_| "failed") - .unwrap(); - - let sk = serde_json::to_vec(&var.sk).unwrap(); - - let decrypted_message = - elgamalDecrypt(wasm_bindgen::Clamped(cipher), wasm_bindgen::Clamped(sk)) - .map_err(|_| "failed") - .unwrap(); - - let decrypted_message: Vec = serde_json::from_slice(&decrypted_message[..]).unwrap(); - - assert_eq!(message, decrypted_message) - } - #[wasm_bindgen_test] async fn verify_hash() { let mut message: Vec = vec![]; diff --git a/tests/wasm/model.compiled b/tests/wasm/model.compiled index c4c20156e..ee8f96f18 100644 Binary files a/tests/wasm/model.compiled and b/tests/wasm/model.compiled differ