diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8035a4e7..354ec6ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: Tests on: push: - branches: ["main", "release-0.3.0"] + branches: ["main", "develop", "community-edition"] pull_request: - branches: ["main", "release-0.3.0"] + branches: ["main", "develop", "community-edition"] env: CARGO_TERM_COLOR: always diff --git a/README.md b/README.md index 34a27e8b..ff9ee93e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # halo2-lib -This repository aims to provide basic primitives for writing zero-knowledge proof circuits using the [Halo 2](https://zcash.github.io/halo2/) proving stack. +This repository aims to provide basic primitives for writing zero-knowledge proof circuits using the [Halo 2](https://zcash.github.io/halo2/) proving stack. To discuss or collaborate, join our community on [Telegram](https://t.me/halo2lib). ## Getting Started @@ -130,7 +130,7 @@ The test config file locations are (relative to `halo2-ecc` directory): | `test_msm` | `src/bn254/configs/msm_circuit.config` | | `test_pairing` | `src/bn254/configs/pairing_circuit.config` | -### Benchmarks +## Benchmarks We have tests that are actually benchmarks using the production Halo2 prover. As mentioned [above](#Configurable-Circuits), there are different configurations for each circuit that lead to _very_ different proving times. The following benchmarks will take a list of possible configurations and benchmark each one. The results are saved in a file in the `results` directory. We currently supply the configuration lists, which should provide optimal configurations for a given circuit degree `k` (however you can check versus the stdout suggestions to see if they really are optimal!). @@ -172,7 +172,7 @@ cargo bench --bench fp_mul This run the same proof generation over 10 runs and collect the average. Each circuit has a fixed configuration chosen for optimal speed. These benchmarks are mostly for use in performance optimization. -## Secp256k1 ECDSA +### Secp256k1 ECDSA We provide benchmarks for ECDSA signature verification for the Secp256k1 curve on several different machines. All machines only use CPUs. @@ -215,7 +215,7 @@ The other columns provide information about the [PLONKish arithmetization](https The r6a has a higher clock speed than the r6g. -## BN254 Pairing +### BN254 Pairing We provide benchmarks of the optimal Ate pairing for BN254 on several different machines. All machines only use CPUs. @@ -258,7 +258,7 @@ The other columns provide information about the [PLONKish arithmetization](https The r6a has a higher clock speed than the r6g. We hypothesize that the Apple Silicon integrated memory leads to the faster performance on the M2 Max. -## BN254 MSM +### BN254 MSM We provide benchmarks of multi-scalar multiplication (MSM, multi-exp) with a batch size of `100` for BN254. @@ -275,3 +275,17 @@ cargo test --release --no-default-features --features "halo2-axiom, mimalloc" -- | 19 | 20 | 3 | 1 | 32.6s | | 20 | 11 | 2 | 1 | 41.3s | | 21 | 6 | 1 | 1 | 51.9s | + +## Projects built with `halo2-lib` + +- [Axiom](https://github.com/axiom-crypto/axiom-eth) -- Prove facts about Ethereum on-chain data via aggregate block header, account, and storage proofs. +- [Proof of Email](https://github.com/zkemail/) -- Prove facts about emails with the same trust assumption as the email domain. + - [halo2-regex](https://github.com/zkemail/halo2-regex) + - [halo2-zk-email](https://github.com/zkemail/halo2-zk-email) + - [halo2-base64](https://github.com/zkemail/halo2-base64) + - [halo2-rsa](https://github.com/zkemail/halo2-rsa/tree/feat/new_bigint) +- [halo2-fri-gadget](https://github.com/maxgillett/halo2-fri-gadget) -- FRI verifier in halo2. +- [eth-voice-recovery](https://github.com/SoraSuegami/voice_recovery_circuit) +- [zkevm tx-circuit](https://github.com/scroll-tech/zkevm-circuits/tree/develop/zkevm-circuits/src/tx_circuit) +- [webauthn-halo2](https://github.com/zkwebauthn/webauthn-halo2) -- Proving and verifying WebAuthn with halo2. +- [Fixed Point Arithmetic](https://github.com/DCMMC/halo2-scaffold/tree/main/src/gadget) -- Fixed point arithmetic library in halo2. diff --git a/halo2-base/Cargo.toml b/halo2-base/Cargo.toml index 33799495..66c26e91 100644 --- a/halo2-base/Cargo.toml +++ b/halo2-base/Cargo.toml @@ -17,7 +17,7 @@ serde_json = "1.0" log = "0.4" # Use Axiom's custom halo2 monorepo for faster proving when feature = "halo2-axiom" is on -halo2_proofs_axiom = { git = "https://github.com/axiom-crypto/halo2.git", branch = "axiom/dev", package = "halo2_proofs", optional = true } +halo2_proofs_axiom = { git = "https://github.com/axiom-crypto/halo2.git", branch = "main", package = "halo2_proofs", optional = true } # Use PSE halo2 and halo2curves for compatibility when feature = "halo2-pse" is on halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2023_02_02", optional = true } diff --git a/halo2-base/src/gates/tests/general.rs b/halo2-base/src/gates/tests/general.rs index 002130fe..1c9924d5 100644 --- a/halo2-base/src/gates/tests/general.rs +++ b/halo2-base/src/gates/tests/general.rs @@ -3,10 +3,7 @@ use crate::gates::{ flex_gate::{GateChip, GateInstructions}, range::{RangeChip, RangeInstructions}, }; -use crate::halo2_proofs::{ - dev::MockProver, - halo2curves::bn256::Fr, -}; +use crate::halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; use crate::utils::{BigPrimeField, ScalarField}; use crate::{Context, QuantumCell::Constant}; use ff::Field; diff --git a/halo2-base/src/gates/tests/idx_to_indicator.rs b/halo2-base/src/gates/tests/idx_to_indicator.rs index 0b0e6dce..33cbaa94 100644 --- a/halo2-base/src/gates/tests/idx_to_indicator.rs +++ b/halo2-base/src/gates/tests/idx_to_indicator.rs @@ -4,17 +4,17 @@ use crate::{ GateChip, GateInstructions, }, halo2_proofs::{ + halo2curves::bn256::Fr, plonk::keygen_pk, plonk::{keygen_vk, Assigned}, poly::kzg::commitment::ParamsKZG, - halo2curves::bn256::Fr, }, - utils::testing::{gen_proof, check_proof}, + utils::testing::{check_proof, gen_proof}, QuantumCell::Witness, }; use ff::Field; use itertools::Itertools; -use rand::{thread_rng, Rng, rngs::OsRng}; +use rand::{rngs::OsRng, thread_rng, Rng}; // soundness checks for `idx_to_indicator` function fn test_idx_to_indicator_gen(k: u32, len: usize) { diff --git a/halo2-base/src/lib.rs b/halo2-base/src/lib.rs index 5fd18ed7..676a742c 100644 --- a/halo2-base/src/lib.rs +++ b/halo2-base/src/lib.rs @@ -1,7 +1,7 @@ //! Base library to build Halo2 circuits. -#![allow(incomplete_features)] #![feature(generic_const_exprs)] #![feature(const_cmp)] +#![allow(incomplete_features)] #![feature(stmt_expr_attributes)] #![feature(trait_alias)] #![deny(clippy::perf)] @@ -41,10 +41,10 @@ use utils::ScalarField; /// Module that contains the main API for creating and working with circuits. pub mod gates; -/// Utility functions for converting between different types of field elements. -pub mod utils; /// Module for SafeType which enforce value range and realted functions. pub mod safe_types; +/// Utility functions for converting between different types of field elements. +pub mod utils; /// Constant representing whether the Layouter calls `synthesize` once just to get region shape. #[cfg(feature = "halo2-axiom")] diff --git a/halo2-base/src/safe_types/tests.rs b/halo2-base/src/safe_types/tests.rs index 1f635053..14480fdd 100644 --- a/halo2-base/src/safe_types/tests.rs +++ b/halo2-base/src/safe_types/tests.rs @@ -1,19 +1,12 @@ -use crate::halo2_proofs::{ - halo2curves::bn256::{Bn256, Fr, G1Affine}, - plonk::{create_proof, verify_proof, Circuit, ProvingKey, VerifyingKey}, - poly::commitment::ParamsProver, - poly::kzg::{ - commitment::KZGCommitmentScheme, commitment::ParamsKZG, multiopen::ProverSHPLONK, - multiopen::VerifierSHPLONK, strategy::SingleStrategy, - }, - transcript::{ - Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, - }, +use crate::{ + halo2_proofs::{halo2curves::bn256::Fr, poly::kzg::commitment::ParamsKZG}, + utils::testing::{check_proof, gen_proof}, }; +use super::*; use crate::{ gates::{ - builder::{RangeCircuitBuilder, GateThreadBuilder}, + builder::{GateThreadBuilder, RangeCircuitBuilder}, RangeChip, }, halo2_proofs::{ @@ -21,57 +14,17 @@ use crate::{ plonk::{keygen_vk, Assigned}, }, }; -use rand::rngs::OsRng; use itertools::Itertools; -use super::*; +use rand::rngs::OsRng; use std::env; -/// helper function to generate a proof with real prover -pub fn gen_proof( - params: &ParamsKZG, - pk: &ProvingKey, - circuit: impl Circuit, -) -> Vec { - let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - create_proof::< - KZGCommitmentScheme, - ProverSHPLONK<'_, Bn256>, - Challenge255<_>, - _, - Blake2bWrite, G1Affine, _>, - _, - >(params, pk, &[circuit], &[&[]], OsRng, &mut transcript) - .expect("prover should not fail"); - transcript.finalize() -} - -/// helper function to verify a proof -pub fn check_proof( - params: &ParamsKZG, - vk: &VerifyingKey, - proof: &[u8], +// soundness checks for `raw_bytes_to` function +fn test_raw_bytes_to_gen( + k: u32, + raw_bytes: &[Fr], + outputs: &[Fr], expect_satisfied: bool, ) { - let verifier_params = params.verifier_params(); - let strategy = SingleStrategy::new(params); - let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(proof); - let res = verify_proof::< - KZGCommitmentScheme, - VerifierSHPLONK<'_, Bn256>, - Challenge255, - Blake2bRead<&[u8], G1Affine, Challenge255>, - SingleStrategy<'_, Bn256>, - >(verifier_params, vk, strategy, &[&[]], &mut transcript); - - if expect_satisfied { - assert!(res.is_ok()); - } else { - assert!(res.is_err()); - } -} - -// soundness checks for `raw_bytes_to` function -fn test_raw_bytes_to_gen(k: u32, raw_bytes: &[Fr], outputs: &[Fr], expect_satisfied: bool) { // first create proving and verifying key let mut builder = GateThreadBuilder::::keygen(); let lookup_bits = 3; @@ -79,13 +32,15 @@ fn test_raw_bytes_to_gen(k: let range_chip = RangeChip::::default(lookup_bits); let safe_type_chip = SafeTypeChip::new(&range_chip); - let dummy_raw_bytes = builder.main(0).assign_witnesses((0..raw_bytes.len()).map(|_| Fr::zero()).collect::>()); + let dummy_raw_bytes = builder + .main(0) + .assign_witnesses((0..raw_bytes.len()).map(|_| Fr::zero()).collect::>()); - let safe_value = safe_type_chip.raw_bytes_to::( - builder.main(0), - dummy_raw_bytes); + let safe_value = + safe_type_chip.raw_bytes_to::(builder.main(0), dummy_raw_bytes); // get the offsets of the safe value cells for later 'pranking' - let safe_value_offsets = safe_value.value().iter().map(|v| v.cell.unwrap().offset).collect::>(); + let safe_value_offsets = + safe_value.value().iter().map(|v| v.cell.unwrap().offset).collect::>(); // set env vars builder.config(k as usize, Some(9)); let circuit = RangeCircuitBuilder::keygen(builder); @@ -101,11 +56,10 @@ fn test_raw_bytes_to_gen(k: let mut builder = GateThreadBuilder::::prover(); let range_chip = RangeChip::::default(lookup_bits); let safe_type_chip = SafeTypeChip::new(&range_chip); - + let assigned_raw_bytes = builder.main(0).assign_witnesses(inputs.to_vec()); - safe_type_chip.raw_bytes_to::( - builder.main(0), - assigned_raw_bytes); + safe_type_chip + .raw_bytes_to::(builder.main(0), assigned_raw_bytes); // prank the safe value cells for (offset, witness) in safe_value_offsets.iter().zip_eq(outputs) { builder.main(0).advice[*offset] = Assigned::::Trivial(*witness); @@ -134,37 +88,70 @@ fn test_raw_bytes_to_uint256() { const TOTAL_BITS: usize = SafeUint256::::TOTAL_BITS; let k = 11; // [0x0; 32] -> [0x0, 0x0] - test_raw_bytes_to_gen::(k, &[Fr::from(0); 32], &[Fr::from(0), Fr::from(0)], true); test_raw_bytes_to_gen::( - k, - &[[Fr::from(1)].as_slice(), [Fr::from(0); 31].as_slice()].concat(), - &[Fr::from(1), Fr::from(0)], true); + k, + &[Fr::from(0); 32], + &[Fr::from(0), Fr::from(0)], + true, + ); + test_raw_bytes_to_gen::( + k, + &[[Fr::from(1)].as_slice(), [Fr::from(0); 31].as_slice()].concat(), + &[Fr::from(1), Fr::from(0)], + true, + ); // [0x1, 0x2] + [0x0; 30] -> [0x201, 0x0] test_raw_bytes_to_gen::( - k, - &[[Fr::from(1), Fr::from(2)].as_slice(), [Fr::from(0); 30].as_slice()].concat(), - &[Fr::from(0x201), Fr::from(0)], true); + k, + &[[Fr::from(1), Fr::from(2)].as_slice(), [Fr::from(0); 30].as_slice()].concat(), + &[Fr::from(0x201), Fr::from(0)], + true, + ); // [[0xff; 32] -> [2^248 - 1, 0xff] test_raw_bytes_to_gen::( - k, - &[Fr::from(0xff); 32], - &[Fr::from_raw([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffff]), Fr::from(0xff)], true); + k, + &[Fr::from(0xff); 32], + &[ + Fr::from_raw([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffff, + ]), + Fr::from(0xff), + ], + true, + ); // invalid raw_bytes, last bytes > 0xff test_raw_bytes_to_gen::( - k, - &[[Fr::from(0); 31].as_slice(), [Fr::from(0x1ff)].as_slice()].concat(), - &[Fr::from(0), Fr::from(0xff)], false); + k, + &[[Fr::from(0); 31].as_slice(), [Fr::from(0x1ff)].as_slice()].concat(), + &[Fr::from(0), Fr::from(0xff)], + false, + ); // 0xff != 0xff00000000000000000000000000000000000000000000000000000000000000 test_raw_bytes_to_gen::( - k, - &[[Fr::from(0xff)].as_slice(), [Fr::from(0); 31].as_slice()].concat(), - &[Fr::from(0), Fr::from(0xff)], false); + k, + &[[Fr::from(0xff)].as_slice(), [Fr::from(0); 31].as_slice()].concat(), + &[Fr::from(0), Fr::from(0xff)], + false, + ); // outputs overflow test_raw_bytes_to_gen::( - k, - &[Fr::from(0xff); 32], - &[Fr::from_raw([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffff]), Fr::from(0x1ff)], false); + k, + &[Fr::from(0xff); 32], + &[ + Fr::from_raw([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffff, + ]), + Fr::from(0x1ff), + ], + false, + ); } #[test] @@ -176,30 +163,40 @@ fn test_raw_bytes_to_uint64() { test_raw_bytes_to_gen::(k, &[Fr::from(0); 8], &[Fr::from(0)], true); // [0x1, 0x2] + [0x0; 6] -> [0x201] test_raw_bytes_to_gen::( - k, - &[[Fr::from(1), Fr::from(2)].as_slice(), [Fr::from(0); 6].as_slice()].concat(), - &[Fr::from(0x201)], true); + k, + &[[Fr::from(1), Fr::from(2)].as_slice(), [Fr::from(0); 6].as_slice()].concat(), + &[Fr::from(0x201)], + true, + ); // [[0xff; 8] -> [2^64-1] test_raw_bytes_to_gen::( - k, - &[Fr::from(0xff); 8], - &[Fr::from(0xffffffffffffffff)], true); + k, + &[Fr::from(0xff); 8], + &[Fr::from(0xffffffffffffffff)], + true, + ); // invalid raw_bytes, last bytes > 0xff test_raw_bytes_to_gen::( - k, - &[[Fr::from(0); 7].as_slice(), [Fr::from(0x1ff)].as_slice()].concat(), - &[Fr::from(0xff00000000000000)], false); + k, + &[[Fr::from(0); 7].as_slice(), [Fr::from(0x1ff)].as_slice()].concat(), + &[Fr::from(0xff00000000000000)], + false, + ); // 0xff != 0xff00000000000000000000000000000000000000000000000000000000000000 test_raw_bytes_to_gen::( - k, - &[[Fr::from(0xff)].as_slice(), [Fr::from(0); 7].as_slice()].concat(), - &[Fr::from(0xff00000000000000)], false); + k, + &[[Fr::from(0xff)].as_slice(), [Fr::from(0); 7].as_slice()].concat(), + &[Fr::from(0xff00000000000000)], + false, + ); // outputs overflow test_raw_bytes_to_gen::( - k, - &[Fr::from(0xff); 8], - &[Fr::from_raw([0xffffffffffffffff, 0x1, 0x0, 0x0])], false); + k, + &[Fr::from(0xff); 8], + &[Fr::from_raw([0xffffffffffffffff, 0x1, 0x0, 0x0])], + false, + ); } #[test] @@ -208,35 +205,52 @@ fn test_raw_bytes_to_bytes32() { const TOTAL_BITS: usize = SafeBytes32::::TOTAL_BITS; let k = 10; // [0x0; 32] -> [0x0; 32] - test_raw_bytes_to_gen::(k, &[Fr::from(0); 32], &[Fr::from(0); 32], true); test_raw_bytes_to_gen::( - k, - &[[Fr::from(1)].as_slice(), [Fr::from(0); 31].as_slice()].concat(), - &[[Fr::from(1)].as_slice(), [Fr::from(0); 31].as_slice()].concat(), true); + k, + &[Fr::from(0); 32], + &[Fr::from(0); 32], + true, + ); + test_raw_bytes_to_gen::( + k, + &[[Fr::from(1)].as_slice(), [Fr::from(0); 31].as_slice()].concat(), + &[[Fr::from(1)].as_slice(), [Fr::from(0); 31].as_slice()].concat(), + true, + ); // [0x1, 0x2] + [0x0; 30] -> [0x201, 0x0] test_raw_bytes_to_gen::( - k, - &[[Fr::from(1), Fr::from(2)].as_slice(), [Fr::from(0); 30].as_slice()].concat(), - &[[Fr::from(1), Fr::from(2)].as_slice(), [Fr::from(0); 30].as_slice()].concat(), true); + k, + &[[Fr::from(1), Fr::from(2)].as_slice(), [Fr::from(0); 30].as_slice()].concat(), + &[[Fr::from(1), Fr::from(2)].as_slice(), [Fr::from(0); 30].as_slice()].concat(), + true, + ); // [[0xff; 32] -> [2^248 - 1, 0xff] test_raw_bytes_to_gen::( - k, - &[Fr::from(0xff); 32], - &[Fr::from(0xff); 32], true); + k, + &[Fr::from(0xff); 32], + &[Fr::from(0xff); 32], + true, + ); // invalid raw_bytes, last bytes > 0xff test_raw_bytes_to_gen::( k, - &[[Fr::from(0); 31].as_slice(), [Fr::from(0x1ff)].as_slice()].concat(), - &[[Fr::from(0); 31].as_slice(), [Fr::from(0x1ff)].as_slice()].concat(), false); + &[[Fr::from(0); 31].as_slice(), [Fr::from(0x1ff)].as_slice()].concat(), + &[[Fr::from(0); 31].as_slice(), [Fr::from(0x1ff)].as_slice()].concat(), + false, + ); // 0xff != 0xff00000000000000000000000000000000000000000000000000000000000000 test_raw_bytes_to_gen::( - k, + k, &[[Fr::from(0xff)].as_slice(), [Fr::from(0); 31].as_slice()].concat(), - &[[Fr::from(0); 31].as_slice(), [Fr::from(0xff)].as_slice()].concat(), false); + &[[Fr::from(0); 31].as_slice(), [Fr::from(0xff)].as_slice()].concat(), + false, + ); // outputs overflow test_raw_bytes_to_gen::( - k, - &[Fr::from(0xff); 32], - &[Fr::from(0x1ff); 32], false); -} \ No newline at end of file + k, + &[Fr::from(0xff); 32], + &[Fr::from(0x1ff); 32], + false, + ); +} diff --git a/halo2-ecc/src/ecc/mod.rs b/halo2-ecc/src/ecc/mod.rs index d63b4c4a..a196e039 100644 --- a/halo2-ecc/src/ecc/mod.rs +++ b/halo2-ecc/src/ecc/mod.rs @@ -246,6 +246,9 @@ pub fn ec_sub_unequal>( /// Constrains `P != -Q` but allows `P == Q`, in which case output is (0,0). /// For Weierstrass curves only. +/// +/// Assumptions +/// # Neither P or Q is the point at infinity pub fn ec_sub_strict>( chip: &FC, ctx: &mut Context, @@ -496,7 +499,8 @@ where assert!(!scalar.is_empty()); assert!((max_bits as u64) <= modulus::().bits()); assert!(window_bits != 0); - + multi_scalar_multiply::(chip, ctx, &[P], vec![scalar], max_bits, window_bits) + /* let total_bits = max_bits * scalar.len(); let num_windows = (total_bits + window_bits - 1) / window_bits; let rounded_bitlen = num_windows * window_bits; @@ -577,6 +581,7 @@ where // if at the end, return identity point (0,0) if still not started let zero = chip.load_constant(ctx, FC::FieldType::zero()); ec_select(chip, ctx, curr_point, EcPoint::new(zero.clone(), zero), *is_started.last().unwrap()) + */ } /// Checks that `P` is indeed a point on the elliptic curve `C`. @@ -729,7 +734,7 @@ where ctx, &rand_start_vec[k], &rand_start_vec[0], - k >= F::CAPACITY as usize, + true, // k >= F::CAPACITY as usize, // this assumed random points on `C` were of prime order equal to modulus of `F`. Since this is easily missed, we turn on strict mode always ); let mut curr_point = start_point.clone();