From fae12ec2e599fd7fc724b1c15c984e95881a681a Mon Sep 17 00:00:00 2001 From: 0xisk <0xisk@proton.me> Date: Tue, 6 Aug 2024 14:52:57 +0200 Subject: [PATCH] feat: a simple gm encryption halo2 circuit --- .vscode/settings.json | 9 + Cargo.toml | 1 + packages/circuits/Cargo.toml | 11 +- packages/circuits/README.md | 0 .../circuits/configs/gm_encryption.config | 8 + packages/circuits/data/fixed_len_keccak-1.in | 3 - packages/circuits/data/fixed_len_keccak.in | 3 - packages/circuits/data/halo2_lib.0.in | 3 - packages/circuits/data/halo2_lib.in | 3 - packages/circuits/data/poseidon.in | 3 - packages/circuits/data/range.in | 3 - packages/circuits/data/var_len_keccak.1.in | 4 - packages/circuits/data/var_len_keccak.in | 4 - packages/circuits/examples/builder.rs | 78 -------- packages/circuits/examples/halo2_lib.rs | 78 -------- packages/circuits/examples/poseidon.rs | 50 ----- packages/circuits/examples/range.rs | 47 ----- packages/circuits/src/consts.rs | 1 + packages/circuits/src/gm_encryption.rs | 181 ++++++++++++++++++ packages/circuits/src/lib.rs | 5 +- .../circuits/src/vanilla_circuits/is_zero.rs | 176 ----------------- packages/circuits/src/vanilla_circuits/mod.rs | 8 - packages/circuits/src/vanilla_circuits/or.rs | 181 ------------------ .../src/vanilla_circuits/standard_plonk.rs | 132 ------------- packages/probabilistic-encryption | 1 + 25 files changed, 210 insertions(+), 783 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 packages/circuits/README.md create mode 100644 packages/circuits/configs/gm_encryption.config delete mode 100644 packages/circuits/data/fixed_len_keccak-1.in delete mode 100644 packages/circuits/data/fixed_len_keccak.in delete mode 100644 packages/circuits/data/halo2_lib.0.in delete mode 100644 packages/circuits/data/halo2_lib.in delete mode 100644 packages/circuits/data/poseidon.in delete mode 100644 packages/circuits/data/range.in delete mode 100644 packages/circuits/data/var_len_keccak.1.in delete mode 100644 packages/circuits/data/var_len_keccak.in delete mode 100644 packages/circuits/examples/builder.rs delete mode 100644 packages/circuits/examples/halo2_lib.rs delete mode 100644 packages/circuits/examples/poseidon.rs delete mode 100644 packages/circuits/examples/range.rs create mode 100644 packages/circuits/src/consts.rs create mode 100644 packages/circuits/src/gm_encryption.rs delete mode 100644 packages/circuits/src/vanilla_circuits/is_zero.rs delete mode 100644 packages/circuits/src/vanilla_circuits/mod.rs delete mode 100644 packages/circuits/src/vanilla_circuits/or.rs delete mode 100644 packages/circuits/src/vanilla_circuits/standard_plonk.rs create mode 160000 packages/probabilistic-encryption diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0a69886 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "cSpell.words": [ + "biguint", + "ciphertext", + "Goldwasser", + "Micali", + "probabilisticpubkey" + ] +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index b8dea4e..344420c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ resolver = "2" members = [ "packages/circuits", "packages/cli", + "packages/probabilistic-encryption" ] # Dev / testing mode. We make opt-level = 3 to improve proving times (otherwise it is really slow) diff --git a/packages/circuits/Cargo.toml b/packages/circuits/Cargo.toml index cb27b36..2c74fc9 100644 --- a/packages/circuits/Cargo.toml +++ b/packages/circuits/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.81" env_logger = "=0.10" serde = { version = "=1.0", default-features = false, features = ["derive"] } serde_json = "=1.0" @@ -13,12 +14,14 @@ rand = "=0.8" clap = { version = "=4.0", features = ["derive"] } clap-num = "=1.0.2" cli = { path = "../cli" } +probabilistic-encryption = { path = "../probabilistic-encryption" } +num-bigint = "0.4.4" # halo2 -halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2023_02_02" } - -# Axiom's helper API with basic functions -halo2-base = { git = "https://github.com/axiom-crypto/halo2-lib", branch = "community-edition" } +halo2-ecc = { version = "0.4.1", default-features = false, features = ["halo2-axiom"] } +halo2-base = { version = "0.4.1", default-features = false, features = ["halo2-axiom"] } +halo2-wasm = { version = "0.3.4" } +wasm-bindgen = "0.2.89" snark-verifier-sdk = { git = "https://github.com/axiom-crypto/snark-verifier.git", branch = "community-edition" } [dev-dependencies] diff --git a/packages/circuits/README.md b/packages/circuits/README.md new file mode 100644 index 0000000..e69de29 diff --git a/packages/circuits/configs/gm_encryption.config b/packages/circuits/configs/gm_encryption.config new file mode 100644 index 0000000..5741421 --- /dev/null +++ b/packages/circuits/configs/gm_encryption.config @@ -0,0 +1,8 @@ +{ + "k": 15, + "numAdvice": 34, + "numLookupAdvice": 3, + "numInstance": 1, + "numLookupBits": 14, + "numVirtualInstance": 0 +} diff --git a/packages/circuits/data/fixed_len_keccak-1.in b/packages/circuits/data/fixed_len_keccak-1.in deleted file mode 100644 index 01de222..0000000 --- a/packages/circuits/data/fixed_len_keccak-1.in +++ /dev/null @@ -1,3 +0,0 @@ -{ - "bytes": [0,1,2] -} \ No newline at end of file diff --git a/packages/circuits/data/fixed_len_keccak.in b/packages/circuits/data/fixed_len_keccak.in deleted file mode 100644 index 0879606..0000000 --- a/packages/circuits/data/fixed_len_keccak.in +++ /dev/null @@ -1,3 +0,0 @@ -{ - "bytes": [] -} \ No newline at end of file diff --git a/packages/circuits/data/halo2_lib.0.in b/packages/circuits/data/halo2_lib.0.in deleted file mode 100644 index 093bb2e..0000000 --- a/packages/circuits/data/halo2_lib.0.in +++ /dev/null @@ -1,3 +0,0 @@ -{ - "x": "0" -} diff --git a/packages/circuits/data/halo2_lib.in b/packages/circuits/data/halo2_lib.in deleted file mode 100644 index 6f6bfe2..0000000 --- a/packages/circuits/data/halo2_lib.in +++ /dev/null @@ -1,3 +0,0 @@ -{ - "x": "12" -} diff --git a/packages/circuits/data/poseidon.in b/packages/circuits/data/poseidon.in deleted file mode 100644 index f0ea27b..0000000 --- a/packages/circuits/data/poseidon.in +++ /dev/null @@ -1,3 +0,0 @@ -{ - "inputs": ["6","100"] -} diff --git a/packages/circuits/data/range.in b/packages/circuits/data/range.in deleted file mode 100644 index 8fe8e04..0000000 --- a/packages/circuits/data/range.in +++ /dev/null @@ -1,3 +0,0 @@ -{ - "x": "18446744073709551615" -} diff --git a/packages/circuits/data/var_len_keccak.1.in b/packages/circuits/data/var_len_keccak.1.in deleted file mode 100644 index de04b41..0000000 --- a/packages/circuits/data/var_len_keccak.1.in +++ /dev/null @@ -1,4 +0,0 @@ -{ - "padded_bytes": [0,1,2], - "len": 3 -} \ No newline at end of file diff --git a/packages/circuits/data/var_len_keccak.in b/packages/circuits/data/var_len_keccak.in deleted file mode 100644 index fa6fb07..0000000 --- a/packages/circuits/data/var_len_keccak.in +++ /dev/null @@ -1,4 +0,0 @@ -{ - "padded_bytes": [0,1,2], - "len": 0 -} \ No newline at end of file diff --git a/packages/circuits/examples/builder.rs b/packages/circuits/examples/builder.rs deleted file mode 100644 index 4635aa5..0000000 --- a/packages/circuits/examples/builder.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Example of scaffolding where function uses full `GateThreaderBuilder` instead of single `Context` -use clap::Parser; -use cli::cmd::Cli; -use cli::run_on_inputs; -use halo2_base::gates::circuit::builder::BaseCircuitBuilder; -use halo2_base::gates::{GateChip, GateInstructions}; -use halo2_base::halo2_proofs::halo2curves::{bn256::Fr, ff::Field}; -use halo2_base::utils::ScalarField; -use halo2_base::AssignedValue; -#[allow(unused_imports)] -use halo2_base::{ - Context, - QuantumCell::{Constant, Existing, Witness}, -}; -use rand::rngs::OsRng; - -// this algorithm takes a public input x, computes x^2 + 72, and outputs the result as public output -fn some_algorithm_in_zk( - builder: &mut BaseCircuitBuilder, - x: F, - make_public: &mut Vec>, -) { - // can still get a Context via: - let ctx = builder.main(0); // 0 means FirstPhase, don't worry about it - - // `Context` can roughly be thought of as a single-threaded execution trace of a program we want to ZK prove. We do some post-processing on `Context` to optimally divide the execution trace into multiple columns in a PLONKish arithmetization - // More advanced usage with multi-threaded witness generation is possible, but we do not explain it here - - // first we load a number `x` into as system, as a "witness" - let x = ctx.load_witness(x); - // by default, all numbers in the system are private - // we can make it public like so: - make_public.push(x); - - // create a Gate chip that contains methods for basic arithmetic operations - let gate = GateChip::::default(); - - // ===== way 1 ===== - // now we can perform arithmetic operations almost like a normal program using halo2-lib API functions - // square x - let x_sq = gate.mul(ctx, x, x); - - // x^2 + 72 - let c = F::from(72); - // the implicit type of most variables is an "Existing" assigned value - // a known constant is a separate type that we specify by `Constant(c)`: - let out = gate.add(ctx, x_sq, Constant(c)); - // Halo2 does not distinguish between public inputs vs outputs because the verifier seems them all at the same time - // However in traditional terms, `out` is our output number. It is currently still private. - // Let's make it public: - make_public.push(out); - // ==== way 2 ======= - // here is a more optimal way to compute x^2 + 72 using the lower level `assign_region` API: - let val = *x.value() * x.value() + c; - let _val_assigned = - ctx.assign_region_last([Constant(c), Existing(x), Existing(x), Witness(val)], [0]); - // the `[0]` tells us to turn on a vertical `a + b * c = d` gate at row position 0. - // this imposes the constraint c + x * x = val - - // ==== way 3 ====== - // this does the exact same thing as way 2, but with a pre-existing function - let _val_assigned = gate.mul_add(ctx, x, x, Constant(c)); - - println!("x: {:?}", x.value()); - println!("val_assigned: {:?}", out.value()); - assert_eq!(*x.value() * x.value() + c, *out.value()); -} - -fn main() { - env_logger::init(); - - let args = Cli::parse(); - - // let's say we don't want to run prover with inputs from file - // instead we generate inputs here: - let private_inputs = Fr::random(OsRng); - run_on_inputs(some_algorithm_in_zk, args, private_inputs); -} diff --git a/packages/circuits/examples/halo2_lib.rs b/packages/circuits/examples/halo2_lib.rs deleted file mode 100644 index 7fe7ff9..0000000 --- a/packages/circuits/examples/halo2_lib.rs +++ /dev/null @@ -1,78 +0,0 @@ -use clap::Parser; -use cli::cmd::Cli; -use cli::run; -use halo2_base::gates::circuit::builder::BaseCircuitBuilder; -use halo2_base::gates::{GateChip, GateInstructions}; -use halo2_base::utils::ScalarField; -use halo2_base::AssignedValue; -#[allow(unused_imports)] -use halo2_base::{ - Context, - QuantumCell::{Constant, Existing, Witness}, -}; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct CircuitInput { - pub x: String, // field element, but easier to deserialize as a string -} - -// this algorithm takes a public input x, computes x^2 + 72, and outputs the result as public output -fn some_algorithm_in_zk( - builder: &mut BaseCircuitBuilder, - input: CircuitInput, - make_public: &mut Vec>, -) { - let x = F::from_str_vartime(&input.x).expect("deserialize field element should not fail"); - // `Context` can roughly be thought of as a single-threaded execution trace of a program we want to ZK prove. We do some post-processing on `Context` to optimally divide the execution trace into multiple columns in a PLONKish arithmetization - let ctx = builder.main(0); - // More advanced usage with multi-threaded witness generation is possible, but we do not explain it here - - // first we load a number `x` into as system, as a "witness" - let x = ctx.load_witness(x); - // by default, all numbers in the system are private - // we can make it public like so: - make_public.push(x); - - // create a Gate chip that contains methods for basic arithmetic operations - let gate = GateChip::::default(); - - // ===== way 1 ===== - // now we can perform arithmetic operations almost like a normal program using halo2-lib API functions - // square x - let x_sq = gate.mul(ctx, x, x); - - // x^2 + 72 - let c = F::from(72); - // the implicit type of most variables is an "Existing" assigned value - // a known constant is a separate type that we specify by `Constant(c)`: - let out = gate.add(ctx, x_sq, Constant(c)); - // Halo2 does not distinguish between public inputs vs outputs because the verifier seems them all at the same time - // However in traditional terms, `out` is our output number. It is currently still private. - // Let's make it public: - make_public.push(out); - // ==== way 2 ======= - // here is a more optimal way to compute x^2 + 72 using the lower level `assign_region` API: - let val = *x.value() * x.value() + c; - let _val_assigned = - ctx.assign_region_last([Constant(c), Existing(x), Existing(x), Witness(val)], [0]); - // the `[0]` tells us to turn on a vertical `a + b * c = d` gate at row position 0. - // this imposes the constraint c + x * x = val - - // ==== way 3 ====== - // this does the exact same thing as way 2, but with a pre-existing function - let _val_assigned = gate.mul_add(ctx, x, x, Constant(c)); - - println!("x: {:?}", x.value()); - println!("val_assigned: {:?}", out.value()); - assert_eq!(*x.value() * x.value() + c, *out.value()); -} - -fn main() { - env_logger::init(); - - let args = Cli::parse(); - - // run different zk commands based on the command line arguments - run(some_algorithm_in_zk, args); -} diff --git a/packages/circuits/examples/poseidon.rs b/packages/circuits/examples/poseidon.rs deleted file mode 100644 index 8f309df..0000000 --- a/packages/circuits/examples/poseidon.rs +++ /dev/null @@ -1,50 +0,0 @@ -use clap::Parser; -use cli::{cmd::Cli, run}; -use halo2_base::{ - gates::{circuit::builder::BaseCircuitBuilder, GateChip}, - poseidon::hasher::PoseidonHasher, - utils::BigPrimeField, - AssignedValue, -}; -use serde::{Deserialize, Serialize}; -use snark_verifier_sdk::halo2::OptimizedPoseidonSpec; - -const T: usize = 3; -const RATE: usize = 2; -const R_F: usize = 8; -const R_P: usize = 57; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct CircuitInput { - pub inputs: [String; 2], // two field elements, but as strings for easier deserialization -} - -fn hash_two( - builder: &mut BaseCircuitBuilder, - inp: CircuitInput, - make_public: &mut Vec>, -) { - // `Context` can roughly be thought of as a single-threaded execution trace of a program we want to ZK prove. We do some post-processing on `Context` to optimally divide the execution trace into multiple columns in a PLONKish arithmetization - let ctx = builder.main(0); - // More advanced usage with multi-threaded witness generation is possible, but we do not explain it here - - // first we load a private input `x` (let's not worry about public inputs for now) - let [x, y] = inp.inputs.map(|x| ctx.load_witness(F::from_str_vartime(&x).unwrap())); - make_public.extend([x, y]); - - // create a Gate chip that contains methods for basic arithmetic operations - let gate = GateChip::::default(); - let mut poseidon = - PoseidonHasher::::new(OptimizedPoseidonSpec::new::()); - poseidon.initialize_consts(ctx, &gate); - let hash = poseidon.hash_fix_len_array(ctx, &gate, &[x, y]); - make_public.push(hash); - println!("x: {:?}, y: {:?}, poseidon(x): {:?}", x.value(), y.value(), hash.value()); -} - -fn main() { - env_logger::init(); - - let args = Cli::parse(); - run(hash_two, args); -} diff --git a/packages/circuits/examples/range.rs b/packages/circuits/examples/range.rs deleted file mode 100644 index 6aa95ba..0000000 --- a/packages/circuits/examples/range.rs +++ /dev/null @@ -1,47 +0,0 @@ -use clap::Parser; -use cli::cmd::Cli; -use cli::run; -use halo2_base::gates::circuit::builder::BaseCircuitBuilder; -use halo2_base::gates::{GateInstructions, RangeInstructions}; -use halo2_base::utils::ScalarField; -use halo2_base::AssignedValue; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct CircuitInput { - pub x: String, // field element, but easier to deserialize as a string -} - -fn some_algorithm_in_zk( - builder: &mut BaseCircuitBuilder, - input: CircuitInput, - make_public: &mut Vec>, -) { - // create a Range chip that contains methods for basic arithmetic operations - let range = builder.range_chip(); - let ctx = builder.main(0); - - let x = F::from_str_vartime(&input.x).expect("deserialize field element should not fail"); - // `Context` can roughly be thought of as a single-threaded execution trace of a program we want to ZK prove. We do some post-processing on `Context` to optimally divide the execution trace into multiple columns in a PLONKish arithmetization - // More advanced usage with multi-threaded witness generation is possible, but we do not explain it here - - // first we load a private input `x` - let x = ctx.load_witness(x); - // make it public - make_public.push(x); - - // check that `x` is in [0, 2^64) - range.range_check(ctx, x, 64); - - // RangeChip contains GateChip so you can still do basic operations: - let _sum = range.gate().add(ctx, x, x); -} - -fn main() { - env_logger::init(); - - let args = Cli::parse(); - - // run different zk commands based on the command line arguments - run(some_algorithm_in_zk, args); -} diff --git a/packages/circuits/src/consts.rs b/packages/circuits/src/consts.rs new file mode 100644 index 0000000..4a6ddc8 --- /dev/null +++ b/packages/circuits/src/consts.rs @@ -0,0 +1 @@ +pub const CONTEXT_PHASE: usize = 0; diff --git a/packages/circuits/src/gm_encryption.rs b/packages/circuits/src/gm_encryption.rs new file mode 100644 index 0000000..3495839 --- /dev/null +++ b/packages/circuits/src/gm_encryption.rs @@ -0,0 +1,181 @@ +#![allow(non_snake_case)] +use halo2_base::gates::circuit::builder::BaseCircuitBuilder; +use halo2_base::gates::flex_gate::{GateChip, GateInstructions}; +use halo2_base::halo2_proofs::halo2curves::bn256::{self, Fr}; +use halo2_base::utils::{BigPrimeField, ScalarField}; +use halo2_base::AssignedValue; +use halo2_ecc::bn254; +use halo2_wasm::Halo2Wasm; +use num_bigint::BigUint; +use probabilistic_encryption::goldwasser_micali::{ + GoldwasserMicaliPrivateKey, GoldwasserMicaliPublicKey, +}; +use probabilistic_encryption::key::{PrivateKey, PublicKey}; +use std::cell::RefCell; +use std::marker::PhantomData; +use std::rc::Rc; + +use crate::consts::CONTEXT_PHASE; + +type F = bn256::Fr; + +pub struct GmVerificationInputs { + gm_pk: GoldwasserMicaliPublicKey, + gm_sk: GoldwasserMicaliPrivateKey, + ciphertext: Vec, + plaintext: Vec, + _F_marker: PhantomData, +} + +pub struct GmVerificationCircuit { + // Add whatever other chips you need here + gm_verification_inputs: GmVerificationInputs, + builder: Rc>>, +} + +impl GmVerificationCircuit { + pub fn new(halo2_wasm: &Halo2Wasm, gm_verification_inputs: GmVerificationInputs) -> Self { + GmVerificationCircuit { gm_verification_inputs, builder: Rc::clone(&halo2_wasm.circuit) } + } + + pub fn verify_decryption(&mut self) { + let mut builder = self.builder.borrow_mut(); + let ctx = builder.main(CONTEXT_PHASE); + + // Decrypt Ciphertext + let expected_plaintext = + self.gm_verification_inputs.gm_sk.decrypt(&self.gm_verification_inputs.ciphertext); + + // Load actual plaintext and expected plaintext as private inputs + let actual_plaintext_assigned = + ctx.load_witness(F::from_bytes_le(&self.gm_verification_inputs.plaintext)); + + let expected_plaintext_assigned = ctx.load_witness(F::from_bytes_le(&expected_plaintext)); + + // Constrain equality + ctx.constrain_equal(&expected_plaintext_assigned, &actual_plaintext_assigned); + } + + pub fn verify_encryption(&mut self) { + let mut builder = self.builder.borrow_mut(); + let ctx = builder.main(CONTEXT_PHASE); + + // Encrypt plaintext + let computed_ciphertext = + self.gm_verification_inputs.gm_pk.encrypt(&self.gm_verification_inputs.plaintext); + + // Assign Private Inputs + let actual_ciphertext_assigned = self + .gm_verification_inputs + .ciphertext + .iter() + .map(|c| ctx.load_witness(F::from_bytes_le(&c.to_bytes_le()))) + .collect::>>(); + + let computed_ciphertext_assigned = computed_ciphertext + .iter() + .map(|c| ctx.load_witness(F::from_bytes_le(&c.to_bytes_le()))) + .collect::>>(); + + // Constrain equality + for (computed, actual) in + computed_ciphertext_assigned.iter().zip(actual_ciphertext_assigned.iter()) + { + ctx.constrain_equal(computed, actual); + } + } +} + +#[cfg(test)] +mod tests { + use std::{fs::File, marker::PhantomData}; + + use anyhow::{anyhow, Context, Ok, Result}; + use halo2_ecc::fields::FpStrategy; + use halo2_wasm::{CircuitConfig, Halo2Wasm}; + use probabilistic_encryption::{goldwasser_micali, key::PublicKey}; + use serde::{Deserialize, Serialize}; + + use super::{GmVerificationCircuit, GmVerificationInputs}; + + #[derive(Clone, Copy, Debug, Serialize, Deserialize)] + pub struct CircuitParams { + strategy: FpStrategy, + degree: u32, + num_advice: usize, + num_lookup_advice: usize, + num_fixed: usize, + lookup_bits: usize, + limb_bits: usize, + num_limbs: usize, + } + + fn mock_gm_encryption() -> Result { + let plaintext = b"hello world"; + + let (gm_pk, gm_sk) = + goldwasser_micali::generate_keys(8).expect("Failed to generate GM keys!"); + + let ciphertext = gm_pk.encrypt(plaintext); + + Ok(GmVerificationInputs { + gm_pk, + gm_sk, + ciphertext, + plaintext: plaintext.to_vec(), + _F_marker: PhantomData, + }) + } + + #[test] + fn test_gm_decryption_mock() -> Result<()> { + let path = "configs/gm_encryption.config"; + let circuit_params: CircuitConfig = serde_json::from_reader( + File::open(path) + .map_err(|e| anyhow!(e)) + .with_context(|| format!("The circuit config file does not exist: {}", path))?, + ) + .map_err(|e| anyhow!(e)) + .with_context(|| format!("Failed to read the circuit config file: {}", path))?; + + let gm_verification_inputs = mock_gm_encryption()?; + + let mut halo2_wasm = Halo2Wasm::new(); + + halo2_wasm.config(circuit_params); + + let mut circuit = GmVerificationCircuit::new(&halo2_wasm, gm_verification_inputs); + + circuit.verify_decryption(); + + halo2_wasm.mock(); + + Ok(()) + } + + #[test] + fn test_gm_encryption_mock() -> Result<()> { + let path = "configs/gm_encryption.config"; + let circuit_params: CircuitConfig = serde_json::from_reader( + File::open(path) + .map_err(|e| anyhow!(e)) + .with_context(|| format!("The circuit config file does not exist: {}", path))?, + ) + .map_err(|e| anyhow!(e)) + .with_context(|| format!("Failed to read the circuit config file: {}", path))?; + + let gm_verification_inputs = mock_gm_encryption()?; + + let mut halo2_wasm = Halo2Wasm::new(); + + halo2_wasm.config(circuit_params); + + let mut circuit = GmVerificationCircuit::new(&halo2_wasm, gm_verification_inputs); + + circuit.verify_encryption(); + + halo2_wasm.mock(); + + Ok(()) + } +} diff --git a/packages/circuits/src/lib.rs b/packages/circuits/src/lib.rs index f1d9bd8..b7a8c81 100644 --- a/packages/circuits/src/lib.rs +++ b/packages/circuits/src/lib.rs @@ -1,3 +1,2 @@ -#![allow(incomplete_features)] - -pub mod vanilla_circuits; +pub(crate) mod consts; +mod gm_encryption; diff --git a/packages/circuits/src/vanilla_circuits/is_zero.rs b/packages/circuits/src/vanilla_circuits/is_zero.rs deleted file mode 100644 index 674a9e5..0000000 --- a/packages/circuits/src/vanilla_circuits/is_zero.rs +++ /dev/null @@ -1,176 +0,0 @@ -use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner, Value}, - halo2curves::FieldExt, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Expression, Selector}, - poly::Rotation, -}; - -#[derive(Clone, Copy, Debug)] -pub struct IsZeroConfig { - x: Column, - y: Column, - out: Column, - selector: Selector, -} - -// Our circuit is going to look like: -// x | y | out | selector -// x_0 | y_0 | out_0 | s_0 -// x_1 | y_1 | out_1 | s_1 -// x_2 | y_2 | out_2 | s_2 -// ... -// with constraints: -// MUL_ADD1: s_i * (x_i * y_i + out_i - 1) = 0 -// MUL_0: s_i * (x_i * out_i) = 0 - -impl IsZeroConfig { - // it is standard practice to define everything where numbers are in a generic prime field `F` (`FieldExt` are the traits of a prime field) - // `meta` is provided by the halo2 backend, it is the api for specifying PLONKish arithmetization grid shape + storing circuit constraints in polynomial form - pub fn configure(meta: &mut ConstraintSystem) -> Self { - let [x, y, out] = [(); 3].map(|_| meta.advice_column()); - let selector = meta.selector(); - - // specify the columns that you may want to impose equality constraints on cells for (this may include fixed columns) - // `y` is some internal column that we don't expose, so we probably don't need equality constraints on it - [x, out].map(|column| meta.enable_equality(column)); - - // we create a single is_zero gate with the two constraints MUL_ADD1 and MUL_0 - meta.create_gate("ISZERO gate", |meta| { - // this gate will be applied AT EVERY ROW - - // we `query` for the `Expression` corresponding to the cell entry in a particular column at a relative row offset - let [x, y, out] = [x, y, out].map(|column| meta.query_advice(column, Rotation::cur())); - let s = meta.query_selector(selector); - - let xy = x.clone() * y; - - // specify all polynomial expressions that we require to equal zero - // `Expression` is basically an abstract container for the polynomial corresponding to a column; in particular it can't implement `Copy` so we need to clone it to pass rust ownership rules - vec![s.clone() * (xy + out.clone() - Expression::Constant(F::one())), s * x * out] - }); - - Self { x, y, out, selector } - } -} - -// we use the config to make a circuit: -// a circuit struct just holds the public/private inputs of a particular input for the circuit to compute -// slightly counterintuitive since the ZKCircuit is only created once, but it is then run multiple times with different inputs -// you should think that during actual ZKCircuit creation, these are just placeholders for the actual inputs -#[derive(Clone, Default)] -pub struct IsZeroCircuit { - // let's say our circuit wants to compute x == 0 ? 1 : 0 - pub x: Value, // Value is a wrapper for rust `Option` with some arithmetic operator overloading -} - -// now we implement the halo2 `Circuit` trait for our struct to actually make it a circuit -impl Circuit for IsZeroCircuit { - type Config = IsZeroConfig; // our earlier config - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - // you don't actually need to implement this if you don't want to - unimplemented!() - // the intention is you return a version of the circuit inputs where all private inputs are `Value::unknown()` to emphasize they shouldn't be known at circuit creation time - /* - Self { - a: Value::unknown(), - b: Value::unknown(), - } - */ - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - IsZeroConfig::configure::(meta) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - layouter.assign_region( - || "IsZero circuit, only using 1 region", - |mut region| { - // for performance, it is better to avoid the ? operator; see https://github.com/rust-lang/rust/issues/37939 - - // self.x, is the private inputs - // we need to first load it into our circuit - // | row | x | y | out | selector | - // | 0 | self.x | | | | - - // The API calls witness cells "advice" - // For mysterious reasons (weird rust trait issues), this function takes closures - let x = region.assign_advice( - || "a | annotation just for debugging", - config.x, - 0, - || self.x, - )?; - // by default, cells all have value 0 (except maybe the very last few rows, where there are "blinding factors" for zero knowledge) - - // We need to compute the witness for y = x == 0 ? 1 : x^{-1} - // x.value() is of type `Value` which means it can be either the underlying value or None, which leads to ugly code: - let y_val = - x.value().map(|x| if x == &F::zero() { F::one() } else { x.invert().unwrap() }); - // we assign this to the y column in row 0 - // | row | x | y | out | selector | - // | 0 | self.x | y_val | | | - let _y = region.assign_advice(|| "y", config.y, 0, || y_val)?; - - // Entirely separately we can just compute the witness for out = x == 0 ? 1 : 0 the normal way - let out_val = x.value().map(|x| if x == &F::zero() { F::one() } else { F::zero() }); - // | row | x | y | out | selector | - // | 0 | self.x | y_val | out_val | | - let out = region.assign_advice(|| "is_zero out", config.out, 0, || out_val)?; - dbg!(&out); - - // but wait, selector column defaults to all 0s, so no gates are actually turned "on" - // we need to turn our ISZERO gate on in row 0 only: - // | row | x | y | out | selector | - // | 0 | self.x | y_val | out_val | 1 | - config.selector.enable(&mut region, 0)?; - - // Let's say we want to use `out` somewhere else, here's how to do that: - // | row | x | y | out | selector | - // | 0 | self.x | y_val | out_val | 1 | - // | 1 | out | | | | - out.copy_advice(|| "copy out", &mut region, config.x, 1)?; - // this is exactly the same as the following two lines of code: - // let out_copy = region.assign_advice(|| "copy out", config.x, 1, || out_val)?; - // region.constrain_equal(out.cell(), out_copy.cell())?; - Ok(()) - }, - ) - } -} - -// cfg(test) tells rust to only compile this in test mode -#[cfg(test)] -mod test { - use halo2_proofs::{ - arithmetic::Field, circuit::Value, dev::MockProver, halo2curves::bn256::Fr, - }; - use rand::rngs::OsRng; - - use super::IsZeroCircuit; - - // this marks the function as a test - #[test] - fn test_is_zero_zero() { - let k = 5; - // when actually running a circuit, we specialize F to the scalar field of BN254, denoted Fr - let circuit = IsZeroCircuit { x: Value::known(Fr::from(0)) }; - - MockProver::run(k, &circuit, vec![]).unwrap().assert_satisfied(); - } - - #[test] - fn test_is_zero_random() { - let k = 5; - // when actually running a circuit, we specialize F to the scalar field of BN254, denoted Fr - let circuit = IsZeroCircuit { x: Value::known(Fr::random(OsRng)) }; - - MockProver::run(k, &circuit, vec![]).unwrap().assert_satisfied(); - } -} diff --git a/packages/circuits/src/vanilla_circuits/mod.rs b/packages/circuits/src/vanilla_circuits/mod.rs deleted file mode 100644 index 810e5d3..0000000 --- a/packages/circuits/src/vanilla_circuits/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! This module contains example circuits written using the raw Halo2 API. -//! -//! This is intended for those who want to learn about how to use the raw Halo2 API and PLONKish arithmetization in general. -//! If you are just getting started, we recommend looking at the `halo2-lib` examples mentioned in the README first. - -pub mod is_zero; -pub mod or; -pub mod standard_plonk; diff --git a/packages/circuits/src/vanilla_circuits/or.rs b/packages/circuits/src/vanilla_circuits/or.rs deleted file mode 100644 index 6bed3cc..0000000 --- a/packages/circuits/src/vanilla_circuits/or.rs +++ /dev/null @@ -1,181 +0,0 @@ -use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner, Value}, - halo2curves::FieldExt, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Selector}, - poly::Rotation, -}; - -#[derive(Clone, Copy, Debug)] -pub struct OrConfig { - witness: Column, - selector: Selector, -} - -// Our circuit is going to look like: -// | witness | selector | -// | a0 | s0 | -// | a1 | s1 | -// | a2 | s2 | -// ... -// with gate: -// s_i * (a_i + a_{i+1} - a_i * a_{i+1} - a_{i+2}) = 0 for all i - -impl OrConfig { - // it is standard practice to define everything where numbers are in a generic prime field `F` (`FieldExt` are the traits of a prime field) - // `meta` is provided by the halo2 backend, it is the api for specifying PLONKish arithmetization grid shape + storing circuit constraints in polynomial form - pub fn configure(meta: &mut ConstraintSystem) -> Self { - // create a single witness column - let witness = meta.advice_column(); - let selector = meta.selector(); - - // specify the columns that you may want to impose equality constraints on cells for (this may include fixed columns) - meta.enable_equality(witness); - - // we create a single OR gate - meta.create_gate("OR gate", |meta| { - // this gate will be applied AT EVERY ROW - // the relative offsets are specified using `Rotation` - - // we `query` for the `Expression` corresponding to the cell entry in a particular column at a relative row offset - let a = meta.query_advice(witness, Rotation::cur()); - let b = meta.query_advice(witness, Rotation(1)); // or Rotation::next() - let out = meta.query_advice(witness, Rotation(2)); // or Rotation::next() - let sel = meta.query_selector(selector); - - // specify all polynomial expressions that we require to equal zero - // `Expression` is basically an abstract container for the polynomial corresponding to a column; in particular it can't implement `Copy` so we need to clone it to pass rust ownership rules - vec![sel * (a.clone() + b.clone() - a * b - out)] - }); - - Self { witness, selector } - } -} - -// we use the config to make a circuit: -// a circuit struct just holds the public/private inputs of a particular input for the circuit to compute -// slightly counterintuitive since the ZKCircuit is only created once, but it is then run multiple times with different inputs -// you should think that during actual ZKCircuit creation, these are just placeholders for the actual inputs -#[derive(Clone, Default)] -pub struct OrCircuit { - // let's say our circuit wants to compute a | b - // ASSUME that the values of a,b are both in {0,1} - pub a: Value, // Value is a wrapper for rust `Option` with some arithmetic operator overloading - pub b: Value, -} - -// now we implement the halo2 `Circuit` trait for our struct to actually make it a circuit -impl Circuit for OrCircuit { - type Config = OrConfig; // our earlier config - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - // you don't actually need to implement this if you don't want to - unimplemented!() - // the intention is you return a version of the circuit inputs where all private inputs are `Value::unknown()` to emphasize they shouldn't be known at circuit creation time - /* - Self { - a: Value::unknown(), - b: Value::unknown(), - } - */ - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - OrConfig::configure::(meta) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - layouter.assign_region( - || "Or circuit, only using 1 region", - |mut region| { - // for performance, it is better to avoid the ? operator; see https://github.com/rust-lang/rust/issues/37939 - - // self.a, self.b are the two private inputs - // we need to first load them into our circuit - // | row | witness | selector | - // | 0 | a | | - // | 1 | b | | - - // The API calls witness cells "advice" - // For mysterious reasons (weird rust trait issues), this function takes closures - let a = region.assign_advice( - || "a | annotation just for debugging", - config.witness, - 0, - || self.a, - )?; - let b = region.assign_advice( - || "b | annotation just for debugging", - config.witness, - 1, - || self.b, - )?; - // by default, cells all have value 0 (except maybe the very last few rows, where there are "blinding factors" for zero knowledge) - - // we need to compute the witness for a | b on our own somehow - // let's emphasize this can be done in a different way than the gate: - // an annoyance: a.value(), b.value() are both `Value` meaning they can be either the value itself or None, so here comes ugly code: - let out_val = a.value().zip(b.value()).map(|(a, b)| { - // now a,b are both type &F - let [a, b] = [a, b].map(|x| { - if x == &F::one() { - true - } else { - assert_eq!(x, &F::zero()); // this is just an assumption check, not a circuit constraint - false - } - }); - // now a,b are bool - let out = a || b; - // we return the bool as an F value - F::from(out) - }); - // out_val is now type `Value` - // we put this in row 2: - // | row | witness | selector | - // | 0 | a | | - // | 1 | b | | - // | 2 | a || b | | - let _out = - region.assign_advice(|| "a OR b output", config.witness, 2, || out_val)?; - - // but wait, selector column defaults to all 0s, so no gates are actually turned "on" - // we need to turn our OR gate on in row 0 only: - // | row | witness | selector | - // | 0 | a | 1 | - // | 1 | b | 0 | - // | 2 | a || b | 0 | - config.selector.enable(&mut region, 0)?; - - // Now the circuit will constrain `out_val` must equal `a + b - a * b` using the OR gate - // For debugging you can also print out the literally cell containing `out_val`: - println!("out cell: {_out:?}"); - // for just the value: - // println!("out value: {:?}", _out.value()); - Ok(()) - }, - ) - } -} - -// cfg(test) tells rust to only compile this in test mode -#[cfg(test)] -mod test { - use halo2_proofs::{circuit::Value, dev::MockProver, halo2curves::bn256::Fr}; - - use super::OrCircuit; - - // this marks the function as a test - #[test] - fn test_or() { - let k = 5; - // when actually running a circuit, we specialize F to the scalar field of BN254, denoted Fr - let circuit = OrCircuit { a: Value::known(Fr::one()), b: Value::known(Fr::one()) }; - - MockProver::run(k, &circuit, vec![]).unwrap().assert_satisfied(); - } -} diff --git a/packages/circuits/src/vanilla_circuits/standard_plonk.rs b/packages/circuits/src/vanilla_circuits/standard_plonk.rs deleted file mode 100644 index 8f65a12..0000000 --- a/packages/circuits/src/vanilla_circuits/standard_plonk.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::marker::PhantomData; - -use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner, Value}, - halo2curves::FieldExt, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed}, - poly::Rotation, -}; - -#[derive(Clone, Copy)] -// it is standard practice to define everything where numbers are in a generic prime field `F` (`FieldExt` are the traits of a prime field) -pub struct StandardPlonkConfig { - a: Column, - b: Column, - c: Column, - #[allow(dead_code)] - q_a: Column, - #[allow(dead_code)] - q_b: Column, - q_c: Column, - q_ab: Column, - constant: Column, - _marker: PhantomData, -} - -impl StandardPlonkConfig { - pub fn configure(meta: &mut ConstraintSystem) -> Self { - // these are the 3 advice columns - let [a, b, c] = [(); 3].map(|_| meta.advice_column()); - // these are the fixed columns - let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); - - // specify the columns that you may want to impose equality constraints on cells for (this may include fixed columns) - [a, b, c].map(|column| meta.enable_equality(column)); - - // this is the standard PLONK gate - meta.create_gate("q_a·a + q_b·b + q_c·c + q_ab·a·b + constant = 0", |meta| { - // this gate will be applied AT EVERY ROW - // the relative offsets are specified using `Rotation` - - // we `query` for the `Expression` corresponding to the cell entry in a particular column at a relative row offset - let [a, b, c] = [a, b, c].map(|column| meta.query_advice(column, Rotation::cur())); - let [q_a, q_b, q_c, q_ab, constant] = [q_a, q_b, q_c, q_ab, constant] - .map(|column| meta.query_fixed(column, Rotation::cur())); - - // specify all polynomial expressions that we require to equal zero - vec![q_a * a.clone() + q_b * b.clone() + q_c * c + q_ab * a * b + constant] - }); - - StandardPlonkConfig { a, b, c, q_a, q_b, q_c, q_ab, constant, _marker: PhantomData } - } - - // Config is essentially synonymous with Chip, so we want to build some functionality into this Chip if we want -} - -// we use the config to make a circuit: -#[derive(Clone, Default)] -pub struct StandardPlonk { - pub x: Value, -} - -impl Circuit for StandardPlonk { - type Config = StandardPlonkConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - StandardPlonkConfig::configure(meta) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - layouter.assign_region( - || "", - |mut region| { - // For an explanation of what the rust ? operator does, see https://doc.rust-lang.org/std/result/#the-question-mark-operator- - let x = region.assign_advice( - || "add annotation if you want", - config.a, - 0, - || self.x, - )?; - // by default, cells all have value 0 (except maybe the very last few rows, where there are "blinding factors" for zero knowledge) - - // square x - // row 1: | x | x | x^2 | 0 | 0 | -1 | 1 | 0 | - x.copy_advice(|| "", &mut region, config.a, 1)?; - x.copy_advice(|| "", &mut region, config.b, 1)?; - let val = x.value().map(|x| *x * x); - region.assign_advice(|| "", config.c, 1, || val)?; - region.assign_fixed(|| "", config.q_c, 1, || Value::known(-F::one()))?; - region.assign_fixed(|| "", config.q_ab, 1, || Value::known(F::one()))?; - - // x^2 + 72 - let c = F::from(72); - let val = x.value().map(|x| *x * x + c); - x.copy_advice(|| "", &mut region, config.a, 2)?; - x.copy_advice(|| "", &mut region, config.b, 2)?; - region.assign_advice(|| "", config.c, 2, || val)?; - region.assign_fixed(|| "", config.q_c, 2, || Value::known(-F::one()))?; - region.assign_fixed(|| "", config.q_ab, 2, || Value::known(F::one()))?; - region.assign_fixed(|| "", config.constant, 2, || Value::known(c))?; - - Ok(()) - }, - ) - } -} - -#[cfg(test)] -mod test { - use halo2_proofs::{ - arithmetic::Field, circuit::Value, dev::MockProver, halo2curves::bn256::Fr, - }; - use rand::rngs::OsRng; - - use super::StandardPlonk; - - #[test] - fn test_standard_plonk() { - let k = 5; - let circuit = StandardPlonk { x: Value::known(Fr::random(OsRng)) }; - - MockProver::run(k, &circuit, vec![]).unwrap().assert_satisfied(); - } -} diff --git a/packages/probabilistic-encryption b/packages/probabilistic-encryption new file mode 160000 index 0000000..74f038f --- /dev/null +++ b/packages/probabilistic-encryption @@ -0,0 +1 @@ +Subproject commit 74f038fafa5ddc52af0627f65a9534af304ad3ed