From f2101697c26420190537fd9929beecb61c14a87b Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Fri, 29 Nov 2024 10:24:30 +0100 Subject: [PATCH 01/20] Remove book and halo2. Move everything to parent repo. --- halo2_proofs/CHANGELOG.md => CHANGELOG.md | 0 Cargo.toml | 112 +- README.md | 10 +- .../benches => benches}/arithmetic.rs | 0 .../benches => benches}/commit_zk.rs | 0 .../benches => benches}/dev_lookup.rs | 0 {halo2_proofs/benches => benches}/fft.rs | 0 .../benches => benches}/hashtocurve.rs | 0 {halo2_proofs/benches => benches}/plonk.rs | 0 book/.gitignore | 1 - book/Makefile | 10 - book/book.toml | 14 - book/edithtml.sh | 28 - book/macros.txt | 76 - book/src/IDENTIFIERS.json | 25 - book/src/README.md | 1 - book/src/SUMMARY.md | 47 - book/src/background.md | 7 - book/src/background/curves.md | 297 --- book/src/background/fields.md | 307 --- book/src/background/groups.md | 94 - book/src/background/pc-ipa.md | 80 - book/src/background/plonkish.md | 82 - book/src/background/polynomials.md | 289 --- book/src/background/recursion.md | 26 - book/src/concepts.md | 5 - book/src/concepts/arithmetization.md | 57 - book/src/concepts/chips.md | 87 - book/src/concepts/gadgets.md | 25 - book/src/concepts/proofs.md | 91 - book/src/design.md | 17 - book/src/design/gadgets.md | 7 - book/src/design/gadgets/decomposition.md | 76 - book/src/design/gadgets/ecc.md | 50 - book/src/design/gadgets/ecc/addition.md | 359 ---- .../gadgets/ecc/fixed-base-scalar-mul.md | 189 -- .../design/gadgets/ecc/var-base-scalar-mul.md | 394 ---- .../design/gadgets/ecc/witnessing-points.md | 35 - book/src/design/gadgets/sha256.md | 65 - .../gadgets/sha256/bit_reassignment.png | Bin 36660 -> 0 bytes .../src/design/gadgets/sha256/compression.png | Bin 42544 -> 0 bytes .../src/design/gadgets/sha256/low_sigma_0.png | Bin 39507 -> 0 bytes .../src/design/gadgets/sha256/low_sigma_1.png | Bin 51433 -> 0 bytes book/src/design/gadgets/sha256/table16.md | 899 --------- .../src/design/gadgets/sha256/upp_sigma_0.png | Bin 46020 -> 0 bytes .../src/design/gadgets/sha256/upp_sigma_1.png | Bin 42804 -> 0 bytes book/src/design/gadgets/sinsemilla.md | 262 --- .../design/gadgets/sinsemilla/merkle-crh.md | 144 -- book/src/design/implementation.md | 1 - book/src/design/implementation/fields.md | 97 - book/src/design/implementation/proofs.md | 89 - .../implementation/selector-combining.md | 116 -- book/src/design/protocol.md | 828 -------- book/src/design/proving-system.md | 74 - .../proving-system/circuit-commitments.md | 114 -- book/src/design/proving-system/comparison.md | 55 - .../design/proving-system/inner-product.md | 11 - book/src/design/proving-system/lookup.md | 177 -- .../proving-system/multipoint-opening.md | 94 - .../proving-system/permutation-diagram.png | Bin 82914 -> 0 bytes .../proving-system/permutation-diagram.svg | 1743 ----------------- book/src/design/proving-system/permutation.md | 272 --- book/src/design/proving-system/vanishing.md | 79 - book/src/user.md | 5 - book/src/user/dev-tools.md | 114 -- book/src/user/experimental-features.md | 140 -- book/src/user/gadgets.md | 1 - book/src/user/lookup-tables.md | 11 - book/src/user/simple-example.md | 86 - book/src/user/tips-and-tricks.md | 71 - .../examples => examples}/circuit-layout.rs | 0 .../examples => examples}/proof-size.rs | 0 .../examples => examples}/serialization.rs | 0 .../examples => examples}/shuffle.rs | 0 .../examples => examples}/shuffle_api.rs | 0 .../examples => examples}/simple-example.rs | 0 .../examples => examples}/two-chip.rs | 0 .../examples => examples}/vector-mul.rs | 0 .../vector-ops-unblinded.rs | 0 halo2/CHANGELOG.md | 15 - halo2/Cargo.toml | 25 - halo2/katex-header.html | 15 - halo2/src/lib.rs | 7 - halo2_proofs/Cargo.toml | 109 -- halo2_proofs/README.md | 37 - halo2_proofs/katex-header.html | 15 - .../plonk/assigned.txt | 0 .../plonk/circuit/compress_selectors.txt | 0 {halo2_proofs/src => src}/arithmetic.rs | 0 {halo2_proofs/src => src}/circuit.rs | 0 .../src => src}/circuit/floor_planner.rs | 0 .../circuit/floor_planner/single_pass.rs | 0 .../src => src}/circuit/floor_planner/v1.rs | 0 .../circuit/floor_planner/v1/strategy.rs | 0 {halo2_proofs/src => src}/circuit/layouter.rs | 0 .../src => src}/circuit/table_layouter.rs | 0 {halo2_proofs/src => src}/circuit/value.rs | 0 {halo2_proofs/src => src}/dev.rs | 0 {halo2_proofs/src => src}/dev/cost.rs | 0 {halo2_proofs/src => src}/dev/cost_model.rs | 0 {halo2_proofs/src => src}/dev/failure.rs | 0 .../src => src}/dev/failure/emitter.rs | 0 {halo2_proofs/src => src}/dev/gates.rs | 0 {halo2_proofs/src => src}/dev/graph.rs | 0 {halo2_proofs/src => src}/dev/graph/layout.rs | 0 {halo2_proofs/src => src}/dev/metadata.rs | 0 {halo2_proofs/src => src}/dev/tfp.rs | 0 {halo2_proofs/src => src}/dev/util.rs | 0 {halo2_proofs/src => src}/helpers.rs | 0 {halo2_proofs/src => src}/lib.rs | 0 {halo2_proofs/src => src}/multicore.rs | 0 {halo2_proofs/src => src}/plonk.rs | 0 {halo2_proofs/src => src}/plonk/assigned.rs | 0 {halo2_proofs/src => src}/plonk/circuit.rs | 0 .../plonk/circuit/compress_selectors.rs | 0 {halo2_proofs/src => src}/plonk/error.rs | 0 {halo2_proofs/src => src}/plonk/evaluation.rs | 0 {halo2_proofs/src => src}/plonk/keygen.rs | 0 {halo2_proofs/src => src}/plonk/lookup.rs | 0 .../src => src}/plonk/lookup/prover.rs | 0 .../src => src}/plonk/lookup/verifier.rs | 0 .../src => src}/plonk/permutation.rs | 0 .../src => src}/plonk/permutation/keygen.rs | 0 .../src => src}/plonk/permutation/prover.rs | 0 .../src => src}/plonk/permutation/verifier.rs | 0 {halo2_proofs/src => src}/plonk/prover.rs | 0 {halo2_proofs/src => src}/plonk/shuffle.rs | 0 .../src => src}/plonk/shuffle/prover.rs | 0 .../src => src}/plonk/shuffle/verifier.rs | 0 {halo2_proofs/src => src}/plonk/vanishing.rs | 0 .../src => src}/plonk/vanishing/prover.rs | 0 .../src => src}/plonk/vanishing/verifier.rs | 0 {halo2_proofs/src => src}/plonk/verifier.rs | 0 .../src => src}/plonk/verifier/batch.rs | 0 {halo2_proofs/src => src}/poly.rs | 0 {halo2_proofs/src => src}/poly/commitment.rs | 0 {halo2_proofs/src => src}/poly/domain.rs | 0 .../src => src}/poly/ipa/commitment.rs | 0 .../src => src}/poly/ipa/commitment/prover.rs | 0 .../poly/ipa/commitment/verifier.rs | 0 {halo2_proofs/src => src}/poly/ipa/mod.rs | 0 {halo2_proofs/src => src}/poly/ipa/msm.rs | 0 .../src => src}/poly/ipa/multiopen.rs | 0 .../src => src}/poly/ipa/multiopen/prover.rs | 0 .../poly/ipa/multiopen/verifier.rs | 0 .../src => src}/poly/ipa/strategy.rs | 0 .../src => src}/poly/kzg/commitment.rs | 0 {halo2_proofs/src => src}/poly/kzg/mod.rs | 0 {halo2_proofs/src => src}/poly/kzg/msm.rs | 0 .../src => src}/poly/kzg/multiopen.rs | 0 .../src => src}/poly/kzg/multiopen/gwc.rs | 0 .../poly/kzg/multiopen/gwc/prover.rs | 0 .../poly/kzg/multiopen/gwc/verifier.rs | 0 .../src => src}/poly/kzg/multiopen/shplonk.rs | 0 .../poly/kzg/multiopen/shplonk/prover.rs | 0 .../poly/kzg/multiopen/shplonk/verifier.rs | 0 .../src => src}/poly/kzg/strategy.rs | 0 .../src => src}/poly/multiopen_test.rs | 0 {halo2_proofs/src => src}/poly/query.rs | 0 {halo2_proofs/src => src}/poly/strategy.rs | 0 {halo2_proofs/src => src}/transcript.rs | 0 {halo2_proofs/tests => tests}/plonk_api.rs | 0 162 files changed, 113 insertions(+), 8556 deletions(-) rename halo2_proofs/CHANGELOG.md => CHANGELOG.md (100%) rename {halo2_proofs/benches => benches}/arithmetic.rs (100%) rename {halo2_proofs/benches => benches}/commit_zk.rs (100%) rename {halo2_proofs/benches => benches}/dev_lookup.rs (100%) rename {halo2_proofs/benches => benches}/fft.rs (100%) rename {halo2_proofs/benches => benches}/hashtocurve.rs (100%) rename {halo2_proofs/benches => benches}/plonk.rs (100%) delete mode 100644 book/.gitignore delete mode 100644 book/Makefile delete mode 100644 book/book.toml delete mode 100755 book/edithtml.sh delete mode 100644 book/macros.txt delete mode 100644 book/src/IDENTIFIERS.json delete mode 100644 book/src/README.md delete mode 100644 book/src/SUMMARY.md delete mode 100644 book/src/background.md delete mode 100644 book/src/background/curves.md delete mode 100644 book/src/background/fields.md delete mode 100644 book/src/background/groups.md delete mode 100644 book/src/background/pc-ipa.md delete mode 100644 book/src/background/plonkish.md delete mode 100644 book/src/background/polynomials.md delete mode 100644 book/src/background/recursion.md delete mode 100644 book/src/concepts.md delete mode 100644 book/src/concepts/arithmetization.md delete mode 100644 book/src/concepts/chips.md delete mode 100644 book/src/concepts/gadgets.md delete mode 100644 book/src/concepts/proofs.md delete mode 100644 book/src/design.md delete mode 100644 book/src/design/gadgets.md delete mode 100644 book/src/design/gadgets/decomposition.md delete mode 100644 book/src/design/gadgets/ecc.md delete mode 100644 book/src/design/gadgets/ecc/addition.md delete mode 100644 book/src/design/gadgets/ecc/fixed-base-scalar-mul.md delete mode 100644 book/src/design/gadgets/ecc/var-base-scalar-mul.md delete mode 100644 book/src/design/gadgets/ecc/witnessing-points.md delete mode 100644 book/src/design/gadgets/sha256.md delete mode 100644 book/src/design/gadgets/sha256/bit_reassignment.png delete mode 100644 book/src/design/gadgets/sha256/compression.png delete mode 100644 book/src/design/gadgets/sha256/low_sigma_0.png delete mode 100644 book/src/design/gadgets/sha256/low_sigma_1.png delete mode 100644 book/src/design/gadgets/sha256/table16.md delete mode 100644 book/src/design/gadgets/sha256/upp_sigma_0.png delete mode 100644 book/src/design/gadgets/sha256/upp_sigma_1.png delete mode 100644 book/src/design/gadgets/sinsemilla.md delete mode 100644 book/src/design/gadgets/sinsemilla/merkle-crh.md delete mode 100644 book/src/design/implementation.md delete mode 100644 book/src/design/implementation/fields.md delete mode 100644 book/src/design/implementation/proofs.md delete mode 100644 book/src/design/implementation/selector-combining.md delete mode 100644 book/src/design/protocol.md delete mode 100644 book/src/design/proving-system.md delete mode 100644 book/src/design/proving-system/circuit-commitments.md delete mode 100644 book/src/design/proving-system/comparison.md delete mode 100644 book/src/design/proving-system/inner-product.md delete mode 100644 book/src/design/proving-system/lookup.md delete mode 100644 book/src/design/proving-system/multipoint-opening.md delete mode 100644 book/src/design/proving-system/permutation-diagram.png delete mode 100644 book/src/design/proving-system/permutation-diagram.svg delete mode 100644 book/src/design/proving-system/permutation.md delete mode 100644 book/src/design/proving-system/vanishing.md delete mode 100644 book/src/user.md delete mode 100644 book/src/user/dev-tools.md delete mode 100644 book/src/user/experimental-features.md delete mode 100644 book/src/user/gadgets.md delete mode 100644 book/src/user/lookup-tables.md delete mode 100644 book/src/user/simple-example.md delete mode 100644 book/src/user/tips-and-tricks.md rename {halo2_proofs/examples => examples}/circuit-layout.rs (100%) rename {halo2_proofs/examples => examples}/proof-size.rs (100%) rename {halo2_proofs/examples => examples}/serialization.rs (100%) rename {halo2_proofs/examples => examples}/shuffle.rs (100%) rename {halo2_proofs/examples => examples}/shuffle_api.rs (100%) rename {halo2_proofs/examples => examples}/simple-example.rs (100%) rename {halo2_proofs/examples => examples}/two-chip.rs (100%) rename {halo2_proofs/examples => examples}/vector-mul.rs (100%) rename {halo2_proofs/examples => examples}/vector-ops-unblinded.rs (100%) delete mode 100644 halo2/CHANGELOG.md delete mode 100644 halo2/Cargo.toml delete mode 100644 halo2/katex-header.html delete mode 100644 halo2/src/lib.rs delete mode 100644 halo2_proofs/Cargo.toml delete mode 100644 halo2_proofs/README.md delete mode 100644 halo2_proofs/katex-header.html rename {halo2_proofs/proptest-regressions => proptest-regressions}/plonk/assigned.txt (100%) rename {halo2_proofs/proptest-regressions => proptest-regressions}/plonk/circuit/compress_selectors.txt (100%) rename {halo2_proofs/src => src}/arithmetic.rs (100%) rename {halo2_proofs/src => src}/circuit.rs (100%) rename {halo2_proofs/src => src}/circuit/floor_planner.rs (100%) rename {halo2_proofs/src => src}/circuit/floor_planner/single_pass.rs (100%) rename {halo2_proofs/src => src}/circuit/floor_planner/v1.rs (100%) rename {halo2_proofs/src => src}/circuit/floor_planner/v1/strategy.rs (100%) rename {halo2_proofs/src => src}/circuit/layouter.rs (100%) rename {halo2_proofs/src => src}/circuit/table_layouter.rs (100%) rename {halo2_proofs/src => src}/circuit/value.rs (100%) rename {halo2_proofs/src => src}/dev.rs (100%) rename {halo2_proofs/src => src}/dev/cost.rs (100%) rename {halo2_proofs/src => src}/dev/cost_model.rs (100%) rename {halo2_proofs/src => src}/dev/failure.rs (100%) rename {halo2_proofs/src => src}/dev/failure/emitter.rs (100%) rename {halo2_proofs/src => src}/dev/gates.rs (100%) rename {halo2_proofs/src => src}/dev/graph.rs (100%) rename {halo2_proofs/src => src}/dev/graph/layout.rs (100%) rename {halo2_proofs/src => src}/dev/metadata.rs (100%) rename {halo2_proofs/src => src}/dev/tfp.rs (100%) rename {halo2_proofs/src => src}/dev/util.rs (100%) rename {halo2_proofs/src => src}/helpers.rs (100%) rename {halo2_proofs/src => src}/lib.rs (100%) rename {halo2_proofs/src => src}/multicore.rs (100%) rename {halo2_proofs/src => src}/plonk.rs (100%) rename {halo2_proofs/src => src}/plonk/assigned.rs (100%) rename {halo2_proofs/src => src}/plonk/circuit.rs (100%) rename {halo2_proofs/src => src}/plonk/circuit/compress_selectors.rs (100%) rename {halo2_proofs/src => src}/plonk/error.rs (100%) rename {halo2_proofs/src => src}/plonk/evaluation.rs (100%) rename {halo2_proofs/src => src}/plonk/keygen.rs (100%) rename {halo2_proofs/src => src}/plonk/lookup.rs (100%) rename {halo2_proofs/src => src}/plonk/lookup/prover.rs (100%) rename {halo2_proofs/src => src}/plonk/lookup/verifier.rs (100%) rename {halo2_proofs/src => src}/plonk/permutation.rs (100%) rename {halo2_proofs/src => src}/plonk/permutation/keygen.rs (100%) rename {halo2_proofs/src => src}/plonk/permutation/prover.rs (100%) rename {halo2_proofs/src => src}/plonk/permutation/verifier.rs (100%) rename {halo2_proofs/src => src}/plonk/prover.rs (100%) rename {halo2_proofs/src => src}/plonk/shuffle.rs (100%) rename {halo2_proofs/src => src}/plonk/shuffle/prover.rs (100%) rename {halo2_proofs/src => src}/plonk/shuffle/verifier.rs (100%) rename {halo2_proofs/src => src}/plonk/vanishing.rs (100%) rename {halo2_proofs/src => src}/plonk/vanishing/prover.rs (100%) rename {halo2_proofs/src => src}/plonk/vanishing/verifier.rs (100%) rename {halo2_proofs/src => src}/plonk/verifier.rs (100%) rename {halo2_proofs/src => src}/plonk/verifier/batch.rs (100%) rename {halo2_proofs/src => src}/poly.rs (100%) rename {halo2_proofs/src => src}/poly/commitment.rs (100%) rename {halo2_proofs/src => src}/poly/domain.rs (100%) rename {halo2_proofs/src => src}/poly/ipa/commitment.rs (100%) rename {halo2_proofs/src => src}/poly/ipa/commitment/prover.rs (100%) rename {halo2_proofs/src => src}/poly/ipa/commitment/verifier.rs (100%) rename {halo2_proofs/src => src}/poly/ipa/mod.rs (100%) rename {halo2_proofs/src => src}/poly/ipa/msm.rs (100%) rename {halo2_proofs/src => src}/poly/ipa/multiopen.rs (100%) rename {halo2_proofs/src => src}/poly/ipa/multiopen/prover.rs (100%) rename {halo2_proofs/src => src}/poly/ipa/multiopen/verifier.rs (100%) rename {halo2_proofs/src => src}/poly/ipa/strategy.rs (100%) rename {halo2_proofs/src => src}/poly/kzg/commitment.rs (100%) rename {halo2_proofs/src => src}/poly/kzg/mod.rs (100%) rename {halo2_proofs/src => src}/poly/kzg/msm.rs (100%) rename {halo2_proofs/src => src}/poly/kzg/multiopen.rs (100%) rename {halo2_proofs/src => src}/poly/kzg/multiopen/gwc.rs (100%) rename {halo2_proofs/src => src}/poly/kzg/multiopen/gwc/prover.rs (100%) rename {halo2_proofs/src => src}/poly/kzg/multiopen/gwc/verifier.rs (100%) rename {halo2_proofs/src => src}/poly/kzg/multiopen/shplonk.rs (100%) rename {halo2_proofs/src => src}/poly/kzg/multiopen/shplonk/prover.rs (100%) rename {halo2_proofs/src => src}/poly/kzg/multiopen/shplonk/verifier.rs (100%) rename {halo2_proofs/src => src}/poly/kzg/strategy.rs (100%) rename {halo2_proofs/src => src}/poly/multiopen_test.rs (100%) rename {halo2_proofs/src => src}/poly/query.rs (100%) rename {halo2_proofs/src => src}/poly/strategy.rs (100%) rename {halo2_proofs/src => src}/transcript.rs (100%) rename {halo2_proofs/tests => tests}/plonk_api.rs (100%) diff --git a/halo2_proofs/CHANGELOG.md b/CHANGELOG.md similarity index 100% rename from halo2_proofs/CHANGELOG.md rename to CHANGELOG.md diff --git a/Cargo.toml b/Cargo.toml index b44700ec43..e340407dd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,109 @@ -[workspace] -members = [ - "halo2", - "halo2_proofs", +[package] +name = "halo2_proofs" +version = "0.3.0" +authors = [ + "Sean Bowe ", + "Ying Tong Lai ", + "Daira Hopwood ", + "Jack Grigg ", ] +edition = "2021" +rust-version = "1.66.0" +description = """ +Fast PLONK-based zero-knowledge proving system with no trusted setup +""" +license = "MIT OR Apache-2.0" +repository = "https://github.com/zcash/halo2" +documentation = "https://docs.rs/halo2_proofs" +readme = "README.md" +categories = ["cryptography"] +keywords = ["halo", "proofs", "zkp", "zkSNARKs"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] + +[[bench]] +name = "arithmetic" +harness = false + +[[bench]] +name = "commit_zk" +harness = false + +[[bench]] +name = "hashtocurve" +harness = false + +[[bench]] +name = "plonk" +harness = false + +[[bench]] +name = "dev_lookup" +harness = false + +[[bench]] +name = "fft" +harness = false + +[dependencies] +backtrace = { version = "0.3", optional = true } +ff = "0.13" +group = "0.13" +halo2curves = { version = "0.6.0", default-features = false } +rand_core = { version = "0.6", default-features = false } +tracing = "0.1" +blake2b_simd = "1" # MSRV 1.66.0 +sha3 = "0.9.1" +rand_chacha = "0.3" +serde = { version = "1", optional = true, features = ["derive"] } +serde_derive = { version = "1", optional = true} +rayon = "1.8" + +# Developer tooling dependencies +plotters = { version = "0.3.0", default-features = false, optional = true } +tabbycat = { version = "0.1", features = ["attributes"], optional = true } + +# Legacy circuit compatibility +halo2_legacy_pdqsort = { version = "0.1.0", optional = true } + +[dev-dependencies] +assert_matches = "1.5" +criterion = "0.3" +gumdrop = "0.8" +proptest = "1" +rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } +serde_json = "1" + +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies] +getrandom = { version = "0.2", features = ["js"] } + +[features] +default = ["batch", "bits"] +dev-graph = ["plotters", "tabbycat"] +test-dev-graph = [ + "dev-graph", + "plotters/bitmap_backend", + "plotters/bitmap_encoder", + "plotters/ttf", +] +bits = ["halo2curves/bits"] +gadget-traces = ["backtrace"] +thread-safe-region = [] +sanity-checks = [] +batch = ["rand_core/getrandom"] +circuit-params = [] +cost-estimator = ["serde", "serde_derive"] +derive_serde = ["halo2curves/derive_serde"] + +[lib] +bench = false + +[[example]] +name = "circuit-layout" +required-features = ["test-dev-graph"] + +[[example]] +name = "proof-size" +required-features = ["cost-estimator"] diff --git a/README.md b/README.md index 3c7aa59572..bdb9a63639 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -# halo2 [![Crates.io](https://img.shields.io/crates/v/halo2.svg)](https://crates.io/crates/halo2) # +# halo2_proofs [![Crates.io](https://img.shields.io/crates/v/halo2_proofs.svg)](https://crates.io/crates/halo2_proofs) # -## [Documentation](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs) - -For experimental features `privacy-scaling-explorations/halo2` fork adds, please refer to [`experimental-features.md`](./book/src/user/experimental-features.md). +## [Documentation](https://docs.rs/halo2_proofs) ## Minimum Supported Rust Version @@ -13,7 +11,9 @@ minor version bump. ## Controlling parallelism -`halo2` currently uses [rayon](https://github.com/rayon-rs/rayon) for parallel computation. The `RAYON_NUM_THREADS` environment variable can be used to set the number of threads. +`halo2_proofs` currently uses [rayon](https://github.com/rayon-rs/rayon) for parallel +computation. The `RAYON_NUM_THREADS` environment variable can be used to set the number of +threads. When compiling to WASM-targets, notice that since version `1.7`, `rayon` will fallback automatically (with no need to handle features) to require `getrandom` in order to be able to work. For more info related to WASM-compilation. diff --git a/halo2_proofs/benches/arithmetic.rs b/benches/arithmetic.rs similarity index 100% rename from halo2_proofs/benches/arithmetic.rs rename to benches/arithmetic.rs diff --git a/halo2_proofs/benches/commit_zk.rs b/benches/commit_zk.rs similarity index 100% rename from halo2_proofs/benches/commit_zk.rs rename to benches/commit_zk.rs diff --git a/halo2_proofs/benches/dev_lookup.rs b/benches/dev_lookup.rs similarity index 100% rename from halo2_proofs/benches/dev_lookup.rs rename to benches/dev_lookup.rs diff --git a/halo2_proofs/benches/fft.rs b/benches/fft.rs similarity index 100% rename from halo2_proofs/benches/fft.rs rename to benches/fft.rs diff --git a/halo2_proofs/benches/hashtocurve.rs b/benches/hashtocurve.rs similarity index 100% rename from halo2_proofs/benches/hashtocurve.rs rename to benches/hashtocurve.rs diff --git a/halo2_proofs/benches/plonk.rs b/benches/plonk.rs similarity index 100% rename from halo2_proofs/benches/plonk.rs rename to benches/plonk.rs diff --git a/book/.gitignore b/book/.gitignore deleted file mode 100644 index 7585238efe..0000000000 --- a/book/.gitignore +++ /dev/null @@ -1 +0,0 @@ -book diff --git a/book/Makefile b/book/Makefile deleted file mode 100644 index 2fb3f9a9cd..0000000000 --- a/book/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -.PHONY: all -all: - find src -type f -a -name '*.md' |sed 's/[.]md$$/.html/g' |xargs $(MAKE) - -clean: - find src -type f -a -name '*.html' -print0 |xargs -0 rm - -%.html: %.md - pandoc --katex --from=markdown --to=html "$<" "--output=$@" - ./edithtml.sh "$@" "$<" diff --git a/book/book.toml b/book/book.toml deleted file mode 100644 index 87448f1696..0000000000 --- a/book/book.toml +++ /dev/null @@ -1,14 +0,0 @@ -[book] -authors = [ - "Jack Grigg", - "Sean Bowe", - "Daira Hopwood", - "Ying Tong Lai", -] -language = "en" -multilingual = false -src = "src" -title = "The halo2 Book" - -[preprocessor.katex] -macros = "macros.txt" diff --git a/book/edithtml.sh b/book/edithtml.sh deleted file mode 100755 index 4825478716..0000000000 --- a/book/edithtml.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh - -cat - "$1" > "$1.prefix" < - - - - - - $2 - - - - - - -EOF -cat "$1.prefix" - >"$1" < - -EOF -rm -f "$1.prefix" diff --git a/book/macros.txt b/book/macros.txt deleted file mode 100644 index 22eab46299..0000000000 --- a/book/macros.txt +++ /dev/null @@ -1,76 +0,0 @@ -# Conventions - -\bconcat:{\mathop{\kern 0.1em||\kern 0.1em}} -\Repr:{\star} - -# Conversions - -\ItoLEBSP:{\mathsf{I2LEBSP}_{#1}} - -# Fields and curves - -\BaseLength:{\ell^\mathsf{#1\vphantom{p}}_{\mathsf{base}}} - -# Commitments and hashes - -\SinsemillaHash:{\mathsf{SinsemillaHash}} -\SinsemillaCommit:{\mathsf{SinsemillaCommit}} -\SinsemillaShortCommit:{\mathsf{SinsemillaShortCommit}} - -# Circuit constraint helper methods - -\BoolCheck:{\texttt{bool\_check}({#1})} -\Ternary:{\texttt{ternary}({{#1}, {#2}, {#3}})} -\RangeCheck:{\texttt{range\_check}({#1, #2})} -\ShortLookupRangeCheck:{\texttt{short\_lookup\_range\_check}({#1})} - -# Halo 2 proof - -\field:{\mathbb{F}} -\group:{\mathbb{G}} -\setup:{\textnormal{Setup}} -\prover:{\mathcal{P}} -\verifier:{\mathcal{V}} -\sec:{\lambda} -\negl:{\textnormal{negl}(\lambda)} -\pp:{\mathsf{pp}} -\ip:{\textnormal{IP}} -\relation:{\mathcal{R}} -\a:{\mathcal{A}} -\sim:{\mathcal{S}} -\tr:{\textnormal{tr}} -\srs:{\textnormal{SRS}} -\srwee:{\textnormal{sr-wee}} -\real:{\textnormal{real}} -\ideal:{\textnormal{ideal}} -\weereal:{\textnormal{WEE-real}} -\weeideal:{\textnormal{WEE-ideal}} -\oracle:{\mathcal{O}} -\ch:{\mathsf{Ch}} -\badch:{\mathsf{BadCh}} -\adv:{\mathsf{Adv}} -\bottom:{\perp} -\alg:{#1_\textnormal{alg}} -\zero:{\mathcal{O}} -\dlrel:{\textsf{dl-rel}} -\game:{\mathsf{G}} -\innerprod:{\langle{#1},{#2}\rangle} -\dlgame:{\mathsf{G}^\dlrel_{\group,n}} -\distinguisher:{\mathcal{D}} -\extractor:{\mathcal{E}} -\state:{\mathsf{st}_{#1}} -\halo:{\textsf{Halo}} -\lo:{\textnormal{lo}} -\hi:{\textnormal{hi}} -\protocol:{\halo} -\extractwitness:{\textnormal{ExtractWitness}} -\pfail:{p_\textnormal{fail}} -\repr:\{\kern-0.1em {#1} \kern-0.1em\}^{#2} -\rep:{\repr{#1}{}} -\repv:{\repr{#1}{\mathbf{#2}}_{#3}} -\dlreladv:{\mathcal{H}} -\mr:{\mathcal{M}^{#1}_{#2}({#3})} -\mv:{\mr{\mathbf{#1}}{#2}{#3}} -\m:{\mr{#1}{}{#2}} -\z:{\mathcal{Z}_{#1}({#2}, {#3})} -\trprefix:{{#1}|_{#2}} diff --git a/book/src/IDENTIFIERS.json b/book/src/IDENTIFIERS.json deleted file mode 100644 index 2508a78a0f..0000000000 --- a/book/src/IDENTIFIERS.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "decompose-combined-lookup": "design/gadgets/decomposition.html#combined-lookup-expression", - "decompose-short-lookup": "design/gadgets/decomposition.html#short-range-check", - "decompose-short-range": "design/gadgets/decomposition.html#short-range-decomposition", - "ecc-complete-addition": "design/gadgets/ecc/addition.html#complete-addition-constraints", - "ecc-incomplete-addition": "design/gadgets/ecc/addition.html#incomplete-addition-constraints", - "ecc-fixed-mul-base-canonicity": "design/gadgets/ecc/fixed-base-scalar-mul.html#base-field-element", - "ecc-fixed-mul-coordinates": "design/gadgets/ecc/fixed-base-scalar-mul.html#constrain-coordinates", - "ecc-fixed-mul-full-word": "design/gadgets/ecc/fixed-base-scalar-mul.html#full-width-scalar", - "ecc-fixed-mul-load-base": "design/gadgets/ecc/fixed-base-scalar-mul.html#load-fixed-base", - "ecc-fixed-mul-short-msb": "design/gadgets/ecc/fixed-base-scalar-mul.html#constrain-short-signed-msb", - "ecc-fixed-mul-short-conditional-neg": "design/gadgets/ecc/fixed-base-scalar-mul.html#constrain-short-signed-conditional-neg", - "ecc-var-mul-complete-gate": "design/gadgets/ecc/var-base-scalar-mul.html#complete-gate", - "ecc-var-mul-incomplete-first-row": "design/gadgets/ecc/var-base-scalar-mul.html#incomplete-first-row-gate", - "ecc-var-mul-incomplete-last-row": "design/gadgets/ecc/var-base-scalar-mul.html#incomplete-last-row-gate", - "ecc-var-mul-incomplete-main-loop": "design/gadgets/ecc/var-base-scalar-mul.html#incomplete-main-loop-gate", - "ecc-var-mul-lsb-gate": "design/gadgets/ecc/var-base-scalar-mul.html#lsb-gate", - "ecc-var-mul-overflow": "design/gadgets/ecc/var-base-scalar-mul.html#overflow-check-constraints", - "ecc-var-mul-witness-scalar": "design/gadgets/ecc/var-base-scalar-mul.html#witness-scalar", - "ecc-witness-point": "design/gadgets/ecc/witnessing-points.html#points-including-the-identity", - "ecc-witness-non-identity-point": "design/gadgets/ecc/witnessing-points.html#non-identity-points", - "sinsemilla-constraints": "design/gadgets/sinsemilla.html#optimized-sinsemilla-gate", - "sinsemilla-merkle-crh-bit-lengths": "design/gadgets/sinsemilla/merkle-crh.html#bit-length-constraints", - "sinsemilla-merkle-crh-decomposition": "design/gadgets/sinsemilla/merkle-crh.html#decomposition-constraints" -} \ No newline at end of file diff --git a/book/src/README.md b/book/src/README.md deleted file mode 100644 index 676d71c102..0000000000 --- a/book/src/README.md +++ /dev/null @@ -1 +0,0 @@ -{{#include ../../README.md}} diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md deleted file mode 100644 index f234b2e4d2..0000000000 --- a/book/src/SUMMARY.md +++ /dev/null @@ -1,47 +0,0 @@ -# The halo2 Book - -[halo2](README.md) -- [Concepts](concepts.md) - - [Proof systems](concepts/proofs.md) - - [PLONKish Arithmetization](concepts/arithmetization.md) - - [Chips](concepts/chips.md) - - [Gadgets](concepts/gadgets.md) -- [User Documentation](user.md) - - [Developer tools](user/dev-tools.md) - - [A simple example](user/simple-example.md) - - [Lookup tables](user/lookup-tables.md) - - [Gadgets](user/gadgets.md) - - [Tips and tricks](user/tips-and-tricks.md) - - [Experimental features](user/experimental-features.md) -- [Design](design.md) - - [Proving system](design/proving-system.md) - - [Lookup argument](design/proving-system/lookup.md) - - [Permutation argument](design/proving-system/permutation.md) - - [Circuit commitments](design/proving-system/circuit-commitments.md) - - [Vanishing argument](design/proving-system/vanishing.md) - - [Multipoint opening argument](design/proving-system/multipoint-opening.md) - - [Inner product argument](design/proving-system/inner-product.md) - - [Comparison to other work](design/proving-system/comparison.md) - - [Protocol Description](design/protocol.md) - - [Implementation](design/implementation.md) - - [Proofs](design/implementation/proofs.md) - - [Fields](design/implementation/fields.md) - - [Selector combining](design/implementation/selector-combining.md) - - [Gadgets](design/gadgets.md) - - [Elliptic curve cryptography](design/gadgets/ecc.md) - - [Witnessing points](design/gadgets/ecc/witnessing-points.md) - - [Incomplete and complete addition](design/gadgets/ecc/addition.md) - - [Fixed-base scalar multiplication](design/gadgets/ecc/fixed-base-scalar-mul.md) - - [Variable-base scalar multiplication](design/gadgets/ecc/var-base-scalar-mul.md) - - [Sinsemilla](design/gadgets/sinsemilla.md) - - [MerkleCRH](design/gadgets/sinsemilla/merkle-crh.md) - - [Decomposition](design/gadgets/decomposition.md) - - [SHA-256](design/gadgets/sha256.md) - - [16-bit table chip](design/gadgets/sha256/table16.md) -- [Background Material](background.md) - - [Fields](background/fields.md) - - [Polynomials](background/polynomials.md) - - [Cryptographic groups](background/groups.md) - - [Elliptic curves](background/curves.md) - - [Polynomial commitment using inner product argument](background/pc-ipa.md) - - [Recursion](background/recursion.md) diff --git a/book/src/background.md b/book/src/background.md deleted file mode 100644 index abc65a5038..0000000000 --- a/book/src/background.md +++ /dev/null @@ -1,7 +0,0 @@ -# Background Material - -This section covers the background material required to understand the Halo 2 proving -system. It is targeted at an ELI15 (Explain It Like I'm 15) level; if you think anything -could do with additional explanation, [let us know]! - -[let us know]: https://github.com/zcash/halo2/issues/new/choose diff --git a/book/src/background/curves.md b/book/src/background/curves.md deleted file mode 100644 index 089ff77fff..0000000000 --- a/book/src/background/curves.md +++ /dev/null @@ -1,297 +0,0 @@ -# Elliptic curves - -Elliptic curves constructed over finite fields are another important cryptographic tool. - -We use elliptic curves because they provide a cryptographic [group](fields.md#Groups), -i.e. a group in which the discrete logarithm problem (discussed below) is hard. - -There are several ways to define the curve equation, but for our purposes, let -$\mathbb{F}_p$ be a large (255-bit) field, and then let the set of solutions $(x, y)$ to -$y^2 = x^3 + b$ for some constant $b$ define the $\mathbb{F}_p$-rational points on an -elliptic curve $E(\mathbb{F}_p)$. These $(x, y)$ coordinates are called "affine -coordinates". Each of the $\mathbb{F}_p$-rational points, together with a "point at -infinity" $\mathcal{O}$ that serves as the group identity, can be interpreted as an -element of a group. By convention, elliptic curve groups are written additively. - -![](https://i.imgur.com/JvLS6yE.png) -*"Three points on a line sum to zero, which is the point at infinity."* - -The group addition law is simple: to add two points together, find the line that -intersects both points and obtain the third point, and then negate its $y$-coordinate. The -case that a point is being added to itself, called point doubling, requires special -handling: we find the line tangent to the point, and then find the single other point that -intersects this line and then negate. Otherwise, in the event that a point is being -"added" to its negation, the result is the point at infinity. - -The ability to add and double points naturally gives us a way to scale them by integers, -called _scalars_. The number of points on the curve is the group order. If this number -is a prime $q$, then the scalars can be considered as elements of a _scalar field_, -$\mathbb{F}_q$. - -Elliptic curves, when properly designed, have an important security property. Given two -random elements $G, H \in E(\mathbb{F}_p)$ finding $a$ such that $[a] G = H$, otherwise -known as the discrete log of $H$ with respect to $G$, is considered computationally -infeasible with classical computers. This is called the elliptic curve discrete log -assumption. - -If an elliptic curve group $\mathbb{G}$ has prime order $q$ (like the ones used in Halo 2), -then it is a finite cyclic group. Recall from the section on [groups](fields.md#Groups) -that this implies it is isomorphic to $\mathbb{Z}/q\mathbb{Z}$, or equivalently, to the -scalar field $\mathbb{F}_q$. Each possible generator $G$ fixes the isomorphism; then -an element on the scalar side is precisely the discrete log of the corresponding group -element with respect to $G$. In the case of a cryptographically secure elliptic curve, -the isomorphism is hard to compute in the $\mathbb{G} \rightarrow \mathbb{F}_q$ direction -because the elliptic curve discrete log problem is hard. - -> It is sometimes helpful to make use of this isomorphism by thinking of group-based -> cryptographic protocols and algorithms in terms of the scalars instead of in terms of -> the group elements. This can make proofs and notation simpler. -> -> For instance, it has become common in papers on proof systems to use the notation $[x]$ -> to denote a group element with discrete log $x$, where the generator is implicit. -> -> We also used this idea in the -> "[distinct-x theorem](https://zips.z.cash/protocol/protocol.pdf#thmdistinctx)", -> in order to prove correctness of optimizations -> [for elliptic curve scalar multiplication](https://github.com/zcash/zcash/issues/3924) -> in Sapling, and an endomorphism-based optimization in Appendix C of the original -> [Halo paper](https://eprint.iacr.org/2019/1021.pdf). - -## Curve arithmetic - -### Point doubling - -The simplest situation is doubling a point $(x_0, y_0)$. Continuing with our example -$y^2 = x^3 + b$, this is done first by computing the derivative -$$ -\lambda = \frac{\mathrm{d}y}{\mathrm{d}x} = \frac{3x^2}{2y}. -$$ - -To obtain expressions for $(x_1, y_1) = (x_0, y_0) + (x_0, y_0),$ we consider - -$$ -\begin{aligned} -\frac{-y_1 - y_0}{x_1 - x_0} = \lambda &\implies -y_1 = \lambda(x_1 - x_0) + y_0 \\ -&\implies \boxed{y_1 = \lambda(x_0 - x_1) - y_0}. -\end{aligned} -$$ - -To get the expression for $x_1,$ we substitute $y = \lambda(x_0 - x) - y_0$ into the -elliptic curve equation: - -$$ -\begin{aligned} -y^2 = x^3 + b &\implies (\lambda(x_0 - x) - y_0)^2 = x^3 + b \\ -&\implies x^3 - \lambda^2 x^2 + \cdots = 0 \leftarrow\text{(rearranging terms)} \\ -&= (x - x_0)(x - x_0)(x - x_1) \leftarrow\text{(known roots $x_0, x_0, x_1$)} \\ -&= x^3 - (x_0 + x_0 + x_1)x^2 + \cdots. -\end{aligned} -$$ - -Comparing coefficients for the $x^2$ term gives us -$\lambda^2 = x_0 + x_0 + x_1 \implies \boxed{x_1 = \lambda^2 - 2x_0}.$ - - -### Projective coordinates -This unfortunately requires an expensive inversion of $2y$. We can avoid this by arranging -our equations to "defer" the computation of the inverse, since we often do not need the -actual affine $(x', y')$ coordinate of the resulting point immediately after an individual -curve operation. Let's introduce a third coordinate $Z$ and scale our curve equation by -$Z^3$ like so: - -$$ -Z^3 y^2 = Z^3 x^3 + Z^3 b -$$ - -Our original curve is just this curve at the restriction $Z = 1$. If we allow the affine -point $(x, y)$ to be represented by $X = xZ$, $Y = yZ$ and $Z \neq 0$ then we have the -[homogeneous projective curve](https://en.wikipedia.org/wiki/Homogeneous_coordinates) - -$$ -Y^2 Z = X^3 + Z^3 b. -$$ - -Obtaining $(x, y)$ from $(X, Y, Z)$ is as simple as computing $(X/Z, Y/Z)$ when -$Z \neq 0$. (When $Z = 0,$ we are dealing with the point at infinity $O := (0:1:0)$.) In -this form, we now have a convenient way to defer the inversion required by doubling a -point. The general strategy is to express $x', y'$ as rational functions using $x = X/Z$ -and $y = Y/Z$, rearrange to make their denominators the same, and then take the resulting -point $(X, Y, Z)$ to have $Z$ be the shared denominator and $X = x'Z, Y = y'Z$. - -> Projective coordinates are often, but not always, more efficient than affine -> coordinates. There may be exceptions to this when either we have a different way to -> apply Montgomery's trick, or when we're in the circuit setting where multiplications and -> inversions are about equally as expensive (at least in terms of circuit size). - -The following shows an example of doubling a point $(X, Y, Z) = (xZ, yZ, Z)$ without an -inversion. Substituting with $X, Y, Z$ gives us -$$ -\lambda = \frac{3x^2}{2y} = \frac{3(X/Z)^2}{2(Y/Z)} = \frac{3 X^2}{2YZ} -$$ - -and gives us -$$ -\begin{aligned} -x' &= \lambda^2 - 2x \\ -&= \lambda^2 - \frac{2X}{Z} \\ -&= \frac{9 X^4}{4Y^2Z^2} - \frac{2X}{Z} \\ -&= \frac{9 X^4 - 8XY^2Z}{4Y^2Z^2} \\ -&= \frac{18 X^4 Y Z - 16XY^3Z^2}{8Y^3Z^3} \\ -\\ -y' &= \lambda (x - x') - y \\ -&= \lambda (\frac{X}{Z} - \frac{9 X^4 - 8XY^2Z}{4Y^2Z^2}) - \frac{Y}{Z} \\ -&= \frac{3 X^2}{2YZ} (\frac{X}{Z} - \frac{9 X^4 - 8XY^2Z}{4Y^2Z^2}) - \frac{Y}{Z} \\ -&= \frac{3 X^3}{2YZ^2} - \frac{27 X^6 - 24X^3Y^2Z}{8Y^3Z^3} - \frac{Y}{Z} \\ -&= \frac{12 X^3Y^2Z - 8Y^4Z^2 - 27 X^6 + 24X^3Y^2Z}{8Y^3Z^3} -\end{aligned} -$$ - -Notice how the denominators of $x'$ and $y'$ are the same. Thus, instead of computing -$(x', y')$ we can compute $(X, Y, Z)$ with $Z = 8Y^3Z^3$ and $X, Y$ set to the -corresponding numerators such that $X/Z = x'$ and $Y/Z = y'$. This completely avoids the -need to perform an inversion when doubling, and something analogous to this can be done -when adding two distinct points. - -### Point addition -We now add two points with distinct $x$-coordinates, $P = (x_0, y_0)$ and $Q = (x_1, y_1),$ -where $x_0 \neq x_1,$ to obtain $R = P + Q = (x_2, y_2).$ The line $\overline{PQ}$ has slope -$$\lambda = \frac{y_1 - y_0}{x_1 - x_0} \implies y - y_0 = \lambda \cdot (x - x_0).$$ - -Using the expression for $\overline{PQ}$, we compute $y$-coordinate $-y_2$ of $-R$ as: -$$-y_2 - y_0 = \lambda \cdot (x_2 - x_0) \implies \boxed{y_2 =\lambda (x_0 - x_2) - y_0}.$$ - -Plugging the expression for $\overline{PQ}$ into the curve equation $y^2 = x^3 + b$ yields -$$ -\begin{aligned} -y^2 = x^3 + b &\implies (\lambda \cdot (x - x_0) + y_0)^2 = x^3 + b \\ -&\implies x^3 - \lambda^2 x^2 + \cdots = 0 \leftarrow\text{(rearranging terms)} \\ -&= (x - x_0)(x - x_1)(x - x_2) \leftarrow\text{(known roots $x_0, x_1, x_2$)} \\ -&= x^3 - (x_0 + x_1 + x_2)x^2 + \cdots. -\end{aligned} -$$ - -Comparing coefficients for the $x^2$ term gives us -$\lambda^2 = x_0 + x_1 + x_2 \implies \boxed{x_2 = \lambda^2 - x_0 - x_1}$. - ----------- - -Important notes: - -* There exist efficient formulae[^complete-formulae] for point addition that do not have - edge cases (so-called "complete" formulae) and that unify the addition and doubling - cases together. The result of adding a point to its negation using those formulae - produces $Z = 0$, which represents the point at infinity. -* In addition, there are other models like the Jacobian representation where - $(x, y) = (xZ^2, yZ^3, Z)$ where the curve is rescaled by $Z^6$ instead of $Z^3$, and - this representation has even more efficient arithmetic but no unified/complete formulae. -* We can easily compare two curve points $(X_1, Y_1, Z_1)$ and $(X_2, Y_2, Z_2)$ for - equality in the homogeneous projective coordinate space by "homogenizing" their - Z-coordinates; the checks become $X_1 Z_2 = X_2 Z_1$ and $Y_1 Z_2 = Y_2 Z_1$. - -## Curve endomorphisms - -Imagine that $\mathbb{F}_p$ has a primitive cube root of unity, or in other words that -$3 | p - 1$ and so an element $\zeta_p$ generates a $3$-order multiplicative subgroup. -Notice that a point $(x, y)$ on our example elliptic curve $y^2 = x^3 + b$ has two cousin -points: $(\zeta_p x,y), (\zeta_p^2 x,y)$, because the computation $x^3$ effectively kills the -$\zeta$ component of the $x$-coordinate. Applying the map $(x, y) \mapsto (\zeta_p x, y)$ -is an application of an endomorphism over the curve. The exact mechanics involved are -complicated, but when the curve has a prime $q$ number of points (and thus a prime -"order") the effect of the endomorphism is to multiply the point by a scalar in -$\mathbb{F}_q$ which is also a primitive cube root $\zeta_q$ in the scalar field. - -## Curve point compression -Given a point on the curve $P = (x,y)$, we know that its negation $-P = (x, -y)$ is also -on the curve. To uniquely specify a point, we need only encode its $x$-coordinate along -with the sign of its $y$-coordinate. - -### Serialization -As mentioned in the [Fields](./fields.md) section, we can interpret the least significant -bit of a field element as its "sign", since its additive inverse will always have the -opposite LSB. So we record the LSB of the $y$-coordinate as `sign`. - -Pallas and Vesta are defined over the $\mathbb{F}_p$ and $\mathbb{F}_q$ fields, which -elements can be expressed in $255$ bits. This conveniently leaves one unused bit in a -32-byte representation. We pack the $y$-coordinate `sign` bit into the highest bit in -the representation of the $x$-coordinate: - -```text - <----------------------------------- x ---------------------------------> -Enc(P) = [_ _ _ _ _ _ _ _] [_ _ _ _ _ _ _ _] ... [_ _ _ _ _ _ _ _] [_ _ _ _ _ _ _ sign] - ^ <-------------------------------------> ^ - LSB 30 bytes MSB -``` - -The "point at infinity" $\mathcal{O}$ that serves as the group identity, does not have an -affine $(x, y)$ representation. However, it turns out that there are no points on either -the Pallas or Vesta curve with $x = 0$ or $y = 0$. We therefore use the "fake" affine -coordinates $(0, 0)$ to encode $\mathcal{O}$, which results in the all-zeroes 32-byte -array. - -### Deserialization -When deserializing a compressed curve point, we first read the most significant bit as -`ysign`, the sign of the $y$-coordinate. Then, we set this bit to zero to recover the -original $x$-coordinate. - -If $x = 0, y = 0,$ we return the "point at infinity" $\mathcal{O}$. Otherwise, we proceed -to compute $y = \sqrt{x^3 + b}.$ Here, we read the least significant bit of $y$ as `sign`. -If `sign == ysign`, we already have the correct sign and simply return the curve point -$(x, y)$. Otherwise, we negate $y$ and return $(x, -y)$. - -## Cycles of curves -Let $E_p$ be an elliptic curve over a finite field $\mathbb{F}_p,$ where $p$ is a prime. -We denote this by $E_p/\mathbb{F}_p.$ and we denote the group of points of $E_p$ over -$\mathbb{F}_p,$ with order $q = \#E(\mathbb{F}_p).$ For this curve, we call $\mathbb{F}_p$ -the "base field" and $\mathbb{F}_q$ the "scalar field". - -We instantiate our proof system over the elliptic curve $E_p/\mathbb{F}_p$. This allows us -to prove statements about $\mathbb{F}_q$-arithmetic circuit satisfiability. - -> **(aside) If our curve $E_p$ is over $\mathbb{F}_p,$ why is the arithmetic circuit instead in $\mathbb{F}_q$?** -> The proof system is basically working on encodings of the scalars in the circuit (or -> more precisely, commitments to polynomials whose coefficients are scalars). The scalars -> are in $\mathbb{F}_q$ when their encodings/commitments are elliptic curve points in -> $E_p/\mathbb{F}_p$. - -However, most of the verifier's arithmetic computations are over the base field -$\mathbb{F}_p,$ and are thus efficiently expressed as an $\mathbb{F}_p$-arithmetic -circuit. - -> **(aside) Why are the verifier's computations (mainly) over $\mathbb{F}_p$?** -> The Halo 2 verifier actually has to perform group operations using information output by -> the circuit. Group operations like point doubling and addition use arithmetic in -> $\mathbb{F}_p$, because the coordinates of points are in $\mathbb{F}_p.$ - -This motivates us to construct another curve with scalar field $\mathbb{F}_p$, which has -an $\mathbb{F}_p$-arithmetic circuit that can efficiently verify proofs from the first -curve. As a bonus, if this second curve had base field $E_q/\mathbb{F}_q,$ it would -generate proofs that could be efficiently verified in the first curve's -$\mathbb{F}_q$-arithmetic circuit. In other words, we instantiate a second proof system -over $E_q/\mathbb{F}_q,$ forming a 2-cycle with the first: - -![](https://i.imgur.com/bNMyMRu.png) - -### TODO: Pallas-Vesta curves -Reference: https://github.com/zcash/pasta - -## Hashing to curves - -Sometimes it is useful to be able to produce a random point on an elliptic curve -$E_p/\mathbb{F}_p$ corresponding to some input, in such a way that no-one will know its -discrete logarithm (to any other base). - -This is described in detail in the [Internet draft on Hashing to Elliptic Curves][cfrg-hash-to-curve]. -Several algorithms can be used depending on efficiency and security requirements. The -framework used in the Internet Draft makes use of several functions: - -* ``hash_to_field``: takes a byte sequence input and maps it to a element in the base - field $\mathbb{F}_p$ -* ``map_to_curve``: takes an $\mathbb{F}_p$ element and maps it to $E_p$. - -[cfrg-hash-to-curve]: https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/?include_text=1 - -### TODO: Simplified SWU -Reference: https://eprint.iacr.org/2019/403.pdf - -## References -[^complete-formulae]: [Renes, J., Costello, C., & Batina, L. (2016, May). "Complete addition formulas for prime order elliptic curves." In Annual International Conference on the Theory and Applications of Cryptographic Techniques (pp. 403-428). Springer, Berlin, Heidelberg.](https://eprint.iacr.org/2015/1060) diff --git a/book/src/background/fields.md b/book/src/background/fields.md deleted file mode 100644 index 325637010a..0000000000 --- a/book/src/background/fields.md +++ /dev/null @@ -1,307 +0,0 @@ -# Fields - -A fundamental component of many cryptographic protocols is the algebraic structure known -as a [field]. Fields are sets of objects (usually numbers) with two associated binary -operators $+$ and $\times$ such that various [field axioms][field-axioms] hold. The real -numbers $\mathbb{R}$ are an example of a field with uncountably many elements. - -[field]: https://en.wikipedia.org/wiki/Field_(mathematics) -[field-axioms]: https://en.wikipedia.org/wiki/Field_(mathematics)#Classic_definition - -Halo makes use of _finite fields_ which have a finite number of elements. Finite fields -are fully classified as follows: - -- if $\mathbb{F}$ is a finite field, it contains $|\mathbb{F}| = p^k$ elements for some - integer $k \geq 1$ and some prime $p$; -- any two finite fields with the same number of elements are isomorphic. In particular, - all of the arithmetic in a prime field $\mathbb{F}_p$ is isomorphic to addition and - multiplication of integers modulo $p$, i.e. in $\mathbb{Z}_p$. This is why we often - refer to $p$ as the _modulus_. - -We'll write a field as $\mathbb{F}_q$ where $q = p^k$. The prime $p$ is called its -_characteristic_. In the cases where $k \gt 1$ the field $\mathbb{F}_q$ is a $k$-degree -extension of the field $\mathbb{F}_p$. (By analogy, the complex numbers -$\mathbb{C} = \mathbb{R}(i)$ are an extension of the real numbers.) However, in Halo we do -not use extension fields. Whenever we write $\mathbb{F}_p$ we are referring to what -we call a _prime field_ which has a prime $p$ number of elements, i.e. $k = 1$. - -Important notes: - -* There are two special elements in any field: $0$, the additive identity, and - $1$, the multiplicative identity. -* The least significant bit of a field element, when represented as an integer in binary - format, can be interpreted as its "sign" to help distinguish it from its additive - inverse (negation). This is because for some nonzero element $a$ which has a least - significant bit $0$ we have that $-a = p - a$ has a least significant bit $1$, and vice - versa. We could also use whether or not an element is larger than $(p - 1) / 2$ to give - it a "sign." - -Finite fields will be useful later for constructing [polynomials](polynomials.md) and -[elliptic curves](curves.md). Elliptic curves are examples of groups, which we discuss -next. - -## Groups - -Groups are simpler and more limited than fields; they have only one binary operator $\cdot$ -and fewer axioms. They also have an identity, which we'll denote as $1$. - -[group]: https://en.wikipedia.org/wiki/Group_(mathematics) -[group-axioms]: https://en.wikipedia.org/wiki/Group_(mathematics)#Definition - -Any non-zero element $a$ in a group has an _inverse_ $b = a^{-1}$, -which is the _unique_ element $b$ such that $a \cdot b = 1$. - -For example, the set of nonzero elements of $\mathbb{F}_p$ forms a group, where the -group operation is given by multiplication on the field. - -[group]: https://en.wikipedia.org/wiki/Group_(mathematics) - -> #### (aside) Additive vs multiplicative notation -> If $\cdot$ is written as $\times$ or omitted (i.e. $a \cdot b$ written as $ab$), the -> identity as $1$, and inversion as $a^{-1}$, as we did above, then we say that the group -> is "written multiplicatively". If $\cdot$ is written as $+$, the identity as $0$ or -> $\mathcal{O}$, and inversion as $-a$, then we say it is "written additively". -> -> It's conventional to use additive notation for elliptic curve groups, and multiplicative -> notation when the elements come from a finite field. -> -> When additive notation is used, we also write -> -> $$[k] A = \underbrace{A + A + \cdots + A}_{k \text{ times}}$$ -> -> for nonnegative $k$ and call this "scalar multiplication"; we also often use uppercase -> letters for variables denoting group elements. When multiplicative notation is used, we -> also write -> -> $$a^k = \underbrace{a \times a \times \cdots \times a}_{k \text{ times}}$$ -> -> and call this "exponentiation". In either case we call the scalar $k$ such that -> $[k] g = a$ or $g^k = a$ the "discrete logarithm" of $a$ to base $g$. We can extend -> scalars to negative integers by inversion, i.e. $[-k] A + [k] A = \mathcal{O}$ or -> $a^{-k} \times a^k = 1$. - -The _order_ of an element $a$ of a finite group is defined as the smallest positive integer -$k$ such that $a^k = 1$ (in multiplicative notation) or $[k] a = \mathcal{O}$ (in additive -notation). The order _of the group_ is the number of elements. - -Groups always have a [generating set], which is a set of elements such that we can produce -any element of the group as (in multiplicative terminology) a product of powers of those -elements. So if the generating set is $g_{1..k}$, we can produce any element of the group -as $\prod\limits_{i=1}^{k} g_i^{a_i}$. There can be many different generating sets for a -given group. - -[generating set]: https://en.wikipedia.org/wiki/Generating_set_of_a_group - -A group is called [cyclic] if it has a (not necessarily unique) generating set with only -a single element — call it $g$. In that case we can say that $g$ generates the group, and -that the order of $g$ is the order of the group. - -Any finite cyclic group $\mathbb{G}$ of order $n$ is [isomorphic] to the integers -modulo $n$ (denoted $\mathbb{Z}/n\mathbb{Z}$), such that: - -- the operation $\cdot$ in $\mathbb{G}$ corresponds to addition modulo $n$; -- the identity in $\mathbb{G}$ corresponds to $0$; -- some generator $g \in \mathbb{G}$ corresponds to $1$. - -Given a generator $g$, the isomorphism is always easy to compute in the -$\mathbb{Z}/n\mathbb{Z} \rightarrow \mathbb{G}$ direction; it is just $a \mapsto g^a$ -(or in additive notation, $a \mapsto [a] g$). -It may be difficult in general to compute in the $\mathbb{G} \rightarrow \mathbb{Z}/n\mathbb{Z}$ -direction; we'll discuss this further when we come to [elliptic curves](curves.md). - -If the order $n$ of a finite group is prime, then the group is cyclic, and every -non-identity element is a generator. - -[isomorphic]: https://en.wikipedia.org/wiki/Isomorphism -[cyclic]: https://en.wikipedia.org/wiki/Cyclic_group - -### The multiplicative group of a finite field - -We use the notation $\mathbb{F}_p^\times$ for the multiplicative group (i.e. the group -operation is multiplication in $\mathbb{F}_p$) over the set $\mathbb{F}_p - \{0\}$. - -A quick way of obtaining the inverse in $\mathbb{F}_p^\times$ is $a^{-1} = a^{p - 2}$. -The reason for this stems from [Fermat's little theorem][fermat-little], which states -that $a^p = a \pmod p$ for any integer $a$. If $a$ is nonzero, we can divide by $a$ twice -to get $a^{p-2} = a^{-1}.$ - -[fermat-little]: https://en.wikipedia.org/wiki/Fermat%27s_little_theorem - -Let's assume that $\alpha$ is a generator of $\mathbb{F}_p^\times$, so it has order $p-1$ -(equal to the number of elements in $\mathbb{F}_p^\times$). Therefore, for any element in -$a \in \mathbb{F}_p^\times$ there is a unique integer $i \in \{0..p-2\}$ such that $a = \alpha^i$. - -Notice that $a \times b$ where $a, b \in \mathbb{F}_p^\times$ can really be interpreted as -$\alpha^i \times \alpha^j$ where $a = \alpha^i$ and $b = \alpha^j$. Indeed, it holds that -$\alpha^i \times \alpha^j = \alpha^{i + j}$ for all $0 \leq i, j \lt p - 1$. As a result -the multiplication of nonzero field elements can be interpreted as addition modulo $p - 1$ -with respect to some fixed generator $\alpha$. The addition just happens "in the exponent." - -This is another way to look at where $a^{p - 2}$ comes from for computing inverses in the -field: - -$$p - 2 \equiv -1 \pmod{p - 1},$$ - -so $a^{p - 2} = a^{-1}$. - -### Montgomery's Trick - -Montgomery's trick, named after Peter Montgomery (RIP) is a way to compute many group -inversions at the same time. It is commonly used to compute inversions in -$\mathbb{F}_p^\times$, which are quite computationally expensive compared to multiplication. - -Imagine we need to compute the inverses of three nonzero elements $a, b, c \in \mathbb{F}_p^\times$. -Instead, we'll compute the products $x = ab$ and $y = xc = abc$, and compute the inversion - -$$z = y^{p - 2} = \frac{1}{abc}.$$ - -We can now multiply $z$ by $x$ to obtain $\frac{1}{c}$ and multiply $z$ by $c$ to obtain -$\frac{1}{ab}$, which we can then multiply by $a, b$ to obtain their respective inverses. - -This technique generalizes to arbitrary numbers of group elements with just a single -inversion necessary. - -## Multiplicative subgroups - -A _subgroup_ of a group $G$ with operation $\cdot$, is a subset of elements of $G$ that -also form a group under $\cdot$. - -In the previous section we said that $\alpha$ is a generator of the $(p - 1)$-order -multiplicative group $\mathbb{F}_p^\times$. This group has _composite_ order, and so by -the Chinese remainder theorem[^chinese-remainder] it has strict subgroups. As an example -let's imagine that $p = 11$, and so $p - 1$ factors into $5 \cdot 2$. Thus, there is a -generator $\beta$ of the $5$-order subgroup and a generator $\gamma$ of the $2$-order -subgroup. All elements in $\mathbb{F}_p^\times$, therefore, can be written uniquely as -$\beta^i \cdot \gamma^j$ for some $i$ (modulo $5$) and some $j$ (modulo $2$). - -If we have $a = \beta^i \cdot \gamma^j$ notice what happens when we compute - -$$ -a^5 = (\beta^i \cdot \gamma^j)^5 - = \beta^{i \cdot 5} \cdot \gamma^{j \cdot 5} - = \beta^0 \cdot \gamma^{j \cdot 5} - = \gamma^{j \cdot 5}; -$$ - -we have effectively "killed" the $5$-order subgroup component, producing a value in the -$2$-order subgroup. - -[Lagrange's theorem (group theory)][lagrange-group] states that the order of any subgroup -$H$ of a finite group $G$ divides the order of $G$. Therefore, the order of any subgroup -of $\mathbb{F}_p^\times$ must divide $p-1.$ - -[lagrange-group]: https://en.wikipedia.org/wiki/Lagrange%27s_theorem_(group_theory) - -[PLONK-based] proving systems like Halo 2 are more convenient to use with fields that have -a large number of multiplicative subgroups with a "smooth" distribution (which makes the -performance cliffs smaller and more granular as circuit sizes increase). The Pallas and -Vesta curves specifically have primes of the form - -$$T \cdot 2^S = p - 1$$ - -with $S = 32$ and $T$ odd (i.e. $p - 1$ has 32 lower zero-bits). This means they have -multiplicative subgroups of order $2^k$ for all $k \leq 32$. These 2-adic subgroups are -nice for [efficient FFTs], as well as enabling a wide variety of circuit sizes. - -[PLONK-based]: plonkish.md -[efficient FFTs]: polynomials.md#fast-fourier-transform-fft - -## Square roots - -In a field $\mathbb{F}_p$ exactly half of all nonzero elements are squares; the remainder -are non-squares or "quadratic non-residues". In order to see why, consider an $\alpha$ -that generates the $2$-order multiplicative subgroup of $\mathbb{F}_p^\times$ (this exists -because $p - 1$ is divisible by $2$ since $p$ is a prime greater than $2$) and $\beta$ that -generates the $t$-order multiplicative subgroup of $\mathbb{F}_p^\times$ where $p - 1 = 2t$. -Then every element $a \in \mathbb{F}_p^\times$ can be written uniquely as -$\alpha^i \cdot \beta^j$ with $i \in \mathbb{Z}_2$ and $j \in \mathbb{Z}_t$. Half of all -elements will have $i = 0$ and the other half will have $i = 1$. - -Let's consider the simple case where $p \equiv 3 \pmod{4}$ and so $t$ is odd (if $t$ is -even, then $p - 1$ would be divisible by $4$, which contradicts $p$ being $3 \pmod{4}$). -If $a \in \mathbb{F}_p^\times$ is a square, then there must exist -$b = \alpha^i \cdot \beta^j$ such that $b^2 = a$. But this means that - -$$a = (\alpha^i \cdot \beta^j)^2 = \alpha^{2i} \cdot \beta^{2j} = \beta^{2j}.$$ - -In other words, all squares in this particular field do not generate the $2$-order -multiplicative subgroup, and so since half of the elements generate the $2$-order subgroup -then at most half of the elements are square. In fact exactly half of the elements are -square (since squaring each nonsquare element gives a unique square). This means we can -assume all squares can be written as $\beta^m$ for some $m$, and therefore finding the -square root is a matter of exponentiating by $2^{-1} \pmod{t}$. - -In the event that $p \equiv 1 \pmod{4}$ then things get more complicated because -$2^{-1} \pmod{t}$ does not exist. Let's write $p - 1$ as $2^k \cdot t$ with $t$ odd. The -case $k = 0$ is impossible, and the case $k = 1$ is what we already described, so consider -$k \geq 2$. $\alpha$ generates a $2^k$-order multiplicative subgroup and $\beta$ generates -the odd $t$-order multiplicative subgroup. Then every element $a \in \mathbb{F}_p^\times$ -can be written as $\alpha^i \cdot \beta^j$ for $i \in \mathbb{Z}_{2^k}$ and -$j \in \mathbb{Z}_t$. If the element is a square, then there exists some $b = \sqrt{a}$ -which can be written $b = \alpha^{i'} \cdot \beta^{j'}$ for $i' \in \mathbb{Z}_{2^k}$ and -$j' \in \mathbb{Z}_t$. This means that $a = b^2 = \alpha^{2i'} \cdot \beta^{2j'}$, -therefore we have $i \equiv 2i' \pmod{2^k}$, and $j \equiv 2j' \pmod{t}$. $i$ would have -to be even in this case because otherwise it would be impossible to have -$i \equiv 2i' \pmod{2^k}$ for any $i'$. In the case that $a$ is not a square, then $i$ is -odd, and so half of all elements are squares. - -In order to compute the square root, we can first raise the element -$a = \alpha^i \cdot \beta^j$ to the power $t$ to "kill" the $t$-order component, giving - -$$a^t = \alpha^{it \pmod {2^k}} \cdot \beta^{jt \pmod t} = \alpha^{it \pmod {2^k}}$$ - -and then raise this result to the power $t^{-1} \pmod{2^k}$ to undo the effect of the -original exponentiation on the $2^k$-order component: - -$$(\alpha^{it \bmod 2^k})^{t^{-1} \pmod{2^k}} = \alpha^i$$ - -(since $t$ is relatively prime to $2^k$). This leaves bare the $\alpha^i$ value which we -can trivially handle. We can similarly kill the $2^k$-order component to obtain -$\beta^{j \cdot 2^{-1} \pmod{t}}$, and put the values together to obtain the square root. - -It turns out that in the cases $k = 2, 3$ there are simpler algorithms that merge several -of these exponentiations together for efficiency. For other values of $k$, the only known -way is to manually extract $i$ by squaring until you obtain the identity for every single -bit of $i$. This is the essence of the [Tonelli-Shanks square root algorithm][ts-sqrt] and -describes the general strategy. (There is another square root algorithm that uses -quadratic extension fields, but it doesn't pay off in efficiency until the prime becomes -quite large.) - -[ts-sqrt]: https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm - -## Roots of unity - -In the previous sections we wrote $p - 1 = 2^k \cdot t$ with $t$ odd, and stated that an -element $\alpha \in \mathbb{F}_p^\times$ generated the $2^k$-order subgroup. For -convenience, let's denote $n := 2^k.$ The elements $\{1, \alpha, \ldots, \alpha^{n-1}\}$ -are known as the $n$th [roots of unity](https://en.wikipedia.org/wiki/Root_of_unity). - -The **primitive root of unity**, $\omega,$ is an $n$th root of unity such that -$\omega^i \neq 1$ except when $i \equiv 0 \pmod{n}$. - -Important notes: - -- If $\alpha$ is an $n$th root of unity, $\alpha$ satisfies $\alpha^n - 1 = 0.$ If - $\alpha \neq 1,$ then - $$1 + \alpha + \alpha^2 + \cdots + \alpha^{n-1} = 0.$$ -- Equivalently, the roots of unity are solutions to the equation - $$X^n - 1 = (X - 1)(X - \alpha)(X - \alpha^2) \cdots (X - \alpha^{n-1}).$$ -- **$\boxed{\omega^{\frac{n}{2}+i} = -\omega^i}$ ("Negation lemma")**. Proof: - $$ - \begin{aligned} - \omega^n = 1 &\implies \omega^n - 1 = 0 \\ - &\implies (\omega^{n/2} + 1)(\omega^{n/2} - 1) = 0. - \end{aligned} - $$ - Since the order of $\omega$ is $n$, $\omega^{n/2} \neq 1.$ Therefore, $\omega^{n/2} = -1.$ - -- **$\boxed{(\omega^{\frac{n}{2}+i})^2 = (\omega^i)^2}$ ("Halving lemma")**. Proof: - $$ - (\omega^{\frac{n}{2}+i})^2 = \omega^{n + 2i} = \omega^{n} \cdot \omega^{2i} = \omega^{2i} = (\omega^i)^2. - $$ - In other words, if we square each element in the $n$th roots of unity, we would get back - only half the elements, $\{(\omega_n^i)^2\} = \{\omega_{n/2}\}$ (i.e. the $\frac{n}{2}$th roots - of unity). There is a two-to-one mapping between the elements and their squares. - -## References -[^chinese-remainder]: [Friedman, R. (n.d.) "Cyclic Groups and Elementary Number Theory II" (p. 5).](http://www.math.columbia.edu/~rf/numbertheory2.pdf) diff --git a/book/src/background/groups.md b/book/src/background/groups.md deleted file mode 100644 index da8cbc385e..0000000000 --- a/book/src/background/groups.md +++ /dev/null @@ -1,94 +0,0 @@ -# Cryptographic groups - -In the section [Inverses and groups](fields.md#inverses-and-groups) we introduced the -concept of *groups*. A group has an identity and a group operation. In this section we -will write groups additively, i.e. the identity is $\mathcal{O}$ and the group operation -is $+$. - -Some groups can be used as *cryptographic groups*. At the risk of oversimplifying, this -means that the problem of finding a discrete logarithm of a group element $P$ to a given -base $G$, i.e. finding $x$ such that $P = [x] G$, is hard in general. - -## Pedersen commitment -The Pedersen commitment [[P99]] is a way to commit to a secret message in a verifiable -way. It uses two random public generators $G, H \in \mathbb{G},$ where $\mathbb{G}$ is a -cryptographic group of order $p$. A random secret $r$ is chosen in $\mathbb{Z}_q$, and the -message to commit to $m$ is from any subset of $\mathbb{Z}_q$. The commitment is - -$$c = \text{Commit}(m,r)=[m]G + [r]H.$$ - -To open the commitment, the committer reveals $m$ and $r,$ thus allowing anyone to verify -that $c$ is indeed a commitment to $m.$ - -[P99]: https://link.springer.com/content/pdf/10.1007%2F3-540-46766-1_9.pdf#page=3 - -Notice that the Pedersen commitment scheme is homomorphic: - -$$ -\begin{aligned} -\text{Commit}(m,r) + \text{Commit}(m',r') &= [m]G + [r]H + [m']G + [r']H \\ -&= [m + m']G + [r + r']H \\ -&= \text{Commit}(m + m',r + r'). -\end{aligned} -$$ - -Assuming the discrete log assumption holds, Pedersen commitments are also perfectly hiding -and computationally binding: - -* **hiding**: the adversary chooses messages $m_0, m_1.$ The committer commits to one of - these messages $c = \text{Commit}(m_b,r), b \in \{0,1\}.$ Given $c,$ the probability of - the adversary guessing the correct $b$ is no more than $\frac{1}{2}$. -* **binding**: the adversary cannot pick two different messages $m_0 \neq m_1,$ and - randomness $r_0, r_1,$ such that $\text{Commit}(m_0,r_0) = \text{Commit}(m_1,r_1).$ - -### Vector Pedersen commitment -We can use a variant of the Pedersen commitment scheme to commit to multiple messages at -once, $\mathbf{m} = (m_0, \cdots, m_{n-1})$. This time, we'll have to sample a corresponding -number of random public generators $\mathbf{G} = (G_0, \cdots, G_{n-1}),$ along with a -single random generator $H$ as before (for use in hiding). Then, our commitment scheme is: - -$$ -\begin{aligned} -\text{Commit}(\mathbf{m}; r) &= \text{Commit}((m_0, \cdots, m_{n-1}); r) \\ -&= [r]H + [m_0]G_0 + \cdots + [m_{n-1}]G_{n-1} \\ -&= [r]H + \sum_{i= 0}^{n-1} [m_i]G_i. -\end{aligned} -$$ - -> TODO: is this positionally binding? - -## Diffie–Hellman - -An example of a protocol that uses cryptographic groups is Diffie–Hellman key agreement -[[DH1976]]. The Diffie–Hellman protocol is a method for two users, Alice and Bob, to -generate a shared private key. It proceeds as follows: - -1. Alice and Bob publicly agree on two prime numbers, $p$ and $G,$ where $p$ is large and - $G$ is a primitive root $\pmod p.$ (Note that $g$ is a generator of the group - $\mathbb{F}_p^\times.$) -2. Alice chooses a large random number $a$ as her private key. She computes her public key - $A = [a]G \pmod p,$ and sends $A$ to Bob. -3. Similarly, Bob chooses a large random number $b$ as his private key. He computes his - public key $B = [b]G \pmod p,$ and sends $B$ to Alice. -4. Now both Alice and Bob compute their shared key $K = [ab]G \pmod p,$ which Alice - computes as - $$K = [a]B \pmod p = [a]([b]G) \pmod p,$$ - and Bob computes as - $$K = [b]A \pmod p = [b]([a]G) \pmod p.$$ - -[DH1976]: https://ee.stanford.edu/~hellman/publications/24.pdf - -A potential eavesdropper would need to derive $K = [ab]g \pmod p$ knowing only -$g, p, A = [a]G,$ and $B = [b]G$: in other words, they would need to either get the -discrete logarithm $a$ from $A = [a]G$ or $b$ from $B = [b]G,$ which we assume to be -computationally infeasible in $\mathbb{F}_p^\times.$ - -More generally, protocols that use similar ideas to Diffie–Hellman are used throughout -cryptography. One way of instantiating a cryptographic group is as an -[elliptic curve](curves.md). Before we go into detail on elliptic curves, we'll describe -some algorithms that can be used for any group. - -## Multiscalar multiplication - -### TODO: Pippenger's algorithm -Reference: https://jbootle.github.io/Misc/pippenger.pdf diff --git a/book/src/background/pc-ipa.md b/book/src/background/pc-ipa.md deleted file mode 100644 index 63410c8ddb..0000000000 --- a/book/src/background/pc-ipa.md +++ /dev/null @@ -1,80 +0,0 @@ -# Polynomial commitment using inner product argument -We want to commit to some polynomial $p(X) \in \mathbb{F}_p[X]$, and be able to provably -evaluate the committed polynomial at arbitrary points. The naive solution would be for the -prover to simply send the polynomial's coefficients to the verifier: however, this -requires $O(n)$ communication. Our polynomial commitment scheme gets the job done using -$O(\log n)$ communication. - -### `Setup` -Given a parameter $d = 2^k,$ we generate the common reference string -$\sigma = (\mathbb{G}, \mathbf{G}, H, \mathbb{F}_p)$ defining certain constants for this -scheme: -* $\mathbb{G}$ is a group of prime order $p;$ -* $\mathbf{G} \in \mathbb{G}^d$ is a vector of $d$ random group elements; -* $H \in \mathbb{G}$ is a random group element; and -* $\mathbb{F}_p$ is the finite field of order $p.$ - -### `Commit` -The Pedersen vector commitment $\text{Commit}$ is defined as - -$$\text{Commit}(\sigma, p(X); r) = \langle\mathbf{a}, \mathbf{G}\rangle + [r]H,$$ - -for some polynomial $p(X) \in \mathbb{F}_p[X]$ and some blinding factor -$r \in \mathbb{F}_p.$ Here, each element of the vector $\mathbf{a}_i \in \mathbb{F}_p$ is -the coefficient for the $i$th degree term of $p(X),$ and $p(X)$ is of maximal degree -$d - 1.$ - -### `Open` (prover) and `OpenVerify` (verifier) -The modified inner product argument is an argument of knowledge for the relation - -$$\boxed{\{((P, x, v); (\mathbf{a}, r)): P = \langle\mathbf{a}, \mathbf{G}\rangle + [r]H, v = \langle\mathbf{a}, \mathbf{b}\rangle\}},$$ - -where $\mathbf{b} = (1, x, x^2, \cdots, x^{d-1})$ is composed of increasing powers of the -evaluation point $x.$ This allows a prover to demonstrate to a verifier that the -polynomial contained “inside” the commitment $P$ evaluates to $v$ at $x,$ and moreover, -that the committed polynomial has maximum degree $d − 1.$ - -The inner product argument proceeds in $k = \log_2 d$ rounds. For our purposes, it is -sufficient to know about its final outputs, while merely providing intuition about the -intermediate rounds. (Refer to Section 3 in the [Halo] paper for a full explanation.) - -[Halo]: https://eprint.iacr.org/2019/1021.pdf - -Before beginning the argument, the verifier selects a random group element $U$ and sends it -to the prover. We initialize the argument at round $k,$ with the vectors -$\mathbf{a}^{(k)} := \mathbf{a},$ $\mathbf{G}^{(k)} := \mathbf{G}$ and -$\mathbf{b}^{(k)} := \mathbf{b}.$ In each round $j = k, k-1, \cdots, 1$: - -* the prover computes two values $L_j$ and $R_j$ by taking some inner product of - $\mathbf{a}^{(j)}$ with $\mathbf{G}^{(j)}$ and $\mathbf{b}^{(j)}$. Note that are in some - sense "cross-terms": the lower half of $\mathbf{a}$ is used with the higher half of - $\mathbf{G}$ and $\mathbf{b}$, and vice versa: - -$$ -\begin{aligned} -L_j &= \langle\mathbf{a_{lo}^{(j)}}, \mathbf{G_{hi}^{(j)}}\rangle + [l_j]H + [\langle\mathbf{a_{lo}^{(j)}}, \mathbf{b_{hi}^{(j)}}\rangle] U\\ -R_j &= \langle\mathbf{a_{hi}^{(j)}}, \mathbf{G_{lo}^{(j)}}\rangle + [r_j]H + [\langle\mathbf{a_{hi}^{(j)}}, \mathbf{b_{lo}^{(j)}}\rangle] U\\ -\end{aligned} -$$ - -* the verifier issues a random challenge $u_j$; -* the prover uses $u_j$ to compress the lower and higher halves of $\mathbf{a}^{(j)}$, - thus producing a new vector of half the original length - $$\mathbf{a}^{(j-1)} = \mathbf{a_{hi}^{(j)}}\cdot u_j^{-1} + \mathbf{a_{lo}^{(j)}}\cdot u_j.$$ - The vectors $\mathbf{G}^{(j)}$ and $\mathbf{b}^{(j)}$ are similarly compressed to give - $\mathbf{G}^{(j-1)}$ and $\mathbf{b}^{(j-1)}$. -* $\mathbf{a}^{(j-1)}$, $\mathbf{G}^{(j-1)}$ and $\mathbf{b}^{(j-1)}$ are input to the - next round $j - 1.$ - -Note that at the end of the last round $j = 1,$ we are left with $a := \mathbf{a}^{(0)}$, -$G := \mathbf{G}^{(0)}$, $b := \mathbf{b}^{(0)},$ each of length 1. The intuition is that -these final scalars, together with the challenges $\{u_j\}$ and "cross-terms" -$\{L_j, R_j\}$ from each round, encode the compression in each round. Since the prover did -not know the challenges $U, \{u_j\}$ in advance, they would have been unable to manipulate -the round compressions. Thus, checking a constraint on these final terms should enforce -that the compression had been performed correctly, and that the original $\mathbf{a}$ -satisfied the relation before undergoing compression. - -Note that $G, b$ are simply rearrangements of the publicly known $\mathbf{G}, \mathbf{b},$ -with the round challenges $\{u_j\}$ mixed in: this means the verifier can compute $G, b$ -independently and verify that the prover had provided those same values. diff --git a/book/src/background/plonkish.md b/book/src/background/plonkish.md deleted file mode 100644 index 47642193e8..0000000000 --- a/book/src/background/plonkish.md +++ /dev/null @@ -1,82 +0,0 @@ -# [WIP] PLONKish arithmetization - -We call the field over which the circuit is defined $\mathbb{F} = \mathbb{F}_p$. - -Let $n = 2^k$, and assume that $\omega$ is a primitive root of unity of order $n$ in -$\mathbb{F}^\times$, so that $\mathbb{F}^\times$ has a multiplicative subgroup -$\mathcal{H} = \{1, \omega, \omega^2, \cdots, \omega^{n-1}\}$. This forms a Lagrange -basis corresponding to the elements in the subgroup. - -## Polynomial rules -A polynomial rule defines a constraint that must hold between its specified columns at -every row (i.e. at every element in the multiplicative subgroup). - -e.g. - -```text -a * sa + b * sb + a * b * sm + c * sc + PI = 0 -``` - -## Columns -- **fixed columns**: fixed for all instances of a particular circuit. These include - selector columns, which toggle parts of a polynomial rule "on" or "off" to form a - "custom gate". They can also include any other fixed data. -- **advice columns**: variable values assigned in each instance of the circuit. - Corresponds to the prover's secret witness. -- **public input**: like advice columns, but publicly known values. - -Each column is a vector of $n$ values, e.g. $\mathbf{a} = [a_0, a_1, \cdots, a_{n-1}]$. We -can think of the vector as the evaluation form of the column polynomial -$a(X), X \in \mathcal{H}.$ To recover the coefficient form, we can use -[Lagrange interpolation](polynomials.md#lagrange-interpolation), such that -$a(\omega^i) = a_i.$ - -## Equality constraints -- Define permutation between a set of columns, e.g. $\sigma(a, b, c)$ -- Assert equalities between specific cells in these columns, e.g. $b_1 = c_0$ -- Construct permuted columns which should evaluate to same value as original columns - -## Permutation grand product -$$Z(\omega^i) := \prod_{0 \leq j \leq i} \frac{C_k(\omega^j) + \beta\delta^k \omega^j + \gamma}{C_k(\omega^j) + \beta S_k(\omega^j) + \gamma},$$ -where $i = 0, \cdots, n-1$ indexes over the size of the multiplicative subgroup, and -$k = 0, \cdots, m-1$ indexes over the advice columns involved in the permutation. This is -a running product, where each term includes the cumulative product of the terms before it. - -> TODO: what is $\delta$? keep columns linearly independent - -Check the constraints: - -1. First term is equal to one - $$\mathcal{L}_0(X) \cdot (1 - Z(X)) = 0$$ - -2. Running product is well-constructed. For each row, we check that this holds: - $$Z(\omega^i) \cdot{(C(\omega^i) + \beta S_k(\omega^i) + \gamma)} - Z(\omega^{i-1}) \cdot{(C(\omega^i) + \delta^k \beta \omega^i + \gamma)} = 0$$ - Rearranging gives - $$Z(\omega^i) = Z(\omega^{i-1}) \frac{C(\omega^i) + \beta\delta^k \omega^i + \gamma}{C(\omega^i) + \beta S_k(\omega^i) + \gamma},$$ - which is how we defined the grand product polynomial in the first place. - -### Lookup -Reference: [Generic Lookups with PLONK (DRAFT)](/LTPc5f-3S0qNF6MtwD-Tdg?view) - -### Vanishing argument -We want to check that the expressions defined by the gate constraints, permutation -constraints and lookup constraints evaluate to zero at all elements in the multiplicative -subgroup. To do this, the prover collapses all the expressions into one polynomial -$$H(X) = \sum_{i=0}^e y^i E_i(X),$$ -where $e$ is the number of expressions and $y$ is a random challenge used to keep the -constraints linearly independent. The prover then divides this by the vanishing polynomial -(see section: [Vanishing polynomial](polynomials.md#vanishing-polynomial)) and commits to -the resulting quotient - -$$\text{Commit}(Q(X)), \text{where } Q(X) = \frac{H(X)}{Z_H(X)}.$$ - -The verifier responds with a random evaluation point $x,$ to which the prover replies with -the claimed evaluations $q = Q(x), \{e_i\}_{i=0}^e = \{E_i(x)\}_{i=0}^e.$ Now, all that -remains for the verifier to check is that the evaluations satisfy - -$$q \stackrel{?}{=} \frac{\sum_{i=0}^e y^i e_i}{Z_H(x)}.$$ - -Notice that we have yet to check that the committed polynomials indeed evaluate to the -claimed values at -$x, q \stackrel{?}{=} Q(x), \{e_i\}_{i=0}^e \stackrel{?}{=} \{E_i(x)\}_{i=0}^e.$ -This check is handled by the polynomial commitment scheme (described in the next section). diff --git a/book/src/background/polynomials.md b/book/src/background/polynomials.md deleted file mode 100644 index 7846233724..0000000000 --- a/book/src/background/polynomials.md +++ /dev/null @@ -1,289 +0,0 @@ -# Polynomials - -Let $A(X)$ be a polynomial over $\mathbb{F}_p$ with formal indeterminate $X$. As an example, - -$$ -A(X) = a_0 + a_1 X + a_2 X^2 + a_3 X^3 -$$ - -defines a degree-$3$ polynomial. $a_0$ is referred to as the constant term. Polynomials of -degree $n-1$ have $n$ coefficients. We will often want to compute the result of replacing -the formal indeterminate $X$ with some concrete value $x$, which we denote by $A(x)$. - -> In mathematics this is commonly referred to as "evaluating $A(X)$ at a point $x$". -> The word "point" here stems from the geometrical usage of polynomials in the form -> $y = A(x)$, where $(x, y)$ is the coordinate of a point in two-dimensional space. -> However, the polynomials we deal with are almost always constrained to equal zero, and -> $x$ will be an [element of some field](fields.md). This should not be confused -> with points on an [elliptic curve](curves.md), which we also make use of, but never in -> the context of polynomial evaluation. - -Important notes: - -* Multiplication of polynomials produces a product polynomial that is the sum of the - degrees of its factors. Polynomial division subtracts from the degree. - $$\deg(A(X)B(X)) = \deg(A(X)) + \deg(B(X)),$$ - $$\deg(A(X)/B(X)) = \deg(A(X)) -\deg(B(X)).$$ -* Given a polynomial $A(X)$ of degree $n-1$, if we obtain $n$ evaluations of the - polynomial at distinct points then these evaluations perfectly define the polynomial. In - other words, given these evaluations we can obtain a unique polynomial $A(X)$ of degree - $n-1$ via polynomial interpolation. -* $[a_0, a_1, \cdots, a_{n-1}]$ is the **coefficient representation** of the polynomial - $A(X)$. Equivalently, we could use its **evaluation representation** - $$[(x_0, A(x_0)), (x_1, A(x_1)), \cdots, (x_{n-1}, A(x_{n-1}))]$$ - at $n$ distinct points. Either representation uniquely specifies the same polynomial. - -> #### (aside) Horner's rule -> Horner's rule allows for efficient evaluation of a polynomial of degree $n-1$, using -> only $n-1$ multiplications and $n-1$ additions. It is the following identity: -> $$\begin{aligned}a_0 &+ a_1X + a_2X^2 + \cdots + a_{n-1}X^{n-1} \\ &= a_0 + X\bigg( a_1 + X \Big( a_2 + \cdots + X(a_{n-2} + X a_{n-1}) \Big)\!\bigg),\end{aligned}$$ - -## Fast Fourier Transform (FFT) -The FFT is an efficient way of converting between the coefficient and evaluation -representations of a polynomial. It evaluates the polynomial at the $n$th roots of unity -$\{\omega^0, \omega^1, \cdots, \omega^{n-1}\},$ where $\omega$ is a primitive $n$th root -of unity. By exploiting symmetries in the roots of unity, each round of the FFT reduces -the evaluation into a problem only half the size. Most commonly we use polynomials of -length some power of two, $n = 2^k$, and apply the halving reduction recursively. - -### Motivation: Fast polynomial multiplication -In the coefficient representation, it takes $O(n^2)$ operations to multiply two -polynomials $A(X)\cdot B(X) = C(X)$: - -$$ -\begin{aligned} -A(X) &= a_0 + a_1X + a_2X^2 + \cdots + a_{n-1}X^{n-1}, \\ -B(X) &= b_0 + b_1X + b_2X^2 + \cdots + b_{n-1}X^{n-1}, \\ -C(X) &= a_0\cdot (b_0 + b_1X + b_2X^2 + \cdots + b_{n-1}X^{n-1}) \\ -&+ a_1X\cdot (b_0 + b_1X + b_2X^2 + \cdots + b_{n-1}X^{n-1})\\ -&+ \cdots \\ -&+ a_{n-1}X^{n-1} \cdot (b_0 + b_1X + b_2X^2 + \cdots + b_{n-1}X^{n-1}), -\end{aligned} -$$ - -where each of the $n$ terms in the first polynomial has to be multiplied by the $n$ terms -of the second polynomial. - -In the evaluation representation, however, polynomial multiplication only requires $O(n)$ -operations: - -$$ -\begin{aligned} -A&: \{(x_0, A(x_0)), (x_1, A(x_1)), \cdots, (x_{n-1}, A(x_{n-1}))\}, \\ -B&: \{(x_0, B(x_0)), (x_1, B(x_1)), \cdots, (x_{n-1}, B(x_{n-1}))\}, \\ -C&: \{(x_0, A(x_0)B(x_0)), (x_1, A(x_1)B(x_1)), \cdots, (x_{n-1}, A(x_{n-1})B(x_{n-1}))\}, -\end{aligned} -$$ - -where each evaluation is multiplied pointwise. - -This suggests the following strategy for fast polynomial multiplication: - -1. Evaluate polynomials at all $n$ points; -2. Perform fast pointwise multiplication in the evaluation representation ($O(n)$); -3. Convert back to the coefficient representation. - -The challenge now is how to **evaluate** and **interpolate** the polynomials efficiently. -Naively, evaluating a polynomial at $n$ points would require $O(n^2)$ operations (we use -the $O(n)$ Horner's rule at each point): - -$$ -\begin{bmatrix} -A(1) \\ -A(\omega) \\ -A(\omega^2) \\ -\vdots \\ -A(\omega^{n-1}) -\end{bmatrix} = -\begin{bmatrix} -1&1&1&\dots&1 \\ -1&\omega&\omega^2&\dots&\omega^{n-1} \\ -1&\omega^2&\omega^{2\cdot2}&\dots&\omega^{2\cdot(n-1)} \\ -\vdots&\vdots&\vdots& &\vdots \\ -1&\omega^{n-1}&\omega^{2(n-1)}&\cdots&\omega^{(n-1)^2}\\ -\end{bmatrix} \cdot -\begin{bmatrix} -a_0 \\ -a_1 \\ -a_2 \\ -\vdots \\ -a_{n-1} -\end{bmatrix}. -$$ - -For convenience, we will denote the matrices above as: -$$\hat{\mathbf{A}} = \mathbf{V}_\omega \cdot \mathbf{A}. $$ - -($\hat{\mathbf{A}}$ is known as the *Discrete Fourier Transform* of $\mathbf{A}$; -$\mathbf{V}_\omega$ is also called the *Vandermonde matrix*.) - -### The (radix-2) Cooley-Tukey algorithm -Our strategy is to divide a DFT of size $n$ into two interleaved DFTs of size $n/2$. Given -the polynomial $A(X) = a_0 + a_1X + a_2X^2 + \cdots + a_{n-1}X^{n-1},$ we split it up into -even and odd terms: - -$$ -\begin{aligned} -A_{\text{even}} &= a_0 + a_2X + \cdots + a_{n-2}X^{\frac{n}{2} - 1}, \\ -A_{\text{odd}} &= a_1 + a_3X + \cdots + a_{n-1}X^{\frac{n}{2} - 1}. \\ -\end{aligned} -$$ - -To recover the original polynomial, we do -$A(X) = A_{\text{even}} (X^2) + X A_{\text{odd}}(X^2).$ - -Trying this out on points $\omega_n^i$ and $\omega_n^{\frac{n}{2} + i}$, -$i \in [0..\frac{n}{2}-1],$ we start to notice some symmetries: - -$$ -\begin{aligned} -A(\omega_n^i) &= A_{\text{even}} ((\omega_n^i)^2) + \omega_n^i A_{\text{odd}}((\omega_n^i)^2), \\ -A(\omega_n^{\frac{n}{2} + i}) &= A_{\text{even}} ((\omega_n^{\frac{n}{2} + i})^2) + \omega_n^{\frac{n}{2} + i} A_{\text{odd}}((\omega_n^{\frac{n}{2} + i})^2) \\ -&= A_{\text{even}} ((-\omega_n^i)^2) - \omega_n^i A_{\text{odd}}((-\omega_n^i)^2) \leftarrow\text{(negation lemma)} \\ -&= A_{\text{even}} ((\omega_n^i)^2) - \omega_n^i A_{\text{odd}}((\omega_n^i)^2). -\end{aligned} -$$ - -Notice that we are only evaluating $A_{\text{even}}(X)$ and $A_{\text{odd}}(X)$ over half -the domain $\{(\omega_n^0)^2, (\omega_n)^2, \cdots, (\omega_n^{\frac{n}{2} -1})^2\} = \{\omega_{n/2}^i\}, i = [0..\frac{n}{2}-1]$ (halving lemma). -This gives us all the terms we need to reconstruct $A(X)$ over the full domain -$\{\omega^0, \omega, \cdots, \omega^{n -1}\}$: which means we have transformed a -length-$n$ DFT into two length-$\frac{n}{2}$ DFTs. - -We choose $n = 2^k$ to be a power of two (by zero-padding if needed), and apply this -divide-and-conquer strategy recursively. By the Master Theorem[^master-thm], this gives us -an evaluation algorithm with $O(n\log_2n)$ operations, also known as the Fast Fourier -Transform (FFT). - -### Inverse FFT -So we've evaluated our polynomials and multiplied them pointwise. What remains is to -convert the product from the evaluation representation back to coefficient representation. -To do this, we simply call the FFT on the evaluation representation. However, this time we -also: -- replace $\omega^i$ by $\omega^{-i}$ in the Vandermonde matrix, and -- multiply our final result by a factor of $1/n$. - -In other words: -$$\mathbf{A} = \frac{1}{n} \mathbf{V}_{\omega^{-1}} \cdot \hat{\mathbf{A}}. $$ - -(To understand why the inverse FFT has a similar form to the FFT, refer to Slide 13-1 of -[^ifft]. The below image was also taken from [^ifft].) - -![](https://i.imgur.com/lSw30zo.png) - - -## The Schwartz-Zippel lemma -The Schwartz-Zippel lemma informally states that "different polynomials are different at -most points." Formally, it can be written as follows: - -> Let $p(x_1, x_2, \cdots, x_n)$ be a nonzero polynomial of $n$ variables with degree $d$. -> Let $S$ be a finite set of numbers with at least $d$ elements in it. If we choose random -> $\alpha_1, \alpha_2, \cdots, \alpha_n$ from $S$, -> $$\text{Pr}[p(\alpha_1, \alpha_2, \cdots, \alpha_n) = 0] \leq \frac{d}{|S|}.$$ - -In the familiar univariate case $p(X)$, this reduces to saying that a nonzero polynomial -of degree $d$ has at most $d$ roots. - -The Schwartz-Zippel lemma is used in polynomial equality testing. Given two multi-variate -polynomials $p_1(x_1,\cdots,x_n)$ and $p_2(x_1,\cdots,x_n)$ of degrees $d_1, d_2$ -respectively, we can test if -$p_1(\alpha_1, \cdots, \alpha_n) - p_2(\alpha_1, \cdots, \alpha_n) = 0$ for random -$\alpha_1, \cdots, \alpha_n \leftarrow S,$ where the size of $S$ is at least -$|S| \geq (d_1 + d_2).$ If the two polynomials are identical, this will always be true, -whereas if the two polynomials are different then the equality holds with probability at -most $\frac{\max(d_1,d_2)}{|S|}$. - -## Vanishing polynomial -Consider the order-$n$ multiplicative subgroup $\mathcal{H}$ with primitive root of unity -$\omega$. For all $\omega^i \in \mathcal{H}, i \in [n-1],$ we have -$(\omega^i)^n = (\omega^n)^i = (\omega^0)^i = 1.$ In other words, every element of -$\mathcal{H}$ fulfils the equation - -$$ -\begin{aligned} -Z_H(X) &= X^n - 1 \\ -&= (X-\omega^0)(X-\omega^1)(X-\omega^2)\cdots(X-\omega^{n-1}), -\end{aligned} -$$ - -meaning every element is a root of $Z_H(X).$ We call $Z_H(X)$ the **vanishing polynomial** -over $\mathcal{H}$ because it evaluates to zero on all elements of $\mathcal{H}.$ - -This comes in particularly handy when checking polynomial constraints. For instance, to -check that $A(X) + B(X) = C(X)$ over $\mathcal{H},$ we simply have to check that -$A(X) + B(X) - C(X)$ is some multiple of $Z_H(X)$. In other words, if dividing our -constraint by the vanishing polynomial still yields some polynomial -$\frac{A(X) + B(X) - C(X)}{Z_H(X)} = H(X),$ we are satisfied that $A(X) + B(X) - C(X) = 0$ -over $\mathcal{H}.$ - -## Lagrange basis functions - -> TODO: explain what a basis is in general (briefly). - -Polynomials are commonly written in the monomial basis (e.g. $X, X^2, ... X^n$). However, -when working over a multiplicative subgroup of order $n$, we find a more natural expression -in the Lagrange basis. - -Consider the order-$n$ multiplicative subgroup $\mathcal{H}$ with primitive root of unity -$\omega$. The Lagrange basis corresponding to this subgroup is a set of functions -$\{\mathcal{L}_i\}_{i = 0}^{n-1}$, where - -$$ -\mathcal{L_i}(\omega^j) = \begin{cases} -1 & \text{if } i = j, \\ -0 & \text{otherwise.} -\end{cases} -$$ - -We can write this more compactly as $\mathcal{L_i}(\omega^j) = \delta_{ij},$ where -$\delta$ is the Kronecker delta function. - -Now, we can write our polynomial as a linear combination of Lagrange basis functions, - -$$A(X) = \sum_{i = 0}^{n-1} a_i\mathcal{L_i}(X), X \in \mathcal{H},$$ - -which is equivalent to saying that $p(X)$ evaluates to $a_0$ at $\omega^0$, -to $a_1$ at $\omega^1$, to $a_2$ at $\omega^2, \cdots,$ and so on. - -When working over a multiplicative subgroup, the Lagrange basis function has a convenient -sparse representation of the form - -$$ -\mathcal{L}_i(X) = \frac{c_i\cdot(X^{n} - 1)}{X - \omega^i}, -$$ - -where $c_i$ is the barycentric weight. (To understand how this form was derived, refer to -[^barycentric].) For $i = 0,$ we have -$c = 1/n \implies \mathcal{L}_0(X) = \frac{1}{n} \frac{(X^{n} - 1)}{X - 1}$. - -Suppose we are given a set of evaluation points $\{x_0, x_1, \cdots, x_{n-1}\}$. -Since we cannot assume that the $x_i$'s form a multiplicative subgroup, we consider also -the Lagrange polynomials $\mathcal{L}_i$'s in the general case. Then we can construct: - -$$ -\mathcal{L}_i(X) = \prod_{j\neq i}\frac{X - x_j}{x_i - x_j}, i \in [0..n-1]. -$$ - -Here, every $X = x_j \neq x_i$ will produce a zero numerator term $(x_j - x_j),$ causing -the whole product to evaluate to zero. On the other hand, $X= x_i$ will evaluate to -$\frac{x_i - x_j}{x_i - x_j}$ at every term, resulting in an overall product of one. This -gives the desired Kronecker delta behaviour $\mathcal{L_i}(x_j) = \delta_{ij}$ on the -set $\{x_0, x_1, \cdots, x_{n-1}\}$. - -### Lagrange interpolation -Given a polynomial in its evaluation representation - -$$A: \{(x_0, A(x_0)), (x_1, A(x_1)), \cdots, (x_{n-1}, A(x_{n-1}))\},$$ - -we can reconstruct its coefficient form in the Lagrange basis: - -$$A(X) = \sum_{i = 0}^{n-1} A(x_i)\mathcal{L_i}(X), $$ - -where $X \in \{x_0, x_1,\cdots, x_{n-1}\}.$ - -## References -[^master-thm]: [Dasgupta, S., Papadimitriou, C. H., & Vazirani, U. V. (2008). "Algorithms" (ch. 2). New York: McGraw-Hill Higher Education.](https://people.eecs.berkeley.edu/~vazirani/algorithms/chap2.pdf) - -[^ifft]: [Golin, M. (2016). "The Fast Fourier Transform and Polynomial Multiplication" [lecture notes], COMP 3711H Design and Analysis of Algorithms, Hong Kong University of Science and Technology.](http://www.cs.ust.hk/mjg_lib/Classes/COMP3711H_Fall16/lectures/FFT_Slides.pdf) - -[^barycentric]: [Berrut, J. and Trefethen, L. (2004). "Barycentric Lagrange Interpolation."](https://people.maths.ox.ac.uk/trefethen/barycentric.pdf) diff --git a/book/src/background/recursion.md b/book/src/background/recursion.md deleted file mode 100644 index 4b6ce7430f..0000000000 --- a/book/src/background/recursion.md +++ /dev/null @@ -1,26 +0,0 @@ -## Recursion -> Alternative terms: Induction; Accumulation scheme; Proof-carrying data - -However, the computation of $G$ requires a length-$2^k$ multiexponentiation -$\langle \mathbf{G}, \mathbf{s}\rangle,$ where $\mathbf{s}$ is composed of the round -challenges $u_1, \cdots, u_k$ arranged in a binary counting structure. This is the -linear-time computation that we want to amortise across a batch of proof instances. -Instead of computing $G,$ notice that we can express $G$ as a commitment to a polynomial - -$$G = \text{Commit}(\sigma, g(X, u_1, \cdots, u_k)),$$ - -where $g(X, u_1, \cdots, u_k) := \prod_{i=1}^k (u_i + u_i^{-1}X^{2^{i-1}})$ is a -polynomial with degree $2^k - 1.$ - -| | | -| -------- | -------- | -| | Since $G$ is a commitment, it can be checked in an inner product argument. The verifier circuit witnesses $G$ and brings $G, u_1, \cdots, u_k$ out as public inputs to the proof $\pi.$ The next verifier instance checks $\pi$ using the inner product argument; this includes checking that $G = \text{Commit}(g(X, u_1, \cdots, u_k))$ evaluates at some random point to the expected value for the given challenges $u_1, \cdots, u_k.$ Recall from the [previous section](#Polynomial-commitment-using-inner-product-argument) that this check only requires $\log d$ work.

At the end of checking $\pi$ and $G,$ the circuit is left with a new $G',$ along with the $u_1', \cdots, u_k'$ challenges sampled for the check. To fully accept $\pi$ as valid, we should perform a linear-time computation of $G' = \langle\mathbf{G}, \mathbf{s}'\rangle$. Once again, we delay this computation by witnessing $G'$ and bringing $G', u_1', \cdots, u_k'$ out as public inputs to the proof $\pi'.$

This goes on from one proof instance to the next, until we are satisfied with the size of our batch of proofs. We finally perform a single linear-time computation, thus deciding the validity of the whole batch. | - -We recall from the section [Cycles of curves](curves.md#cycles-of-curves) that we can -instantiate this protocol over a two-cycle, where a proof produced by one curve is -efficiently verified in the circuit of the other curve. However, some of these verifier -checks can actually be efficiently performed in the native circuit; these are "deferred" -to the next native circuit (see diagram below) instead of being immediately passed over to -the other curve. - -![](https://i.imgur.com/l4HrYgE.png) diff --git a/book/src/concepts.md b/book/src/concepts.md deleted file mode 100644 index 328529c65f..0000000000 --- a/book/src/concepts.md +++ /dev/null @@ -1,5 +0,0 @@ -# Concepts - -First we'll describe the concepts behind zero-knowledge proof systems; the -*arithmetization* (kind of circuit description) used by Halo 2; and the -abstractions we use to build circuit implementations. diff --git a/book/src/concepts/arithmetization.md b/book/src/concepts/arithmetization.md deleted file mode 100644 index 65b25c8274..0000000000 --- a/book/src/concepts/arithmetization.md +++ /dev/null @@ -1,57 +0,0 @@ -# PLONKish Arithmetization - -The arithmetization used by Halo 2 comes from [PLONK](https://eprint.iacr.org/2019/953), or -more precisely its extension UltraPLONK that supports custom gates and lookup arguments. We'll -call it [***PLONKish***](https://twitter.com/feministPLT/status/1413815927704014850). - -***PLONKish circuits*** are defined in terms of a rectangular matrix of values. We refer to -***rows***, ***columns***, and ***cells*** of this matrix with the conventional meanings. - -A PLONKish circuit depends on a ***configuration***: - -* A finite field $\mathbb{F}$, where cell values (for a given statement and witness) will be - elements of $\mathbb{F}$. -* The number of columns in the matrix, and a specification of each column as being - ***fixed***, ***advice***, or ***instance***. Fixed columns are fixed by the circuit; - advice columns correspond to witness values; and instance columns are normally used for - public inputs (technically, they can be used for any elements shared between the prover - and verifier). - -* A subset of the columns that can participate in equality constraints. - -* A ***maximum constraint degree***. - -* A sequence of ***polynomial constraints***. These are multivariate polynomials over - $\mathbb{F}$ that must evaluate to zero *for each row*. The variables in a polynomial - constraint may refer to a cell in a given column of the current row, or a given column of - another row relative to this one (with wrap-around, i.e. taken modulo $n$). The maximum - degree of each polynomial is given by the maximum constraint degree. - -* A sequence of ***lookup arguments*** defined over tuples of ***input expressions*** - (which are multivariate polynomials as above) and ***table columns***. - -A PLONKish circuit also defines: - -* The number of rows $n$ in the matrix. $n$ must correspond to the size of a multiplicative - subgroup of $\mathbb{F}^\times$; typically a power of two. - -* A sequence of ***equality constraints***, which specify that two given cells must have equal - values. - -* The values of the fixed columns at each row. - -From a circuit description we can generate a ***proving key*** and a ***verification key***, -which are needed for the operations of proving and verification for that circuit. - -> Note that we specify the ordering of columns, polynomial constraints, lookup arguments, and -> equality constraints, even though these do not affect the meaning of the circuit. This makes -> it easier to define the generation of proving and verification keys as a deterministic -> process. - -Typically, a configuration will define polynomial constraints that are switched off and on by -***selectors*** defined in fixed columns. For example, a constraint $q_i \cdot p(...) = 0$ can -be switched off for a particular row $i$ by setting $q_i = 0$. In this case we sometimes refer -to a set of constraints controlled by a set of selector columns that are designed to be used -together, as a ***gate***. Typically there will be a ***standard gate*** that supports generic -operations like field multiplication and division, and possibly also ***custom gates*** that -support more specialized operations. diff --git a/book/src/concepts/chips.md b/book/src/concepts/chips.md deleted file mode 100644 index 4770d97237..0000000000 --- a/book/src/concepts/chips.md +++ /dev/null @@ -1,87 +0,0 @@ -# Chips - -The previous section gives a fairly low-level description of a circuit. When implementing circuits we will -typically use a higher-level API which aims for the desirable characteristics of auditability, -efficiency, modularity, and expressiveness. - -Some of the terminology and concepts used in this API are taken from an analogy with -integrated circuit design and layout. [As for integrated circuits](https://opencores.org/), -the above desirable characteristics are easier to obtain by composing ***chips*** that provide -efficient pre-built implementations of particular functionality. - -For example, we might have chips that implement particular cryptographic primitives such as a -hash function or cipher, or algorithms like scalar multiplication or pairings. - -In PLONKish circuits, it is possible to build up arbitrary logic just from standard gates that do -field multiplication and addition. However, very significant efficiency gains can be obtained by -using custom gates. - -Using our API, we define chips that "know" how to use particular sets of custom gates. This -creates an abstraction layer that isolates the implementation of a high-level circuit from the -complexity of using custom gates directly. - -> Even if we sometimes need to "wear two hats", by implementing both a high-level circuit and -> the chips that it uses, the intention is that this separation will result in code that is -> easier to understand, audit, and maintain/reuse. This is partly because some potential -> implementation errors are ruled out by construction. - -Gates in PLONKish circuits refer to cells by ***relative references***, i.e. to the cell in a given -column, and the row at a given offset relative to the one in which the gate's selector is set. We -call this an ***offset reference*** when the offset is nonzero (i.e. offset references are a subset -of relative references). - -Relative references contrast with ***absolute references*** used in equality constraints, -which can point to any cell. - -The motivation for offset references is to reduce the number of columns needed in the -configuration, which reduces proof size. If we did not have offset references then we would -need a column to hold each value referred to by a custom gate, and we would need to use -equality constraints to copy values from other cells of the circuit into that column. With -offset references, we not only need fewer columns; we also do not need equality constraints to -be supported for all of those columns, which improves efficiency. - -In R1CS (another arithmetization which may be more familiar to some readers, but don't worry -if it isn't), a circuit consists of a "sea of gates" with no semantically significant ordering. -Because of offset references, the order of rows in a PLONKish circuit, on the other hand, *is* -significant. We're going to make some simplifying assumptions and define some abstractions to -tame the resulting complexity: the aim will be that, [at the gadget level](gadgets.md) where -we do most of our circuit construction, we will not have to deal with relative references or -with gate layout explicitly. - -We will partition a circuit into ***regions***, where each region contains a disjoint subset -of cells, and relative references only ever point *within* a region. Part of the responsibility -of a chip implementation is to ensure that gates that make offset references are laid out in -the correct positions in a region. - -Given the set of regions and their ***shapes***, we will use a separate ***floor planner*** -to decide where (i.e. at what starting row) each region is placed. There is a default floor -planner that implements a very general algorithm, but you can write your own floor planner if -you need to. - -Floor planning will in general leave gaps in the matrix, because the gates in a given row did -not use all available columns. These are filled in —as far as possible— by gates that do -not require offset references, which allows them to be placed on any row. - -Chips can also define lookup tables. If more than one table is defined for the same lookup -argument, we can use a ***tag column*** to specify which table is used on each row. It is also -possible to perform a lookup in the union of several tables (limited by the polynomial degree -bound). - -## Composing chips -In order to combine functionality from several chips, we compose them in a tree. The top-level -chip defines a set of fixed, advice, and instance columns, and then specifies how they -should be distributed between lower-level chips. - -In the simplest case, each lower-level chips will use columns disjoint from the other chips. -However, it is allowed to share a column between chips. It is important to optimize the number -of advice columns in particular, because that affects proof size. - -The result (possibly after optimization) is a PLONKish configuration. Our circuit implementation -will be parameterized on a chip, and can use any features of the supported lower-level chips via -the top-level chip. - -Our hope is that less expert users will normally be able to find an existing chip that -supports the operations they need, or only have to make minor modifications to an existing -chip. Expert users will have full control to do the kind of -[circuit optimizations](https://zips.z.cash/protocol/canopy.pdf#circuitdesign) -[that ECC is famous for](https://electriccoin.co/blog/cultivating-sapling-faster-zksnarks/) 🙂. diff --git a/book/src/concepts/gadgets.md b/book/src/concepts/gadgets.md deleted file mode 100644 index aa352db41f..0000000000 --- a/book/src/concepts/gadgets.md +++ /dev/null @@ -1,25 +0,0 @@ -# Gadgets - -When implementing a circuit, we could use the features of the chips we've selected directly. -Typically, though, we will use them via ***gadgets***. This indirection is useful because, -for reasons of efficiency and limitations imposed by PLONKish circuits, the chip interfaces will -often be dependent on low-level implementation details. The gadget interface can provide a more -convenient and stable API that abstracts away from extraneous detail. - -For example, consider a hash function such as SHA-256. The interface of a chip supporting -SHA-256 might be dependent on internals of the hash function design such as the separation -between message schedule and compression function. The corresponding gadget interface can -provide a more convenient and familiar `update`/`finalize` API, and can also handle parts -of the hash function that do not need chip support, such as padding. This is similar to how -[accelerated](https://software.intel.com/content/www/us/en/develop/articles/intel-sha-extensions.html) -[instructions](https://developer.arm.com/documentation/ddi0514/g/introduction/about-the-cortex-a57-processor-cryptography-engine) -for cryptographic primitives on CPUs are typically accessed via software libraries, rather -than directly. - -Gadgets can also provide modular and reusable abstractions for circuit programming -at a higher level, similar to their use in libraries such as -[libsnark](https://github.com/christianlundkvist/libsnark-tutorial) and -[bellman](https://electriccoin.co/blog/bellman-zksnarks-in-rust/). As well as abstracting -*functions*, they can also abstract *types*, such as elliptic curve points or integers of -specific sizes. - diff --git a/book/src/concepts/proofs.md b/book/src/concepts/proofs.md deleted file mode 100644 index fa82c76ffe..0000000000 --- a/book/src/concepts/proofs.md +++ /dev/null @@ -1,91 +0,0 @@ -# Proof systems - -The aim of any ***proof system*** is to be able to prove interesting mathematical or -cryptographic ***statements***. - -Typically, in a given protocol we will want to prove families of statements that differ -in their ***public inputs***. The prover will also need to show that they know some -***private inputs*** that make the statement hold. - -To do this we write down a ***relation***, $\mathcal{R}$, that specifies which -combinations of public and private inputs are valid. - -> The terminology above is intended to be aligned with the -> [ZKProof Community Reference](https://docs.zkproof.org/reference#latest-version). - -To be precise, we should distinguish between the relation $\mathcal{R}$, and its -implementation to be used in a proof system. We call the latter a ***circuit***. - -The language that we use to express circuits for a particular proof system is called an -***arithmetization***. Usually, an arithmetization will define circuits in terms of -polynomial constraints on variables over a field. - -> The _process_ of expressing a particular relation as a circuit is also sometimes called -> "arithmetization", but we'll avoid that usage. - -To create a proof of a statement, the prover will need to know the private inputs, -and also intermediate values, called ***advice*** values, that are used by the circuit. - -We assume that we can compute advice values efficiently from the private and public inputs. -The particular advice values will depend on how we write the circuit, not only on the -high-level statement. - -The private inputs and advice values are collectively called a ***witness***. - -> Some authors use "witness" as just a synonym for private inputs. But in our usage, -> a witness includes advice, i.e. it includes all values that the prover supplies to -> the circuit. - -For example, suppose that we want to prove knowledge of a preimage $x$ of a -hash function $H$ for a digest $y$: - -* The private input would be the preimage $x$. - -* The public input would be the digest $y$. - -* The relation would be $\{(x, y) : H(x) = y\}$. - -* For a particular public input $Y$, the statement would be: $\{(x) : H(x) = Y\}$. - -* The advice would be all of the intermediate values in the circuit implementing the - hash function. The witness would be $x$ and the advice. - -A ***Non-interactive Argument*** allows a ***prover*** to create a ***proof*** for a -given statement and witness. The proof is data that can be used to convince a ***verifier*** -that _there exists_ a witness for which the statement holds. The security property that -such proofs cannot falsely convince a verifier is called ***soundness***. - -A ***Non-interactive Argument of Knowledge*** (***NARK***) further convinces the verifier -that the prover _knew_ a witness for which the statement holds. This security property is -called ***knowledge soundness***, and it implies soundness. - -In practice knowledge soundness is more useful for cryptographic protocols than soundness: -if we are interested in whether Alice holds a secret key in some protocol, say, we need -Alice to prove that _she knows_ the key, not just that it exists. - -Knowledge soundness is formalized by saying that an ***extractor***, which can observe -precisely how the proof is generated, must be able to compute the witness. - -> This property is subtle given that proofs can be ***malleable***. That is, depending on the -> proof system it may be possible to take an existing proof (or set of proofs) and, without -> knowing the witness(es), modify it/them to produce a distinct proof of the same or a related -> statement. Higher-level protocols that use malleable proof systems need to take this into -> account. -> -> Even without malleability, proofs can also potentially be ***replayed***. For instance, -> we would not want Alice in our example to be able to present a proof generated by someone -> else, and have that be taken as a demonstration that she knew the key. - -If a proof yields no information about the witness (other than that a witness exists and was -known to the prover), then we say that the proof system is ***zero knowledge***. - -If a proof system produces short proofs —i.e. of length polylogarithmic in the circuit -size— then we say that it is ***succinct***. A succinct NARK is called a ***SNARK*** -(***Succinct Non-Interactive Argument of Knowledge***). - -> By this definition, a SNARK need not have verification time polylogarithmic in the circuit -> size. Some papers use the term ***efficient*** to describe a SNARK with that property, but -> we'll avoid that term since it's ambiguous for SNARKs that support amortized or recursive -> verification, which we'll get to later. - -A ***zk-SNARK*** is a zero-knowledge SNARK. diff --git a/book/src/design.md b/book/src/design.md deleted file mode 100644 index db8a6d09de..0000000000 --- a/book/src/design.md +++ /dev/null @@ -1,17 +0,0 @@ -# Design - -## Note on Language - -We use slightly different language than others to describe PLONK concepts. Here's the -overview: - -1. We like to think of PLONK-like arguments as tables, where each column corresponds to a - "wire". We refer to entries in this table as "cells". -2. We like to call "selector polynomials" and so on "fixed columns" instead. We then refer - specifically to a "selector constraint" when a cell in a fixed column is being used to - control whether a particular constraint is enabled in that row. -3. We call the other polynomials "advice columns" usually, when they're populated by the - prover. -4. We use the term "rule" to refer to a "gate" like - $$A(X) \cdot q_A(X) + B(X) \cdot q_B(X) + A(X) \cdot B(X) \cdot q_M(X) + C(X) \cdot q_C(X) = 0.$$ - - TODO: Check how consistent we are with this, and update the code and docs to match. diff --git a/book/src/design/gadgets.md b/book/src/design/gadgets.md deleted file mode 100644 index 98fdbf92f5..0000000000 --- a/book/src/design/gadgets.md +++ /dev/null @@ -1,7 +0,0 @@ -# Gadgets - -In this section we document the gadgets and chip designs provided in the `halo2_gadgets` -crate. - -> Neither these gadgets, nor their implementations, have been reviewed, and they should -> not be used in production. diff --git a/book/src/design/gadgets/decomposition.md b/book/src/design/gadgets/decomposition.md deleted file mode 100644 index 94e2349081..0000000000 --- a/book/src/design/gadgets/decomposition.md +++ /dev/null @@ -1,76 +0,0 @@ -# Decomposition -Given a field element $\alpha$, these gadgets decompose it into $W$ $K$-bit windows $$\alpha = k_0 + 2^{K} \cdot k_1 + 2^{2K} \cdot k_2 + \cdots + 2^{(W-1)K} \cdot k_{W-1}$$ where each $k_i$ a $K$-bit value. - -This is done using a running sum $z_i, i \in [0..W).$ We initialize the running sum $z_0 = \alpha,$ and compute subsequent terms $z_{i+1} = \frac{z_i - k_i}{2^{K}}.$ This gives us: - -$$ -\begin{aligned} -z_0 &= \alpha \\ - &= k_0 + 2^{K} \cdot k_1 + 2^{2K} \cdot k_2 + 2^{3K} \cdot k_3 + \cdots, \\ -z_1 &= (z_0 - k_0) / 2^K \\ - &= k_1 + 2^{K} \cdot k_2 + 2^{2K} \cdot k_3 + \cdots, \\ -z_2 &= (z_1 - k_1) / 2^K \\ - &= k_2 + 2^{K} \cdot k_3 + \cdots, \\ - &\vdots \\ -\downarrow &\text{ (in strict mode)} \\ -z_W &= (z_{W-1} - k_{W-1}) / 2^K \\ - &= 0 \text{ (because } z_{W-1} = k_{W-1} \text{)} -\end{aligned} -$$ - -### Strict mode -Strict mode constrains the running sum output $z_{W}$ to be zero, thus range-constraining the field element to be within $W \cdot K$ bits. - -In strict mode, we are also assured that $z_{W-1} = k_{W-1}$ gives us the last window in the decomposition. -## Lookup decomposition -This gadget makes use of a $K$-bit lookup table to decompose a field element $\alpha$ into $K$-bit words. Each $K$-bit word $k_i = z_i - 2^K \cdot z_{i+1}$ is range-constrained by a lookup in the $K$-bit table. - -The region layout for the lookup decomposition uses a single advice column $z$, and two selectors $q_{lookup}$ and $q_{running}.$ -$$ -\begin{array}{|c|c|c|} -\hline - z & q_\mathit{lookup} & q_\mathit{running} \\\hline -\hline - z_0 & 1 & 1 \\\hline - z_1 & 1 & 1 \\\hline -\vdots & \vdots & \vdots \\\hline -z_{n-1} & 1 & 1 \\\hline -z_n & 0 & 0 \\\hline -\end{array} -$$ -### Short range check -Using two $K$-bit lookups, we can range-constrain a field element $\alpha$ to be $n$ bits, where $n \leq K.$ To do this: - -1. Constrain $0 \leq \alpha < 2^K$ to be within $K$ bits using a $K$-bit lookup. -2. Constrain $0 \leq \alpha \cdot 2^{K - n} < 2^K$ to be within $K$ bits using a $K$-bit lookup. - -The short variant of the lookup decomposition introduces a $q_{bitshift}$ selector. The same advice column $z$ has here been renamed to $\textsf{word}$ for clarity: -$$ -\begin{array}{|c|c|c|c|} -\hline -\textsf{word} & q_\mathit{lookup} & q_\mathit{running} & q_\mathit{bitshift} \\\hline -\hline -\alpha & 1 & 0 & 0 \\\hline -\alpha' & 1 & 0 & 1 \\\hline -2^{K-n} & 0 & 0 & 0 \\\hline -\end{array} -$$ - -where $\alpha' = \alpha \cdot 2^{K - n}.$ Note that $2^{K-n}$ is assigned to a fixed column at keygen, and copied in at proving time. This is used in the gate enabled by the $q_\mathit{bitshift}$ selector to check that $\alpha$ was shifted correctly: -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline - 2 & q_\mathit{bitshift} \cdot ((\alpha \cdot 2^{K - n}) - \alpha') \\\hline -\end{array} -$$ - -### Combined lookup expression -Since the lookup decomposition and its short variant both make use of the same lookup table, we combine their lookup input expressions into a single one: - -$$q_\mathit{lookup} \cdot \left(q_\mathit{running} \cdot (z_i - 2^K \cdot z_{i+1}) + (1 - q_\mathit{running}) \cdot \textsf{word} \right)$$ - -where $z_i$ and $\textsf{word}$ are the same cell (but distinguished here for clarity of usage). - -## Short range decomposition -For a short range (for instance, $[0, \texttt{range})$ where $\texttt{range} \leq 8$), we can range-constrain each word using a degree-$\texttt{range}$ polynomial constraint instead of a lookup: $$\RangeCheck{word}{range} = \texttt{word} \cdot (1 - \texttt{word}) \cdots (\texttt{range} - 1 - \texttt{word}).$$ diff --git a/book/src/design/gadgets/ecc.md b/book/src/design/gadgets/ecc.md deleted file mode 100644 index e1025ee98f..0000000000 --- a/book/src/design/gadgets/ecc.md +++ /dev/null @@ -1,50 +0,0 @@ -# Elliptic Curves - -## `EccChip` - -`halo2_gadgets` provides a chip that implements `EccInstructions` using 10 advice columns. -The chip is currently restricted to the Pallas curve, but will be extended to support the -[Vesta curve](https://github.com/zcash/halo2/issues/578) in the near future. - -### Chip assumptions - -A non-exhaustive list of assumptions made by `EccChip`: -- $0$ is not an $x$-coordinate of a valid point on the curve. - - Holds for Pallas because $5$ is not square in $\mathbb{F}_q$. -- $0$ is not a $y$-coordinate of a valid point on the curve. - - Holds for Pallas because $-5$ is not a cube in $\mathbb{F}_q$. - -### Layout - -The following table shows how columns are used by the gates for various chip sub-areas: - -- $W$ - witnessing points. -- $AI$ - incomplete point addition. -- $AC$ - complete point addition. -- $MF$ - Fixed-base scalar multiplication. -- $MVI$ - variable-base scalar multiplication, incomplete rounds. -- $MVC$ - variable-base scalar multiplication, complete rounds. -- $MVO$ - variable-base scalar multiplication, overflow check. - -$$ -\begin{array}{|c||c|c|c|c|c|c|c|c|c|c|} -\hline -\text{Sub-area} & a_0 & a_1 & a_2 & a_3 & a_4 & a_5 & a_6 & a_7 & a_8 & a_9 \\\hline -\hline - W & x & y \\\hline -\hline - AI & x_p & y_p & x_q & y_q \\\hline - & & & x_r & y_r \\\hline -\hline - AC & x_p & y_p & x_q & y_q & \lambda & \alpha & \beta & \gamma & \delta & \\\hline - & & & x_r & y_r \\\hline -\hline - MF & x_p & y_p & x_q & y_q & \text{window} & u \\\hline - & & & x_r & y_r \\\hline -\hline -MVI & x_p & y_p & \lambda_2^{lo} & x_A^{hi} & \lambda_1^{hi} & \lambda_2^{hi} & z^{lo} & x_A^{lo} & \lambda_1^{lo} & z^{hi} \\\hline -\hline -MVC & x_p & y_p & x_q & y_q & \lambda & \alpha & \beta & \gamma & \delta & z^{complete} \\\hline - & & & x_r & y_r \\\hline -\end{array} -$$ diff --git a/book/src/design/gadgets/ecc/addition.md b/book/src/design/gadgets/ecc/addition.md deleted file mode 100644 index a51d9dc165..0000000000 --- a/book/src/design/gadgets/ecc/addition.md +++ /dev/null @@ -1,359 +0,0 @@ -We will use formulae for curve arithmetic using affine coordinates on short Weierstrass curves, -derived from section 4.1 of [Hüseyin Hışıl's thesis](https://core.ac.uk/download/pdf/10898289.pdf). - -## Incomplete addition - -- Inputs: $P = (x_p, y_p), Q = (x_q, y_q)$ -- Output: $R = P \;⸭\; Q = (x_r, y_r)$ - -The formulae from Hışıl's thesis are: - -- $x_3 = \left(\frac{y_1 - y_2}{x_1 - x_2}\right)^2 - x_1 - x_2$ -- $y_3 = \frac{y_1 - y_2}{x_1 - x_2} \cdot (x_1 - x_3) - y_1.$ - -Rename $(x_1, y_1)$ to $(x_q, y_q)$, $(x_2, y_2)$ to $(x_p, y_p)$, and $(x_3, y_3)$ to $(x_r, y_r)$, giving - -- $x_r = \left(\frac{y_q - y_p}{x_q - x_p}\right)^2 - x_q - x_p$ -- $y_r = \frac{y_q - y_p}{x_q - x_p} \cdot (x_q - x_r) - y_q$ - -which is equivalent to - -- $x_r + x_q + x_p = \left(\frac{y_p - y_q}{x_p - x_q}\right)^2$ -- $y_r + y_q = \frac{y_p - y_q}{x_p - x_q} \cdot (x_q - x_r).$ - -Assuming $x_p \neq x_q$, we have - -$ -\begin{array}{lrrll} -&& x_r + x_q + x_p &=& \left(\frac{y_p - y_q}{x_p - x_q}\right)^2 \\[1.2ex] -&\Longleftrightarrow &(x_r + x_q + x_p) \cdot (x_p - x_q)^2 &=& (y_p - y_q)^2 \\[1ex] -&\Longleftrightarrow &(x_r + x_q + x_p) \cdot (x_p - x_q)^2 - (y_p - y_q)^2 &=& 0 \\[1.5ex] -\text{and} \\ -&&y_r + y_q &=& \frac{y_p - y_q}{x_p - x_q} \cdot (x_q - x_r) \\[0.8ex] -&\Longleftrightarrow &(y_r + y_q) \cdot (x_p - x_q) &=& (y_p - y_q) \cdot (x_q - x_r) \\[1ex] -&\Longleftrightarrow &(y_r + y_q) \cdot (x_p - x_q) - (y_p - y_q) \cdot (x_q - x_r) &=& 0. -\end{array} -$ - -So we get the constraints: -- $(x_r + x_q + x_p) \cdot (x_p - x_q)^2 - (y_p - y_q)^2 = 0$ - - Note that this constraint is unsatisfiable for $P \;⸭\; (-P)$ (when $P \neq \mathcal{O}$), - and so cannot be used with arbitrary inputs. -- $(y_r + y_q) \cdot (x_p - x_q) - (y_p - y_q) \cdot (x_q - x_r) = 0.$ - -### Constraints - -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline -4 & q_\text{add-incomplete} \cdot \left( (x_r + x_q + x_p) \cdot (x_p - x_q)^2 - (y_p - y_q)^2 \right) = 0 \\\hline -3 & q_\text{add-incomplete} \cdot \left( (y_r + y_q) \cdot (x_p - x_q) - (y_p - y_q) \cdot (x_q - x_r) \right) = 0 \\\hline -\end{array} -$$ - -## Complete addition - -$\hspace{1em} \begin{array}{rcll} -\mathcal{O} &+& \mathcal{O} &= \mathcal{O} \\[0.4ex] -\mathcal{O} &+& (x_q, y_q) &= (x_q, y_q) \\[0.4ex] - (x_p, y_p) &+& \mathcal{O} &= (x_p, y_p) \\[0.6ex] - (x, y) &+& (x, y) &= [2] (x, y) \\[0.6ex] - (x, y) &+& (x, -y) &= \mathcal{O} \\[0.4ex] - (x_p, y_p) &+& (x_q, y_q) &= (x_p, y_p) \;⸭\; (x_q, y_q), \text{ if } x_p \neq x_q. -\end{array}$ - -Suppose that we represent $\mathcal{O}$ as $(0, 0)$. ($0$ is not an $x$-coordinate of a valid point because we would need $y^2 = x^3 + 5$, and $5$ is not square in $\mathbb{F}_q$. Also $0$ is not a $y$-coordinate of a valid point because $-5$ is not a cube in $\mathbb{F}_q$.) - -$$ -\begin{aligned} -P + Q &= R\\ -(x_p, y_p) + (x_q, y_q) &= (x_r, y_r) \\ - \lambda &= \frac{y_q - y_p}{x_q - x_p} \\ - x_r &= \lambda^2 - x_p - x_q \\ - y_r &= \lambda(x_p - x_r) - y_p -\end{aligned} -$$ - -For the doubling case, Hışıl's thesis tells us that $\lambda$ has to -instead be computed as $\frac{3x^2}{2y}$. - -Define $\mathsf{inv0}(x) = \begin{cases} 0, &\text{if } x = 0 \\ 1/x, &\text{otherwise.} \end{cases}$ - -Witness $\alpha, \beta, \gamma, \delta, \lambda$ where: - -$\hspace{1em} -\begin{array}{rl} -\alpha \,\,=&\!\! \mathsf{inv0}(x_q - x_p) \\[0.4ex] -\beta \,\,=&\!\! \mathsf{inv0}(x_p) \\[0.4ex] -\gamma \,\,=&\!\! \mathsf{inv0}(x_q) \\[0.4ex] -\delta \,\,=&\!\! \begin{cases} - \mathsf{inv0}(y_q + y_p), &\text{if } x_q = x_p \\ - 0, &\text{otherwise} - \end{cases} \\[2.5ex] -\lambda \,\,=&\!\! \begin{cases} - \frac{y_q - y_p}{x_q - x_p}, &\text{if } x_q \neq x_p \\[1.2ex] - \frac{3{x_p}^2}{2y_p} &\text{if } x_q = x_p \wedge y_p \neq 0 \\[0.8ex] - 0, &\text{otherwise.} - \end{cases} -\end{array} -$ - -### Constraints - -$$ -\begin{array}{|c|lcl|l|} -\hline -\text{Degree} & \text{Constraint}\hspace{7em} &&& \text{Meaning} \\\hline -4 & q_\mathit{add} \cdot (x_q - x_p) \cdot ((x_q - x_p) \cdot \lambda - (y_q - y_p)) &=& 0 & x_q \neq x_p \implies \lambda = \frac{y_q - y_p}{x_q - x_p} \\\hline \\[-2.3ex] -5 & q_\mathit{add} \cdot (1 - (x_q - x_p) \cdot \alpha) \cdot \left(2y_p \cdot \lambda - 3{x_p}^2\right) &=& 0 & \begin{cases} x_q = x_p \wedge y_p \neq 0 \implies \lambda = \frac{3{x_p}^2}{2y_p} \\ x_q = x_p \wedge y_p = 0 \implies x_p = 0 \end{cases} \\\hline -6 & q_\mathit{add} \cdot x_p \cdot x_q \cdot (x_q - x_p) \cdot (\lambda^2 - x_p - x_q - x_r) &=& 0 & x_p \neq 0 \wedge x_q \neq 0 \wedge x_q \neq x_p \implies x_r = \lambda^2 - x_p - x_q \\ -6 & q_\mathit{add} \cdot x_p \cdot x_q \cdot (x_q - x_p) \cdot (\lambda \cdot (x_p - x_r) - y_p - y_r) &=& 0 & x_p \neq 0 \wedge x_q \neq 0 \wedge x_q \neq x_p \implies y_r = \lambda \cdot (x_p - x_r) - y_p \\ -6 & q_\mathit{add} \cdot x_p \cdot x_q \cdot (y_q + y_p) \cdot (\lambda^2 - x_p - x_q - x_r) &=& 0 & x_p \neq 0 \wedge x_q \neq 0 \wedge y_q \neq -y_p \implies x_r = \lambda^2 - x_p - x_q \\ -6 & q_\mathit{add} \cdot x_p \cdot x_q \cdot (y_q + y_p) \cdot (\lambda \cdot (x_p - x_r) - y_p - y_r) &=& 0 & x_p \neq 0 \wedge x_q \neq 0 \wedge y_q \neq -y_p \implies y_r = \lambda \cdot (x_p - x_r) - y_p \\\hline -4 & q_\mathit{add} \cdot (1 - x_p \cdot \beta) \cdot (x_r - x_q) &=& 0 & x_p = 0 \implies x_r = x_q \\ -4 & q_\mathit{add} \cdot (1 - x_p \cdot \beta) \cdot (y_r - y_q) &=& 0 & x_p = 0 \implies y_r = y_q \\\hline -4 & q_\mathit{add} \cdot (1 - x_q \cdot \gamma) \cdot (x_r - x_p) &=& 0 & x_q = 0 \implies x_r = x_p \\ -4 & q_\mathit{add} \cdot (1 - x_q \cdot \gamma) \cdot (y_r - y_p) &=& 0 & x_q = 0 \implies y_r = y_p \\\hline -4 & q_\mathit{add} \cdot (1 - (x_q - x_p) \cdot \alpha - (y_q + y_p) \cdot \delta) \cdot x_r &=& 0 & x_q = x_p \wedge y_q = -y_p \implies x_r = 0 \\ -4 & q_\mathit{add} \cdot (1 - (x_q - x_p) \cdot \alpha - (y_q + y_p) \cdot \delta) \cdot y_r &=& 0 & x_q = x_p \wedge y_q = -y_p \implies y_r = 0 \\\hline -\end{array} -$$ - -Max degree: 6 - -### Analysis of constraints -$$ -\begin{array}{rl} -1. & (x_q - x_p) \cdot ((x_q - x_p) \cdot \lambda - (y_q - y_p)) = 0 \\ - & \\ - & \begin{aligned} - \text{At least one of } &x_q - x_p = 0 \\ - \text{or } &(x_q - x_p) \cdot \lambda - (y_q - y_p) = 0 \\ - \end{aligned} \\ - & \text{must be satisfied for the constraint to be satisfied.} \\ - & \\ - & \text{If } x_q - x_p \neq 0, \text{ then } (x_q - x_p) \cdot \lambda - (y_q - y_p) = 0, \text{ and} \\ - & \text{by rearranging both sides we get } \lambda = (y_q - y_p) / (x_q - x_p). \\ - & \\ - & \text{Therefore:} \\ - & \hspace{2em} x_q \neq x_p \implies \lambda = (y_q - y_p) / (x_q - x_p).\\ - & \\ -2. & (1 - (x_q - x_p) \cdot \alpha) \cdot (2y_p \cdot \lambda - 3x_p^2) = 0 \\ - & \\ - & \begin{aligned} - \text{At least one of } &(1 - (x_q - x_p) \cdot \alpha) = 0 \\ - \text{or } &(2y_p \cdot \lambda - 3x_p^2) = 0 - \end{aligned} \\ - & \text{must be satisfied for the constraint to be satisfied.} \\ - & \\ - & \text{If } x_q = x_p, \text{ then } 1 - (x_q - x_p) \cdot \alpha = 0 \text{ has no solution for } \alpha, \\ - & \text{so it must be that } 2y_p \cdot \lambda - 3x_p^2 = 0. \\ - & \\ - & \text{If } x_q = x_p \text{ and } y_p = 0 \text{ then } x_p = 0, \text{ and the constraint is satisfied.}\\ - & \\ - & \text{If } x_q = x_p \text{ and } y_p \neq 0 \text{ then by rearranging both sides} \\ - & \text{we get } \lambda = 3x_p^2 / 2y_p. \\ - & \\ - & \text{Therefore:} \\ - & \hspace{2em} (x_q = x_p) \wedge y_p \neq 0 \implies \lambda = 3x_p^2 / 2y_p. \\ - & \\ -3.\text{ a)} & x_p \cdot x_q \cdot (x_q - x_p) \cdot (\lambda^2 - x_p - x_q - x_r) = 0 \\ - \text{ b)} & x_p \cdot x_q \cdot (x_q - x_p) \cdot (\lambda \cdot (x_p - x_r) - y_p - y_r) = 0 \\ - \text{ c)} & x_p \cdot x_q \cdot (y_q + y_p) \cdot (\lambda^2 - x_p - x_q - x_r) = 0 \\ - \text{ d)} & x_p \cdot x_q \cdot (y_q + y_p) \cdot (\lambda \cdot (x_p - x_r) - y_p - y_r) = 0 \\ - & \\ - & \begin{aligned} - \text{At least one of } &x_p = 0 \\ - \text{or } &x_p = 0 \\ - \text{or } &(x_q - x_p) = 0 \\ - \text{or } &(\lambda^2 - x_p - x_q - x_r) = 0 \\ - \end{aligned} \\ - & \text{must be satisfied for constraint (a) to be satisfied.} \\ - & \\ - & \text{If } x_p \neq 0 \wedge x_q \neq 0 \wedge x_q \neq x_p, \\[1.5ex] - & \text{• Constraint (a) imposes that } x_r = \lambda^2 - x_p - x_q. \\ - & \text{• Constraint (b) imposes that } y_r = \lambda \cdot (x_p - x_r) - y_p. \\ - & \\ - & \text{If } x_p \neq 0 \wedge x_q \neq 0 \wedge y_q \neq -y_p, \\[1.5ex] - & \text{• Constraint (c) imposes that } x_r = \lambda^2 - x_p - x_q. \\ - & \text{• Constraint (d) imposes that } y_r = \lambda \cdot (x_p - x_r) - y_p. \\ - & \\ - & \text{Therefore:} \\ - & \begin{aligned} - &(x_p \neq 0) \wedge (x_q \neq 0) \wedge ((x_q \neq x_p) \vee (y_q \neq -y_p)) \\ - \implies &(x_r = \lambda^2 - x_p - x_q) \wedge (y_r = \lambda \cdot (x_p - x_r) - y_p). - \end{aligned} \\ - & \\ -4.\text{ a)} & (1 - x_p \cdot \beta) \cdot (x_r - x_q) = 0 \\ - \text{ b)} & (1 - x_p \cdot \beta) \cdot (y_r - y_q) = 0 \\ - & \\ - & \begin{aligned} - \text{At least one of } 1 - x_p \cdot \beta &= 0 \\ - \text{or } x_r - x_q &= 0 - \end{aligned} \\ - & \text{must be satisfied for constraint (a) to be satisfied.} \\ - & \\ - & \text{If } x_p = 0 \text{ then } 1 - x_p \cdot \beta = 0 \text{ has no solutions for } \beta, \\ - & \text{and so it must be that } x_r - x_q = 0. \\ - & \\ - & \text{Similarly, constraint (b) imposes that if } x_p = 0 \\ - & \text{then } y_r - y_q = 0. \\ - & \\ - & \text{Therefore:} \\ - & \hspace{2em} x_p = 0 \implies (x_r, y_r) = (x_q, y_q). \\ - & \\ -5.\text{ a)} & (1 - x_q \cdot \gamma) \cdot (x_r - x_p) = 0 \\ - \text{ b)} & (1 - x_q \cdot \gamma) \cdot (y_r - y_p) = 0 \\ - & \\ - & \begin{aligned} - \text{At least one of } 1 - x_q \cdot \gamma &= 0 \\ - \text{or } x_r - x_p &= 0 - \end{aligned} \\ - & \text{must be satisfied for constraint (a) to be satisfied.} \\ - & \\ - & \text{If } x_q = 0 \text{ then } 1 - x_q \cdot \gamma = 0 \text{ has no solutions for } \gamma, \\ - & \text{and so it must be that } x_r - x_p = 0. \\ - & \\ - & \text{Similarly, constraint (b) imposes that if } x_q = 0 \\ - & \text{then } y_r - y_p = 0. \\ - & \\ - & \text{Therefore:} \\ - & \hspace{2em} x_q = 0 \implies (x_r, y_r) = (x_p, y_p). \\ - & \\ -6.\text{ a)} & (1 - (x_q - x_p) \cdot \alpha - (y_q + y_p) \cdot \delta) \cdot x_r = 0 \\ - \text{ b)} & (1 - (x_q - x_p) \cdot \alpha - (y_q + y_p) \cdot \delta) \cdot y_r = 0 \\ - & \\ - & \begin{aligned} - \text{At least one of } &1 - (x_q - x_p) \cdot \alpha - (y_q + y_p) \cdot \delta = 0 \\ - \text{or } &x_r = 0 - \end{aligned} \\ - & \text{must be satisfied for constraint (a) to be satisfied,} \\ - & \text{and similarly replacing } x_r \text{ by } y_r. \\ - & \\ - & \text{If } x_r \neq 0 \text{ or } y_r \neq 0, \text{ then it must be that } 1 - (x_q - x_p) \cdot \alpha - (y_q + y_p) \cdot \delta = 0. \\ - & \\ - & \text{However, if } x_q = x_p \wedge y_q = -y_p, \text{ then there are no solutions for } \alpha \text { and } \delta. \\ - & \\ - & \text{Therefore: } \\ - & \hspace{2em} x_q = x_p \wedge y_q = -y_p \implies (x_r, y_r) = (0, 0). -\end{array} -$$ - -#### Propositions: - -$ -\begin{array}{cl} -(1)& x_q \neq x_p \implies \lambda = (y_q - y_p) / (x_q - x_p) \\[0.8ex] -(2)& (x_q = x_p) \wedge y_p \neq 0 \implies \lambda = 3x_p^2 / 2y_p \\[0.8ex] -(3)& (x_p \neq 0) \wedge (x_q \neq 0) \wedge ((x_q \neq x_p) \vee (y_q \neq -y_p)) \\[0.4ex] - &\implies (x_r = \lambda^2 - x_p - x_q) \wedge (y_r = \lambda \cdot (x_p - x_r) - y_p) \\[0.8ex] -(4)& x_p = 0 \implies (x_r, y_r) = (x_q, y_q) \\[0.8ex] -(5)& x_q = 0 \implies (x_r, y_r) = (x_p, y_p) \\[0.8ex] -(6)& x_q = x_p \wedge y_q = -y_p \implies (x_r, y_r) = (0, 0) -\end{array} -$ - -#### Cases: - -$(x_p, y_p) + (x_q, y_q) = (x_r, y_r)$ - -Note that we rely on the fact that $0$ is not a valid $x$-coordinate or $y$-coordinate of a -point on the Pallas curve other than $\mathcal{O}$. - -* $(0, 0) + (0, 0)$ - - Completeness: - - $ - \begin{array}{cl} - (1)&\text{holds because } x_q = x_p \\ - (2)&\text{holds because } y_p = 0 \\ - (3)&\text{holds because } x_p = 0 \\ - (4)&\text{holds because } (x_r, y_r) = (x_q, y_q) = (0, 0) \\ - (5)&\text{holds because } (x_r, y_r) = (x_p, y_p) = (0, 0) \\ - (6)&\text{holds because } (x_r, y_r) = (0, 0). \\ - \end{array} - $ - - - Soundness: $(x_r, y_r) = (0, 0)$ is the only solution to $(6).$ - -* $(x, y) + (0, 0)$ for $(x, y) \neq (0, 0)$ - - Completeness: - - $ - \begin{array}{cl} - (1)&\text{holds because } x_q \neq x_p, \text{ therefore } \lambda = (y_q - y_p) / (x_q - x_p) \text{ is a solution} \\ - (2)&\text{holds because } x_q \neq x_p, \text{ therefore } \alpha = (x_q - x_p)^{-1} \text{ is a solution} \\ - (3)&\text{holds because } x_q = 0 \\ - (4)&\text{holds because } x_p \neq 0, \text{ therefore } \beta = x_p^{-1} \text{ is a solution} \\ - (5)&\text{holds because } (x_r, y_r) = (x_p, y_p) \\ - (6)&\text{holds because } x_q \neq x_p, \text{ therefore } \alpha = (x_q - x_p)^{-1} \text{ and } \delta = 0 \text{ is a solution.} - \end{array} - $ - - - Soundness: $(x_r, y_r) = (x_p, y_p)$ is the only solution to $(5).$ - -* $(0, 0) + (x, y)$ for $(x, y) \neq (0, 0)$ - - Completeness: - - $ - \begin{array}{cl} - (1)&\text{holds because } x_q \neq x_p, \text{ therefore } \lambda = (y_q - y_p) / (x_q - x_p) \text{ is a solution} \\ - (2)&\text{holds because } x_q \neq x_p, \text{ therefore } \alpha = (x_q - x_p)^{-1} \text{ is a solution} \\ - (3)&\text{holds because } x_p = 0 \\ - (4)&\text{holds because } x_p = 0 \text{ only when } (x_r, y_r) = (x_q, y_q) \\ - (5)&\text{holds because } x_q \neq 0, \text{ therefore } \gamma = x_q^{-1} \text{ is a solution}\\ - (6)&\text{holds because } x_q \neq x_p, \text{ therefore } \alpha = (x_q - x_p)^{-1} \text{ and } \delta = 0 \text{ is a solution.} - \end{array} - $ - - - Soundness: $(x_r, y_r) = (x_q, y_q)$ is the only solution to $(4).$ - -* $(x, y) + (x, y)$ for $(x, y) \neq (0, 0)$ - - Completeness: - - $ - \begin{array}{cl} - (1)&\text{holds because } x_q = x_p \\ - (2)&\text{holds because } x_q = x_p \wedge y_p \neq 0, \text{ therefore } \lambda = 3x_p^2 / 2y_p \text{ is a solution}\\ - (3)&\text{holds because } x_r = \lambda^2 - x_p - x_q \wedge y_r = \lambda \cdot (x_p - x_r) - y_p \text{ in this case} \\ - (4)&\text{holds because } x_p \neq 0, \text{ therefore } \beta = x_p^{-1} \text{ is a solution} \\ - (5)&\text{holds because } x_p \neq 0, \text{ therefore } \gamma = x_q^{-1} \text{ is a solution} \\ - (6)&\text{holds because } x_q = x_p \text{ and } y_q \neq -y_p, \text{ therefore } \alpha = 0 \text{ and } \delta = (y_q + y_p)^{-1} \text{ is a solution.} \\ - \end{array} - $ - - - Soundness: $\lambda$ is computed correctly, and $(x_r, y_r) = (\lambda^2 - x_p - x_q, \lambda \cdot (x_p - x_r) - y_p)$ is the only solution. - -* $(x, y) + (x, -y)$ for $(x, y) \neq (0, 0)$ - - Completeness: - - $ - \begin{array}{cl} - (1)&\text{holds because } x_q = x_p \\ - (2)&\text{holds because } x_q = x_p \wedge y_p \neq 0, \text{ therefore } \lambda = 3x_p^2 / 2y_p \text{ is a solution} \\ - &\text{(although } \lambda \text{ is not used in this case)} \\ - (3)&\text{holds because } x_q = x_p \text{ and } y_q = -y_p \\ - (4)&\text{holds because } x_p \neq 0, \text{ therefore } \beta = x_p^{-1} \text{ is a solution} \\ - (5)&\text{holds because } x_q \neq 0, \text{ therefore } \gamma = x_q^{-1} \text{ is a solution} \\ - (6)&\text{holds because } (x_r, y_r) = (0, 0) \\ - \end{array} - $ - - - Soundness: $(x_r, y_r) = (0, 0)$ is the only solution to $(6).$ - -* $(x_p, y_p) + (x_q, y_q)$ for $(x_p, y_p) \neq (0,0)$ and $(x_q, y_q) \neq (0, 0)$ and $x_p \neq x_q$ - - Completeness: - - $ - \begin{array}{cl} - (1)&\text{holds because } x_q \neq x_p, \text{ therefore } \lambda = (y_q - y_p) / (x_q - x_p) \text{ is a solution} \\ - (2)&\text{holds because } x_q \neq x_p, \text{ therefore } \alpha = (x_q - x_p)^{-1} \text{ is a solution} \\ - (3)&\text{holds because } x_r = \lambda^2 - x_p - x_q \wedge y_r = \lambda \cdot (x_p - x_r) - y_p \text{ in this case} \\ - (4)&\text{holds because } x_p \neq 0, \text{ therefore } \beta = x_p^{-1} \text{ is a solution} \\ - (5)&\text{holds because } x_q \neq 0, \text{ therefore } \gamma = x_q^{-1} \text{ is a solution} \\ - (6)&\text{holds because } x_q \neq x_p, \text{ therefore } \alpha = (x_q - x_p)^{-1} \text{ and } \delta = 0 \text{ is a solution.} - \end{array} - $ - - - Soundness: $\lambda$ is computed correctly, and $(x_r, y_r) = (\lambda^2 - x_p - x_q, \lambda \cdot (x_p - x_r) - y_p)$ is the only solution. diff --git a/book/src/design/gadgets/ecc/fixed-base-scalar-mul.md b/book/src/design/gadgets/ecc/fixed-base-scalar-mul.md deleted file mode 100644 index dbeece496d..0000000000 --- a/book/src/design/gadgets/ecc/fixed-base-scalar-mul.md +++ /dev/null @@ -1,189 +0,0 @@ -# Fixed-base scalar multiplication - -There are $6$ fixed bases in the Orchard protocol: -- $\mathcal{K}^{\mathsf{Orchard}}$, used in deriving the nullifier; -- $\mathcal{G}^{\mathsf{Orchard}}$, used in spend authorization; -- $\mathcal{R}$ base for $\mathsf{NoteCommit}^{\mathsf{Orchard}}$; -- $\mathcal{V}$ and $\mathcal{R}$ bases for $\mathsf{ValueCommit}^{\mathsf{Orchard}}$; and -- $\mathcal{R}$ base for $\mathsf{Commit}^{\mathsf{ivk}}$. -## Decompose scalar -We support fixed-base scalar multiplication with three types of scalars: -### Full-width scalar -A $255$-bit scalar from $\mathbb{F}_q$. We decompose a full-width scalar $\alpha$ into $85$ $3$-bit windows: - -$$\alpha = k_0 + k_1 \cdot (2^3)^1 + \cdots + k_{84} \cdot (2^3)^{84}, k_i \in [0..2^3).$$ - -The scalar multiplication will be computed correctly for $k_{0..84}$ representing any -integer in the range $[0, 2^{255})$ - that is, the scalar is allowed to be non-canonical. - -We range-constrain each $3$-bit word of the scalar decomposition using a polynomial range-check constraint: -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline -9 & q_\texttt{mul\_fixed\_full} \cdot \RangeCheck{\text{word}}{2^3} = 0 \\\hline -\end{array} -$$ -where $\RangeCheck{\text{word}}{\texttt{range}} = \text{word} \cdot (1 - \text{word}) \cdots (\texttt{range} - 1 - \text{word}).$ - -### Base field element -We support using a base field element as the scalar in fixed-base multiplication. This occurs, for example, in the scalar multiplication for the nullifier computation of the Action circuit $\mathsf{DeriveNullifier_{nk}} = \mathsf{Extract}_\mathbb{P}\left(\left[(\mathsf{PRF_{nk}^{nfOrchard}}(\rho) + \psi) \bmod{q_\mathbb{P}}\right]\mathcal{K}^\mathsf{Orchard} + \mathsf{cm}\right)$: here, the scalar $$\left[(\mathsf{PRF_{nk}^{nfOrchard}}(\rho) + \psi) \bmod{q_\mathbb{P}}\right]$$ is the result of a base field addition. - -Decompose the base field element $\alpha$ into three-bit windows, and range-constrain each window, using the [short range decomposition](../decomposition.md#short-range-decomposition) gadget in strict mode, with $W = 85, K = 3.$ - -If $k_{0..84}$ is witnessed directly then no issue of canonicity arises. However, because the scalar is given as a base field element here, care must be taken to ensure a canonical representation, since $2^{255} > p$. That is, we must check that $0 \leq \alpha < p,$ where $p$ the is Pallas base field modulus $$p = 2^{254} + t_p = 2^{254} + 45560315531419706090280762371685220353.$$ Note that $t_p < 2^{130}.$ - -To do this, we decompose $\alpha$ into three pieces: $$\alpha = \alpha_0 \text{ (252 bits) } \,||\, \alpha_1 \text{ (2 bits) } \,||\, \alpha_2 \text{ (1 bit) }.$$ - -We check the correctness of this decomposition by: -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline -5 & q_\text{canon-base-field} \cdot \RangeCheck{\alpha_1}{2^2} = 0 \\\hline -3 & q_\text{canon-base-field} \cdot \BoolCheck{\alpha_2} = 0 \\\hline -2 & q_\text{canon-base-field} \cdot \left(z_{84} - (\alpha_1 + \alpha_2 \cdot 2^2)\right) = 0 \\\hline -\end{array} -$$ -If the MSB $\alpha_2 = 0$ is not set, then $\alpha < 2^{254} < p.$ However, in the case where $\alpha_2 = 1$, we must check: -- $\alpha_2 = 1 \implies \alpha_1 = 0;$ -- $\alpha_2 = 1 \implies \alpha_0 < t_p$: - - $\alpha_2 = 1 \implies 0 \leq \alpha_0 < 2^{130}$, - - $\alpha_2 = 1 \implies 0 \leq \alpha_0 + 2^{130} - t_p < 2^{130}$ - -To check that $0 \leq \alpha_0 < 2^{130},$ we make use of the three-bit running sum decomposition: -- Firstly, we constrain $\alpha_0$ to be a $132$-bit value by enforcing its high $120$ bits to be all-zero. We can get $\textsf{alpha\_0\_hi\_120}$ from the decomposition: -$$ -\begin{aligned} -z_{44} &= k_{44} + 2^3 k_{45} + \cdots + 2^{3 \cdot (84 - 44)} k_{84}\\ -\implies \textsf{alpha\_0\_hi\_120} &= z_{44} - 2^{3 \cdot (84 - 44)} k_{84}\\ -&= z_{44} - 2^{3 \cdot (40)} z_{84}. -\end{aligned} -$$ -- Then, we constrain bits $130..\!\!=\!\!131$ of $\alpha_0$ to be zeroes; in other words, we constrain the three-bit word $k_{43} = \alpha[129..\!\!=\!\!131] = \alpha_0[129..\!\!=\!\!131] \in \{0, 1\}.$ We make use of the running sum decomposition to obtain $k_{43} = z_{43} - z_{44} \cdot 2^3.$ - -Define $\alpha'_0 = \alpha_0 + 2^{130} - t_p$. To check that $0 \leq \alpha'_0 < 2^{130},$ we use 13 ten-bit [lookups](../decomposition.md#lookup-decomposition), where we constrain the $z_{13}$ running sum output of the lookup to be $0$ if $\alpha_2 = 1.$ -$$ -\begin{array}{|c|l|l|} -\hline -\text{Degree} & \text{Constraint} & \text{Comment} \\\hline -2 & q_\text{canon-base-field} \cdot (\alpha_0' - (\alpha_0 + 2^{130} - t_\mathbb{P})) = 0 \\\hline -3 & q_\text{canon-base-field} \cdot \alpha_2 \cdot \alpha_1 = 0 & \alpha_2 = 1 \implies \alpha_1 = 0 \\\hline -3 & q_\text{canon-base-field} \cdot \alpha_2 \cdot \textsf{alpha\_0\_hi\_120} = 0 & \text{Constrain $\alpha_0$ to be a $132$-bit value} \\\hline -4 & q_\text{canon-base-field} \cdot \alpha_2 \cdot \BoolCheck{k_{43}} = 0 & \text{Constrain $\alpha_0[130..\!\!=\!\!131]$ to $0$} \\\hline -3 & q_\text{canon-base-field} \cdot \alpha_2 \cdot z_{13}(\texttt{lookup}(\alpha_0', 13)) = 0 & \alpha_2 = 1 \implies 0 \leq \alpha'_0 < 2^{130}\\\hline -\end{array} -$$ -### Short signed scalar -A short signed scalar is witnessed as a magnitude $m$ and sign $s$ such that -$$ -s \in \{-1, 1\} \\ -m \in [0, 2^{64}) \\ -\mathsf{v^{old}} - \mathsf{v^{new}} = s \cdot m. -$$ - -This is used for $\mathsf{ValueCommit^{Orchard}}$. We want to compute $\mathsf{ValueCommit^{Orchard}_{rcv}}(\mathsf{v^{old}} - \mathsf{v^{new}}) = [\mathsf{v^{old}} - \mathsf{v^{new}}] \mathcal{V} + [\mathsf{rcv}] \mathcal{R}$, where -$$ --(2^{64}-1) \leq \mathsf{v^{old}} - \mathsf{v^{new}} \leq 2^{64}-1 -$$ - -$\mathsf{v^{old}}$ and $\mathsf{v^{new}}$ are each already constrained to $64$ bits (by their use as inputs to $\mathsf{NoteCommit^{Orchard}}$). - -Decompose the magnitude $m$ into three-bit windows, and range-constrain each window, using the [short range decomposition](../decomposition.md#short-range-decomposition) gadget in strict mode, with $W = 22, K = 3.$ - - We have two additional constraints: -$$ -\begin{array}{|c|l|l|} -\hline -\text{Degree} & \text{Constraint} & \text{Comment} \\\hline -3 & q_\texttt{mul\_fixed\_short} \cdot \BoolCheck{k_{21}} = 0 & \text{The last window must be a single bit.}\\\hline -3 & q_\texttt{mul\_fixed\_short} \cdot \left(s^2 - 1\right) = 0 &\text{The sign must be $1$ or $-1$.}\\\hline -\end{array} -$$ -where $\BoolCheck{x} = x \cdot (1 - x)$. - -## Load fixed base -Then, we precompute multiples of the fixed base $B$ for each window. This takes the form of a window table: $M[0..W)[0..8)$ such that: - -- for the first (W-1) rows $M[0..(W-1))[0..8)$: $$M[w][k] = [(k+2) \cdot (2^3)^w]B$$ -- in the last row $M[W-1][0..8)$: $$M[w][k] = [k \cdot (2^3)^w - \sum\limits_{j=0}^{83} 2^{3j+1}]B$$ - -The additional $(k + 2)$ term lets us avoid adding the point at infinity in the case $k = 0$. We offset these accumulated terms by subtracting them in the final window, i.e. we subtract $\sum\limits_{j=0}^{W-2} 2^{3j+1}$. - -> Note: Although an offset of $(k + 1)$ would naively suffice, it introduces an edge case when $k_0 = 7, k_1= 0$. -> In this case, the window table entries evaluate to the same point: -> * $M[0][k_0] = [(7+1)*(2^3)^0]B = [8]B,$ -> * $M[1][k_1] = [(0+1)*(2^3)^1]B = [8]B.$ -> -> In fixed-base scalar multiplication, we sum the multiples of $B$ at each window (except the last) using incomplete addition. -> Since the point doubling case is not handled by incomplete addition, we avoid it by using an offset of $(k+2).$ - -For each window of fixed-base multiples $M[w] = (M[w][0], \cdots, M[w][7]), w \in [0..(W-1))$: -- Define a Lagrange interpolation polynomial $\mathcal{L}_x(k)$ that maps $k \in [0..8)$ to the $x$-coordinate of the multiple $M[w][k]$, i.e. - $$ - \mathcal{L}_x(k) = \begin{cases} - ([(k + 2) \cdot (2^3)^w] B)_x &\text{for } w \in [0..(W-1)); \\ - ([k \cdot (2^3)^w - \sum\limits_{j=0}^{83} 2^{3j+1}] B)_x &\text{for } w = 84; \text{ and} - \end{cases} - $$ -- Find a value $z_w$ such that $z_w + (M[w][k])_y$ is a square $u^2$ in the field, but the wrong-sign $y$-coordinate $z_w - (M[w][k])_y$ does not produce a square. - -Repeating this for all $W$ windows, we end up with: -- an $W \times 8$ table $\mathcal{L}_x$ storing $8$ coefficients interpolating the $x-$coordinate for each window. Each $x$-coordinate interpolation polynomial will be of the form -$$\mathcal{L}_x[w](k) = c_0 + c_1 \cdot k + c_2 \cdot k^2 + \cdots + c_7 \cdot k^7,$$ -where $k \in [0..8), w \in [0..85)$ and $c_k$'s are the coefficients for each power of $k$; and -- a length-$W$ array $Z$ of $z_w$'s. - -We load these precomputed values into fixed columns whenever we do fixed-base scalar multiplication in the circuit. - -## Fixed-base scalar multiplication -Given a decomposed scalar $\alpha$ and a fixed base $B$, we compute $[\alpha]B$ as follows: - -1. For each $k_w, w \in [0..85), k_w \in [0..8)$ in the scalar decomposition, witness the $x$- and $y$-coordinates $(x_w,y_w) = M[w][k_w].$ -2. Check that $(x_w, y_w)$ is on the curve: $y_w^2 = x_w^3 + b$. -3. Witness $u_w$ such that $y_w + z_w = u_w^2$. -4. For all windows but the last, use [incomplete addition](./incomplete-add.md) to sum the $M[w][k_w]$'s, resulting in $[\alpha - k_{84} \cdot (2^3)^{84} + \sum\limits_{j=0}^{83} 2^{3j+1}]B$. -5. For the last window, use complete addition $M[83][k_{83}] + M[84][k_{84}]$ and return the final result. - -> Note: complete addition is required in the final step to correctly map $[0]B$ to a representation of the point at infinity, $(0,0)$; and also to handle a corner case for which the last step is a doubling. - - Constraints: -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline -8 & q_\text{mul-fixed} \cdot \left( \mathcal{L}_x[w](k_w) - x_w \right) = 0 \\\hline -4 & q_\text{mul-fixed} \cdot \left( y_w^2 - x_w^3 - b \right) = 0 \\\hline -3 & q_\text{mul-fixed} \cdot \left( u_w^2 - y_w - Z[w] \right) = 0 \\\hline -\end{array} -$$ - -where $b = 5$ (from the Pallas curve equation). - -### Signed short exponent -Recall that the signed short exponent is witnessed as a $64-$bit magnitude $m$, and a sign $s \in {1, -1}.$ Using the above algorithm, we compute $P = [m] \mathcal{B}$. Then, to get the final result $P',$ we conditionally negate $P$ using $(x, y) \mapsto (x, s \cdot y)$. - - Constraints: -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline -3 & q_\texttt{mul\_fixed\_short} \cdot \left(P'_y - P_y\right) \cdot \left(P'_y + P_y\right) = 0 \\\hline -3 & q_\texttt{mul\_fixed\_short} \cdot \left(s \cdot P'_y - P_y\right) = 0 \\\hline -\end{array} -$$ - -## Layout - -$$ -\begin{array}{|c|c|c|c|c|c|c|c|} -\hline - x_P & y_P & x_{QR} & y_{QR} & u & \text{window} & L_{0..=7} & \textsf{fixed\_z} \\\hline -x_{P,0} & y_{P,0} & & & u_0 & \text{window}_0 & L_{0..=7,0} & \textsf{fixed\_z}_0 \\\hline -x_{P,1} & y_{P,1} & x_{Q,1} = x_{P,0} & y_{Q,1} = y_{P,0} & u_1 & \text{window}_1 & L_{0..=7,1} & \textsf{fixed\_z}_1 \\\hline -x_{P,2} & y_{P,2} & x_{Q,2} = x_{R,1} & y_{Q,2} = y_{R,1} & u_2 & \text{window}_2 & L_{0..=7,1} & \textsf{fixed\_z}_2 \\\hline -\vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots \\\hline -\end{array} -$$ - -Note: this doesn't include the last row that uses [complete addition](./addition.md#Complete-addition). In the implementation this is allocated in a different region. diff --git a/book/src/design/gadgets/ecc/var-base-scalar-mul.md b/book/src/design/gadgets/ecc/var-base-scalar-mul.md deleted file mode 100644 index 84ef5ab3dc..0000000000 --- a/book/src/design/gadgets/ecc/var-base-scalar-mul.md +++ /dev/null @@ -1,394 +0,0 @@ -# Variable-base scalar multiplication - -In the Orchard circuit we need to check $\mathsf{pk_d} = [\mathsf{ivk}] \mathsf{g_d}$ where $\mathsf{ivk} \in [0, p)$ and the scalar field is $\mathbb{F}_q$ with $p < q$. - -We have $p = 2^{254} + t_p$ and $q = 2^{254} + t_q$, for $t_p, t_q < 2^{128}$. - -## Witness scalar -We're trying to compute $[\alpha] T$ for $\alpha \in [0, q)$. Set $k = \alpha + t_q$ and $n = 254$. Then we can compute - -$\begin{array}{cl} -[2^{254} + (\alpha + t_q)] T &= [2^{254} + (\alpha + t_q) - (2^{254} + t_q)] T \\ - &= [\alpha] T -\end{array}$ - -provided that $\alpha + t_q \in [0, 2^{n+1})$, i.e. $\alpha < 2^{n+1} - t_q$ which covers the whole range we need because in fact $2^{255} - t_q > q$. - -Thus, given a scalar $\alpha$, we witness the boolean decomposition of $k = \alpha + t_q.$ (We use big-endian bit order for convenient input into the variable-base scalar multiplication algorithm.) - -$$k = k_{254} \cdot 2^{254} + k_{253} \cdot 2^{253} + \cdots + k_0.$$ - -## Variable-base scalar multiplication -We use an optimized double-and-add algorithm, copied from ["Faster variable-base scalar multiplication in zk-SNARK circuits"](https://github.com/zcash/zcash/issues/3924) with some variable name changes: - -```ignore -Acc := [2] T -for i from n-1 down to 0 { - P := k_{i+1} ? T : −T - Acc := (Acc + P) + Acc -} -return (k_0 = 0) ? (Acc - T) : Acc -``` - -It remains to check that the x-coordinates of each pair of points to be added are distinct. - -When adding points in a prime-order group, we can rely on Theorem 3 from Appendix C of the [Halo paper](https://eprint.iacr.org/2019/1021.pdf), which says that if we have two such points with nonzero indices wrt a given odd-prime order base, where the indices taken in the range $-(q-1)/2..(q-1)/2$ are distinct disregarding sign, then they have different x-coordinates. This is helpful, because it is easier to reason about the indices of points occurring in the scalar multiplication algorithm than it is to reason about their x-coordinates directly. - -So, the required check is equivalent to saying that the following "indexed version" of the above algorithm never asserts: - -```ignore -acc := 2 -for i from n-1 down to 0 { - p = k_{i+1} ? 1 : −1 - assert acc ≠ ± p - assert (acc + p) ≠ acc // X - acc := (acc + p) + acc - assert 0 < acc ≤ (q-1)/2 -} -if k_0 = 0 { - assert acc ≠ 1 - acc := acc - 1 -} -``` - -The maximum value of `acc` is: -```ignore - <--- n 1s ---> - 1011111...111111 -= 1100000...000000 - 1 -``` -= $2^{n+1} + 2^n - 1$ - -> The assertion labelled X obviously cannot fail because $p \neq 0$. It is possible to see that acc is monotonically increasing except in the last conditional. It reaches its largest value when $k$ is maximal, i.e. $2^{n+1} + 2^n - 1$. - -So to entirely avoid exceptional cases, we would need $2^{n+1} + 2^n - 1 < (q-1)/2$. But we can use $n$ larger by $c$ if the last $c$ iterations use [complete addition](./addition.md#Complete-addition). - -The first $i$ for which the algorithm using **only** incomplete addition fails is going to be $252$, since $2^{252+1} + 2^{252} - 1 > (q - 1)/2$. We need $n = 254$ to make the wraparound technique above work. - -```python -sage: q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 -sage: 2^253 + 2^252 - 1 < (q-1)//2 -False -sage: 2^252 + 2^251 - 1 < (q-1)//2 -True -``` - -So the last three iterations of the loop ($i = 2..0$) need to use [complete addition](./addition.md#Complete-addition), as does the conditional subtraction at the end. Writing this out using ⸭ for incomplete addition (as we do in the spec), we have: - -```ignore -Acc := [2] T -for i from 253 down to 3 { - P := k_{i+1} ? T : −T - Acc := (Acc ⸭ P) ⸭ Acc -} -for i from 2 down to 0 { - P := k_{i+1} ? T : −T - Acc := (Acc + P) + Acc // complete addition -} -return (k_0 = 0) ? (Acc + (-T)) : Acc // complete addition -``` - -## Constraint program for optimized double-and-add -Define a running sum $\mathbf{z_j} = \sum_{i=j}^{n} (\mathbf{k}_{i} \cdot 2^{i-j})$, where $n = 254$ and: - -$$ -\begin{aligned} - &\mathbf{z}_{n+1} = 0,\\ - &\mathbf{z}_{n} = \mathbf{k}_{n}, \hspace{2em}\text{(most significant bit)}\\ - &\mathbf{z}_0 = k.\\ -\end{aligned} -$$ - -$\begin{array}{l} -\text{Initialize } A_{254} = [2] T. \\ -\\ -\text{for } i \text{ from } 254 \text{ down to } 4: \\ -\hspace{1.5em} \BoolCheck{\mathbf{k}_i} = 0 \\ -\hspace{1.5em} \mathbf{z}_{i} = 2\mathbf{z}_{i+1} + \mathbf{k}_{i} \\ -\hspace{1.5em} x_{P,i} = x_T \\ -\hspace{1.5em} y_{P,i} = (2 \mathbf{k}_i - 1) \cdot y_T \hspace{2em}\text{(conditionally negate)} \\ -\hspace{1.5em} \lambda_{1,i} \cdot (x_{A,i} - x_{P,i}) = y_{A,i} - y_{P,i} \\ -\hspace{1.5em} x_{R,i} = \lambda_{1,i}^2 - x_{A,i} - x_{P,i} \\ -\hspace{1.5em} (\lambda_{1,i} + \lambda_{2,i}) \cdot (x_{A,i} - x_{R,i}) = 2 y_{\mathsf{A},i} \\ -\hspace{1.5em} \lambda_{2,i}^2 = x_{A,i-1} + x_{R,i} + x_{A,i} \\ -\hspace{1.5em} \lambda_{2,i} \cdot (x_{A,i} - x_{A,i-1}) = y_{A,i} + y_{A,i-1}. \\ -\end{array}$ - -The helper $\BoolCheck{x} = x \cdot (1 - x)$. -After substitution of $x_{P,i}, y_{P,i}, x_{R,i}, y_{A,i}$, and $y_{A,i-1}$, this becomes: - -$\begin{array}{l} -\text{Initialize } A_{254} = [2] T. \\ -\\ -\text{for } i \text{ from } 254 \text{ down to } 4: \\ -\hspace{1.5em} \text{// let } \mathbf{k}_{i} = \mathbf{z}_{i} - 2\mathbf{z}_{i+1} \\ -\hspace{1.5em} \text{// let } y_{A,i} = \frac{(\lambda_{1,i} + \lambda_{2,i}) \cdot (x_{A,i} - (\lambda_{1,i}^2 - x_{A,i} - x_T))}{2} \\[2ex] -\hspace{1.5em} \BoolCheck{\mathbf{k}_i} = 0 \\ -\hspace{1.5em} \lambda_{1,i} \cdot (x_{A,i} - x_T) = y_{A,i} - (2 \mathbf{k}_i - 1) \cdot y_T \\ -\hspace{1.5em} \lambda_{2,i}^2 = x_{A,i-1} + \lambda_{1,i}^2 - x_T \\[1ex] -\hspace{1.5em} \begin{cases} - \lambda_{2,i} \cdot (x_{A,i} - x_{A,i-1}) = y_{A,i} + y_{A, i-1}, &\text{if } i > 4 \\[0.5ex] - \lambda_{2,4} \cdot (x_{A,4} - x_{A,3}) = y_{A,4} + y_{A,3}^\text{witnessed}, &\text{if } i = 4. - \end{cases} -\end{array}$ - -Here, $y_{A,3}^\text{witnessed}$ is assigned to a cell. This is unlike previous $y_{A,i}$'s, which were implicitly derived from $\lambda_{1,i}, \lambda_{2,i}, x_{A,i}, x_T$, but never actually assigned. - -The bits $\mathbf{k}_{3 \dots 1}$ are used in three further steps, using [complete addition](./addition.md#Complete-addition): - -$\begin{array}{l} -\text{for } i \text{ from } 3 \text{ down to } 1: \\ -\hspace{1.5em} \text{// let } \mathbf{k}_{i} = \mathbf{z}_{i} - 2\mathbf{z}_{i+1} \\[0.5ex] -\hspace{1.5em} \BoolCheck{\mathbf{k}_i} = 0 \\ -\hspace{1.5em} (x_{A,i-1}, y_{A,i-1}) = \left((x_{A,i}, y_{A,i}) + (x_T, y_T)\right) + (x_{A,i}, y_{A,i}) -\end{array}$ - -If the least significant bit $\mathbf{k_0} = 1,$ we set $B = \mathcal{O},$ otherwise we set ${B = -T}$. Then we return ${A + B}$ using complete addition. - -Let $B = \begin{cases} -(0, 0), &\text{ if } \mathbf{k_0} = 1, \\ -(x_T, -y_T), &\text{ otherwise.} -\end{cases}$ - -Output $(x_{A,0}, y_{A,0}) + B$. - -(Note that $(0, 0)$ represents $\mathcal{O}$.) - -## Incomplete addition - -We need six advice columns to witness $(x_T, y_T, \lambda_1, \lambda_2, x_{A,i}, \mathbf{z}_i)$. However, since $(x_T, y_T)$ are the same, we can perform two incomplete additions in a single row, reusing the same $(x_T, y_T)$. We split the scalar bits used in incomplete addition into $hi$ and $lo$ halves and process them in parallel. This means that we effectively have two for loops: -- the first, covering the $hi$ half for $i$ from $254$ down to $130$, with a special case at $i = 130$; and -- the second, covering the $lo$ half for the remaining $i$ from $129$ down to $4$, with a special case at $i = 4$. - -$$ -\begin{array}{|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|} -\hline - x_T & y_T & z^{hi} & x_A^{hi} & \lambda_1^{hi} & \lambda_2^{hi} & q_1^{hi} & q_2^{hi} & q_3^{hi} & z^{lo} & x_A^{lo} & \lambda_1^{lo} & \lambda_2^{lo} & q_1^{lo} & q_2^{lo} & q_3^{lo} \\\hline - & & \mathbf{z}_{255} = 0 & & y_{A,254}=2[T]_y & & 1 & 0 & 0 & \mathbf{z}_{130} & & y_{A,129} & & 1 & 0 & 0 \\\hline - x_T & y_T & \mathbf{z}_{254} & x_{A,254} = 2[T]_x & \lambda_{1,254} & \lambda_{2,254} & 0 & 1 & 0 & \mathbf{z}_{129} & x_{A,129} & \lambda_{1,129} & \lambda_{2,129} & 0 & 1 & 0 \\\hline - x_T & y_T & \mathbf{z}_{253} & x_{A,253} & \lambda_{1,253} & \lambda_{2,253} & 0 & 1 & 0 & \mathbf{z}_{128} & x_{A,128} & \lambda_{1,128} & \lambda_{2,128} & 0 & 1 & 0 \\\hline - \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots \\\hline - x_T & y_T & \mathbf{z}_{130} & x_{A,130} & \lambda_{1,130} & \lambda_{2,130} & 0 & 0 & 1 & \mathbf{z}_5 & x_{A,5} & \lambda_{1,5} & \lambda_{2,5} & 0 & 1 & 0 \\\hline - x_T & y_T & & x_{A,129} & y_{A,129} & & & & & \mathbf{z}_4 & x_{A,4} & \lambda_{1,4} & \lambda_{2,4} & 0 & 0 & 1 \\\hline - & & & & & & & & & & x_{A,3} & y_{A,3} & & & & \\\hline - -\end{array} -$$ - -For each $hi$ and $lo$ half, we have three sets of gates. Note that $i$ is going from $255..=3$; $i$ is NOT indexing the rows. - -### $q_1 = 1$ -This gate is only used on the first row (before the for loop). We check that $\lambda_1, \lambda_2$ are initialized to values consistent with the initial $y_A.$ -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline -4 & q_1 \cdot \left(y_{A,n}^\text{witnessed} - y_{A,n}\right) = 0 \\\hline -\end{array} -$$ -where -$$ -\begin{aligned} -y_{A,n} &= \frac{(\lambda_{1,n} + \lambda_{2,n}) \cdot (x_{A,n} - (\lambda_{1,n}^2 - x_{A,n} - x_T))}{2},\\ -y_{A,n}^\text{witnessed} &\text{ is witnessed.} -\end{aligned} -$$ - -### $q_2 = 1$ -This gate is used on all rows corresponding to the for loop except the last. - -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline -2 & q_2 \cdot \left(x_{T,cur} - x_{T,next}\right) = 0 \\\hline -2 & q_2 \cdot \left(y_{T,cur} - y_{T,next}\right) = 0 \\\hline -3 & q_2 \cdot \BoolCheck{\mathbf{k}_i} = 0, \text{ where } \mathbf{k}_i = \mathbf{z}_{i} - 2\mathbf{z}_{i+1} \\\hline -4 & q_2 \cdot \left(\lambda_{1,i} \cdot (x_{A,i} - x_{T,i}) - y_{A,i} + (2\mathbf{k}_i - 1) \cdot y_{T,i}\right) = 0 \\\hline -3 & q_2 \cdot \left(\lambda_{2,i}^2 - x_{A,i-1} - x_{R,i} - x_{A,i}\right) = 0 \\\hline -3 & q_2 \cdot \left(\lambda_{2,i} \cdot (x_{A,i} - x_{A,i-1}) - y_{A,i} - y_{A,i-1}\right) = 0 \\\hline -\end{array} -$$ -where -$$ -\begin{aligned} -x_{R,i} &= \lambda_{1,i}^2 - x_{A,i} - x_T, \\ -y_{A,i} &= \frac{(\lambda_{1,i} + \lambda_{2,i}) \cdot (x_{A,i} - (\lambda_{1,i}^2 - x_{A,i} - x_T))}{2}, \\ -y_{A,i-1} &= \frac{(\lambda_{1,i-1} + \lambda_{2,i-1}) \cdot (x_{A,i-1} - (\lambda_{1,i-1}^2 - x_{A,i-1} - x_T))}{2}, \\ -\end{aligned} -$$ - -### $q_3 = 1$ -This gate is used on the final iteration of the for loop, handling the special case where we check that the output $y_A$ has been witnessed correctly. -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline -3 & q_3 \cdot \BoolCheck{\mathbf{k}_i} = 0, \text{ where } \mathbf{k}_i = \mathbf{z}_{i} - 2\mathbf{z}_{i+1} \\\hline -4 & q_3 \cdot \left(\lambda_{1,i} \cdot (x_{A,i} - x_{T,i}) - y_{A,i} + (2\mathbf{k}_i - 1) \cdot y_{T,i}\right) = 0 \\\hline -3 & q_3 \cdot \left(\lambda_{2,i}^2 - x_{A,i-1} - x_{R,i} - x_{A,i}\right) = 0 \\\hline -3 & q_3 \cdot \left(\lambda_{2,i} \cdot (x_{A,i} - x_{A,i-1}) - y_{A,i} - y_{A,i-1}^\text{witnessed}\right) = 0 \\\hline -\end{array} -$$ -where -$$ -\begin{aligned} -x_{R,i} &= \lambda_{1,i}^2 - x_{A,i} - x_T, \\ -y_{A,i} &= \frac{(\lambda_{1,i} + \lambda_{2,i}) \cdot (x_{A,i} - (\lambda_{1,i}^2 - x_{A,i} - x_T))}{2},\\ -y_{A,i-1}^\text{witnessed} &\text{ is witnessed.} -\end{aligned} -$$ - -## Complete addition - -We reuse the [complete addition](addition.md#complete-addition) constraints to implement -the final $c$ rounds of double-and-add. This requires two rows per round because we need -9 advice columns for each complete addition. In the 10th advice column we stash the other -cells that we need to correctly implement the double-and-add: - -- The base $y$ coordinate, so we can conditionally negate it as input to one of the - complete additions. -- The running sum, which we constrain over two rows instead of sequentially. - -### Layout - -$$ -\begin{array}{|c|c|c|c|c|c|c|c|c|c|c|} -a_0 & a_1 & a_2 & a_3 & a_4 & a_5 & a_6 & a_7 & a_8 & a_9 & q_\texttt{mul\_decompose\_var} \\\hline -x_T & y_p & x_A & y_A & \lambda_1 & \alpha_1 & \beta_1 & \gamma_1 & \delta_1 & z_{i+1} & 0 \\\hline -x_A & y_A & x_q & y_q & \lambda_2 & \alpha_2 & \beta_2 & \gamma_2 & \delta_2 & y_T & 1 \\\hline - & & x_r & y_r & & & & & & z_i & 0 \\\hline -\end{array} -$$ - -### Constraints - -In addition to the complete addition constraints, we define the following gate: - -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline - & q_\texttt{mul\_decompose\_var} \cdot \BoolCheck{\mathbf{k}_i} = 0 \\\hline - & q_\texttt{mul\_decompose\_var} \cdot \Ternary{\mathbf{k}_i}{y_T - y_p}{y_T + y_p} = 0 \\\hline -\end{array} -$$ -where $\mathbf{k}_i = \mathbf{z}_{i} - 2\mathbf{z}_{i+1}$. - -## LSB - -### Layout - -$$ -\begin{array}{|c|c|c|c|c|c|c|c|c|c|c|} -a_0 & a_1 & a_2 & a_3 & a_4 & a_5 & a_6 & a_7 & a_8 & a_9 & q_\texttt{mul\_lsb} \\\hline -x_p & y_p & x_A & y_A & \lambda & \alpha & \beta & \gamma & \delta & z_1 & 1 \\\hline -x_T & y_T & x_r & y_r & & & & & & z_0 & 0 \\\hline -\end{array} -$$ - -### Constraints - -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline - & q_\texttt{mul\_lsb} \cdot \BoolCheck{\mathbf{k}_0} = 0 \\\hline - & q_\texttt{mul\_lsb} \cdot \Ternary{\mathbf{k}_0}{x_p}{x_p - x_T} = 0 \\\hline - & q_\texttt{mul\_lsb} \cdot \Ternary{\mathbf{k}_0}{y_p}{y_p + y_T} = 0 \\\hline -\end{array} -$$ -where $\mathbf{k}_0 = \mathbf{z}_0 - 2\mathbf{z}_1$. - -## Overflow check - -$\mathbf{z}_i$ cannot overflow for any $i \geq 1$, because it is a weighted sum of bits only up to $2^{n-1} = 2^{253}$, which is smaller than $p$ (and also $q$). - -However, $\mathbf{z}_0 = \alpha + t_q$ *can* overflow $[0, p)$. - -Since overflow can only occur in the final step that constrains $\mathbf{z}_0 = 2 \cdot \mathbf{z}_1 + \mathbf{k}_0$, we have $\mathbf{z}_0 = k \pmod{p}$. It is then sufficient to also check that $\mathbf{z}_0 = \alpha + t_q \pmod{p}$ (so that $k = \alpha + t_q \pmod{p}$) and that $k \in [t_q, p + t_q)$. These conditions together imply that $k = \alpha + t_q$ as an integer, and so $2^{254} + k = \alpha \pmod{q}$ as required. - -> Note: the bits $\mathbf{k}_{254..0}$ do not represent a value reduced modulo $q$, but rather a representation of the unreduced $\alpha + t_q$. - -### Optimized check for $k \in [t_q, p + t_q)$ - -Since $t_p + t_q < 2^{130}$, we have $$[t_q, p + t_q) = [t_q, t_q + 2^{130}) \;\cup\; [2^{130}, 2^{254}) \;\cup\; \big([2^{254}, 2^{254} + 2^{130}) \;\cap\; [p + t_q - 2^{130}, p + t_q)\big).$$ - -We may assume that $k = \alpha + t_q \pmod{p}$. - -Therefore, -$\begin{array}{rcl} -k \in [t_q, p + t_q) &\Leftrightarrow& \big(k \in [t_q, t_q + 2^{130}) \;\vee\; k \in [2^{130}, 2^{254})\big) \;\vee\; \\ - & & \big(k \in [2^{254}, 2^{254} + 2^{130}) \;\wedge\; k \in [p + t_q - 2^{130}, p + t_q)\big) \\ - \\ - &\Leftrightarrow& \big(\mathbf{k}_{254} = 0 \implies (k \in [t_q, t_q + 2^{130}) \;\vee\; k \in [2^{130}, 2^{254}))\big) \;\wedge \\ - & & \big(\mathbf{k}_{254} = 1 \implies (k \in [2^{254}, 2^{254} + 2^{130}) \;\wedge\; k \in [p + t_q - 2^{130}, p + t_q)\big) \\ - \\ - &\Leftrightarrow& \big(\mathbf{k}_{254} = 0 \implies (\alpha \in [0, 2^{130}) \;\vee\; k \in [2^{130}, 2^{254})\big) \;\wedge \\ - & & \big(\mathbf{k}_{254} = 1 \implies (k \in [2^{254}, 2^{254} + 2^{130}) \;\wedge\; (\alpha + 2^{130}) \bmod p \in [0, 2^{130}))\big) \;\;Ⓐ -\end{array}$ - -> Given $k \in [2^{254}, 2^{254} + 2^{130})$, we prove equivalence of $k \in [p + t_q - 2^{130}, p + t_q)$ and $(\alpha + 2^{130}) \bmod p \in [0, 2^{130})$ as follows: -> * shift the range by $2^{130} - p - t_q$ to give $k + 2^{130} - p - t_q \in [0, 2^{130})$; -> * observe that $k + 2^{130} - p - t_q$ is guaranteed to be in $[2^{130} - t_p - t_q, 2^{131} - t_p - t_q)$ and therefore cannot overflow or underflow modulo $p$; -> * using the fact that $k = \alpha + t_q \pmod{p}$, observe that $(k + 2^{130} - p - t_q) \bmod p = (\alpha + t_q + 2^{130} - p - t_q) \bmod p = (\alpha + 2^{130}) \bmod p$. -> -> (We can see in a different way that this is correct by observing that it checks whether $\alpha \bmod p \in [p - 2^{130}, p)$, so the upper bound is aligned as we would expect.) - -Now, we can continue optimizing from $Ⓐ$: - -$\begin{array}{rcl} -k \in [t_q, p + t_q) &\Leftrightarrow& \big(\mathbf{k}_{254} = 0 \implies (\alpha \in [0, 2^{130}) \;\vee\; k \in [2^{130}, 2^{254})\big) \;\wedge \\ - & & \big(\mathbf{k}_{254} = 1 \implies (k \in [2^{254}, 2^{254} + 2^{130}) \;\wedge\; (\alpha + 2^{130}) \bmod p \in [0, 2^{130}))\big) \\ - \\ - &\Leftrightarrow& \big(\mathbf{k}_{254} = 0 \implies (\alpha \in [0, 2^{130}) \;\vee\; \mathbf{k}_{253..130} \text{ are not all } 0)\big) \;\wedge \\ - & & \big(\mathbf{k}_{254} = 1 \implies (\mathbf{k}_{253..130} \text{ are all } 0 \;\wedge\; (\alpha + 2^{130}) \bmod p \in [0, 2^{130}))\big) -\end{array}$ - -Constraining $\mathbf{k}_{253..130}$ to be all-$0$ or not-all-$0$ can be implemented almost "for free", as follows. - -Recall that $\mathbf{z}_i = \sum_{h=i}^{n} (\mathbf{k}_{h} \cdot 2^{h-i})$, so we have: - -$\begin{array}{rcl} - \mathbf{z}_{130} &=& \sum_{h=130}^{254} (\mathbf{k}_h \cdot 2^{h-130}) \\ - \mathbf{z}_{130} &=& \mathbf{k}_{254} \cdot 2^{254-130} + \sum_{h=130}^{253} (\mathbf{k}_h \cdot 2^{h-130}) \\ -\mathbf{z}_{130} - \mathbf{k}_{254} \cdot 2^{124} &=& \sum_{h=130}^{253} (\mathbf{k}_h \cdot 2^{h-130}) -\end{array}$ - -So $\mathbf{k}_{253..130}$ are all $0$ exactly when $\mathbf{z}_{130} = \mathbf{k}_{254} \cdot 2^{124}$. - -Finally, we can merge the $130$-bit decompositions for the $\mathbf{k}_{254} = 0$ and $\mathbf{k}_{254} = 1$ cases by checking that $(\alpha + \mathbf{k}_{254} \cdot 2^{130}) \bmod p \in [0, 2^{130})$. - -### Overflow check constraints - -Let $s = \alpha + \mathbf{k}_{254} \cdot 2^{130}$. The constraints for the overflow check are: - -$$ -\begin{aligned} -\mathbf{z}_0 &= \alpha + t_q \pmod{p} \\ -\mathbf{k}_{254} = 1 \implies \big(\mathbf{z}_{130} &= 2^{124} \;\wedge\; s \bmod p \in [0, 2^{130})\big) \\ -\mathbf{k}_{254} = 0 \implies \big(\mathbf{z}_{130} &\neq 0 \;\vee\; s \bmod p \in [0, 2^{130})\big) -\end{aligned} -$$ - -Define $\mathsf{inv0}(x) = \begin{cases} 0, &\text{if } x = 0 \\ 1/x, &\text{otherwise.} \end{cases}$ - -Witness $\eta = \mathsf{inv0}(\mathbf{z}_{130})$, and decompose $s \bmod p$ as $\mathbf{s}_{129..0}$. - -Then the needed gates are: - -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline -2 & q_\texttt{mul\_overflow} \cdot \left(s - (\alpha + \mathbf{k}_{254} \cdot 2^{130})\right) = 0 \\\hline -2 & q_\texttt{mul\_overflow} \cdot \left(\mathbf{z}_0 - \alpha - t_q\right) = 0 \\\hline -3 & q_\texttt{mul\_overflow} \cdot \left(\mathbf{k}_{254} \cdot (\mathbf{z}_{130} - 2^{124})\right) = 0 \\\hline -3 & q_\texttt{mul\_overflow} \cdot \left(\mathbf{k}_{254} \cdot (s - \sum\limits_{i=0}^{129} 2^i \cdot \mathbf{s}_i)/2^{130}\right) = 0 \\\hline -5 & q_\texttt{mul\_overflow} \cdot \left((1 - \mathbf{k}_{254}) \cdot (1 - \mathbf{z}_{130} \cdot \eta) \cdot (s - \sum\limits_{i=0}^{129} 2^i \cdot \mathbf{s}_i)/2^{130}\right) = 0 \\\hline -\end{array} -$$ -where $(s - \sum\limits_{i=0}^{129} 2^i \cdot \mathbf{s}_i)/2^{130}$ can be computed by another running sum. Note that the factor of $1/2^{130}$ has no effect on the constraint, since the RHS is zero. - -#### Running sum range check -We make use of a $10$-bit [lookup range check](../decomposition.md#lookup-decomposition) in the circuit to subtract the low $130$ bits of $\mathbf{s}$. The range check subtracts the first $13 \cdot 10$ bits of $\mathbf{s},$ and right-shifts the result to give $(s - \sum\limits_{i=0}^{129} 2^i \cdot \mathbf{s}_i)/2^{130}.$ diff --git a/book/src/design/gadgets/ecc/witnessing-points.md b/book/src/design/gadgets/ecc/witnessing-points.md deleted file mode 100644 index 1159f37d16..0000000000 --- a/book/src/design/gadgets/ecc/witnessing-points.md +++ /dev/null @@ -1,35 +0,0 @@ -# Witnessing points - -We represent elliptic curve points in the circuit in their affine representation $(x, y)$. -The identity is represented as the pseudo-coordinate $(0, 0)$, which we -[assume](../ecc.md#chip-assumptions) is not a valid point on the curve. - -## Non-identity points - -To constrain a coordinate pair $(x, y)$ as representing a valid point on the curve, we -directly check the curve equation. For Pallas and Vesta, this is: - -$$y^2 = x^3 + 5$$ - -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline -4 & q_\text{point}^\text{non-id} \cdot (y^2 - x^3 - 5) = 0 \\\hline -\end{array} -$$ - -## Points including the identity - -To allow $(x, y)$ to represent either a valid point on the curve, or the pseudo-coordinate -$(0, 0)$, we define a separate gate that enforces the curve equation check unless both $x$ -and $y$ are zero. - -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline -5 & (q_\text{point} \cdot x) \cdot (y^2 - x^3 - 5) = 0 \\\hline -5 & (q_\text{point} \cdot y) \cdot (y^2 - x^3 - 5) = 0 \\\hline -\end{array} -$$ diff --git a/book/src/design/gadgets/sha256.md b/book/src/design/gadgets/sha256.md deleted file mode 100644 index eb9489bf7a..0000000000 --- a/book/src/design/gadgets/sha256.md +++ /dev/null @@ -1,65 +0,0 @@ -# SHA-256 - -## Specification - -SHA-256 is specified in [NIST FIPS PUB 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf). - -Unlike the specification, we use $\boxplus$ for addition modulo $2^{32}$, and $+$ for -field addition. $\oplus$ is used for XOR. - -## Gadget interface - -SHA-256 maintains state in eight 32-bit variables. It processes input as 512-bit blocks, -but internally splits these blocks into 32-bit chunks. We therefore designed the SHA-256 -gadget to consume input in 32-bit chunks. - -## Chip instructions - -The SHA-256 gadget requires a chip with the following instructions: - -```rust -# extern crate halo2_proofs; -# use halo2_proofs::plonk::Error; -# use std::fmt; -# -# trait Chip: Sized {} -# trait Layouter {} -const BLOCK_SIZE: usize = 16; -const DIGEST_SIZE: usize = 8; - -pub trait Sha256Instructions: Chip { - /// Variable representing the SHA-256 internal state. - type State: Clone + fmt::Debug; - /// Variable representing a 32-bit word of the input block to the SHA-256 compression - /// function. - type BlockWord: Copy + fmt::Debug; - - /// Places the SHA-256 IV in the circuit, returning the initial state variable. - fn initialization_vector(layouter: &mut impl Layouter) -> Result; - - /// Starting from the given initial state, processes a block of input and returns the - /// final state. - fn compress( - layouter: &mut impl Layouter, - initial_state: &Self::State, - input: [Self::BlockWord; BLOCK_SIZE], - ) -> Result; - - /// Converts the given state into a message digest. - fn digest( - layouter: &mut impl Layouter, - state: &Self::State, - ) -> Result<[Self::BlockWord; DIGEST_SIZE], Error>; -} -``` - -TODO: Add instruction for computing padding. - -This set of instructions was chosen to strike a balance between the reusability of the -instructions, and the scope for chips to internally optimise them. In particular, we -considered splitting the compression function into its constituent parts (Ch, Maj etc), -and providing a compression function gadget that implemented the round logic. However, -this would prevent chips from using relative references between the various parts of a -compression round. Having an instruction that implements all compression rounds is also -similar to the Intel SHA extensions, which provide an instruction that performs multiple -compression rounds. diff --git a/book/src/design/gadgets/sha256/bit_reassignment.png b/book/src/design/gadgets/sha256/bit_reassignment.png deleted file mode 100644 index faf495c7770c8392bfbfb3887ed3946de245a0bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36660 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 diff --git a/book/src/design/gadgets/sha256/compression.png b/book/src/design/gadgets/sha256/compression.png deleted file mode 100644 index fc0c4a39090f502ab3855eb2f54b669d5af45435..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42544 zcmeFZbySt>*DeeT6c8nqPC<|k=~h4*1f;t=r5hDckP?w@C1e4M?vUzec$_@bH?|_H_m@&k7wu@xP9*Xp7)&Byyi9MElV{Dhq%9S;14{_d6BTTI|TKTPvaC@3^2a#AnfdKqq~VKh7*KkmYPO8L!F zS{fYmq|^?*zVtMzv0a;hF!I{rw>1yLYcnS+hzzcISlGPenaFcBgASUw;fimks*A&;PdtMj3}$GqGii5t3*V zfBw=-fbO0if*Rt1#q@3VDIu&F6s&ehC%|hEYe#Him#m=(!uWS}e z{w1B0`73PmZo63Ae{!l+-W|b3OamI&9np-M>R=Y=PeUj|h|Z8V~hJMK4K7ov$m){)VQc42x{m z%B&rbb8u(HKa~qYFO#Hs3mSFr(D>n7A~v1%=5-IeK;1Q?%BU`abl_L@G=W0m=xnY5 zLO)eY-`_#UCeqzC(5lvr>fn{KVV*^O(x;=ZY=~m=4jc@(0?T1+w|c+EUA$bXI(NAe|>sX*;P}S5zAX->$E;ukE5u zN+GK-zV;)1;hNZRGlNGOImr~)^j$~2`H6qC^z4Z{^n=f7Y$d{;*57kVPl#i(2*m3t z#!kFqslXG~^z+BO8s*QULl(JccZ58yqRn=r&duD7Kl_@SR?b$`$Wh@m>#;-YCS2O+ zSqnBX`?pf323BfQ=EX2NqZp<|KPG5kBJ0t5|MzFQ72o(Mh0KJ^RA3A5{9&~&;yO-! zJ6u;ql0Ple&Km@pOiYO?n_kdtVBUI>_>j&@sbunVfhK7?>f7hGl!MK9OFHUN`b2`wq z*&*@X>HM23t=)?)o3azj*`9zreHy#dD~&jJKks*c-k)<@b&jPL1VlPz!Dsw1rVsYj zf9`qIBc6r@kUh=p+WnMwzYlHl;kUhJlGz`J_uj2{_fs?UFxuvX_Qbr@TCbY54)eFD zHoJLlXK!rg;uYt7#&8=65>Y{M>JY=g3)IU8VotGmuuLTtzwdqBU3*^@qGsg&@|&&@ z#u){qGu;Tei`T+b!r~RhUaeW3n~b8Lucl3|_RJQ4`<&5`0KMwKptI-}F^R;_pOA2qV&vl%wo4tibDhwdE*cNuEA08Gy%6mZ#DOMpUdGc%h2EA z?}{mxKRtc0armQ3mAMe!f8CNq&652P$6lo37i`{pYgXp{c0=QKetRKZtJ{sQdw$|J zGbwl)3@YqIy8AL^KgeU-7+YN#!f<#N*XjR8<%jgv$>>eX3Rj0MxniZR$_(4Qj&7w5 z%ZtEdW^ogtiM*PSbBq$4SpE!OSvUqg(LZ7H9M72@H3@#sczBMW+oW^rjEwFU^aV%X zaI`NW+>-57%yL_E`KHW{)B9!9yW@CTo3A_3QCtYFh(m zg)PIBlQ@HDAxfB3b(&I7zS|uyxr00Rc0b1vMjU}=XnnmPv%QteFmgtT`hz;ql z(T&Pf&+E;EG}OCx)eOErsf?f0SE&+R5=aufm-d=}$iIr;NSD+8TwH zS|)7{u_?%~Ut>VMLiM<>*{*Y&B(Bl(i#Y@EfzL$4`B&FrYK{UYx8=M?&96G^^Hz1q zkLT!)(d_2?NPQ)FIqOxS71Sk#~7SCAis| zvdQcJ;)K%sx+&#FPVB2oI?m++45>VoC%t8|~ zBQd5fh%I~+k?EuI+0t83wz|4ovPPDD;1X7?__0>SEAKzofxeCRpW1sR?EF22)>1c| zwEDn=sr^IkueYwya)vujo39XQ!CU8=FA)f3nXA%;9gN~D?A+CX6Q%oC11P9zI2Uh5 zO6%9Y}1NSc=75aYy9uc1HR}ycIZ@oX#beJK{eF?kE&o21R45&%h zZKuZ(U=95n|7b%gvu6M3UpF_tZB#>-Z1U^<*E$S=2L%rU;p~DpEo`ehS-9))t&*5} zliy`37CcmMHJ>*gu;PB-#Dved&0aj}in8_6ly$GHeS#&$n2Afu`pqx}9=$-L_iuv2 zbNvX`epLBNlyABAHh_cmYc`+d0q=d6g8k&J2l>D8hP7 zUB4sjQQ&hLe}6}e!Av7!J@n4DPp|~I?roLhzV4~c2>kZW0}G6pa!bhFi>ZJOBG$wv zx!3ws%;yj#T8|UsAHkWH)^{qi=3EYJ7Uu8SU#IFEn^=#3f7e~O6IT>5(c7QVR&T7) z7dhJ@6V&DC$2v`Lmod;#8HG=pUmm_rGX2MPwI18>6N?>^1e>rGQXayb?pz~_sUV7EP8x@ zxVb->$w8QPCvwk5=;8=&b6js!RvGwNub?T70ZM<8W|20aSJhFfcZ$}!o%F(W2oBL~ zZLc1ELSy%^AOXo!$OvVx9(-c2qPp&;JPvU1Y~hOZ*I&Kq$gmSF%IGLFkN94jhqa|% zhH<$V?+RO?mhG!jSo_f=aAtXaR`h3~dw1Bo*2l=|QlNvp8S3HoSH->hho<<9r!Iqo zXHDP-bfG2T{5S6@>V>SG?C&3_a^D#Q3sk^JlaubRP1K(f_pU^}dfa^AQ8nuy+xgjx zTE{%;EgOGqix4WizAUS&9K+3bHx6F9Ra+McI<X{W5#d2Nvul5Ts9xsDcD?)9U+oNZ5TUl` z(GFSEJiesGkr98R7>9Ek_>M#yCrkX&l4wHsG<@-8nK^n?(NtxY<+d^Bj8Qv>j=-I#8o$xMs+sxRk%UfuZqr+RT*NWbApPDqyc)*KH}&efk^j7Y4mPCb zI+mQ{vnK|r{Z7^vM{nuqXq}lBtg?rX^sSs+G2yV8X9({^Uu~`NwFs%-xOZ{A;>}(0 zBtjFT3(EF%Bh!U%M?;JK-Abh+B8j<(Zf?F6Qh~b)cdUJNIGmdIr5f9dS;mV#97rDp z-evW=3 zzQb`mZgjTyh#S50)jtJ@D&~*xL4Nx)Z<2T`cmLS3&ANYkgxDdF?Rq`C%fO~PIbW`Ml-aHegaC;91D2P|DuZqp0YR2`vA{?tfu9+f1fz{}0(LbC2 zdAIz?NBN9`{iA-H!yT=R9;=+iucu++bU8-e9{8Urzwae8cFTL?@Kb|n!=QO= zh>(Mv-au1ZkS#%SPm7xKJON=CYj^FH=(j%_SL^Y&Kau&S{?~CGrf;x;7E`06)9f#cY{7xnSThw(C|)oO!$1hfR7`{pWNUHNsZo-#rX8V-}af zF#jQhv*>C`?~gYLPnRYyLj(^C!R4R9Q#n@o&%qUc>yU+`yZeY?@%+OCkFG!C<;-z}H&`k@^YSNZi@Oej1 zRmAoO)x+yTNej5$P#?WwLMqp>a1y_7E|1fQ?hHV`AIxhdR3fhB;Y6dN{MS!%qjC>@ zwJcX8Opu_o^aAM+eN;|ft?%A?%~Jbi=byh782G`iTeoX9&*s^!GZ8*cqBr6oZB@JC zTItS@U)iWEP(NpB7(m3^q`Kvc6{&oioM9>;NF-U5qYv!N7{jaEjiphzJg%XkS^_EcINf^MdQgkio^km`_ z4F69hj(iQKL=cuNiuT;M|F$Ms@S7o2@6(@o78CG{?49Yj8N#mU0q=|PsnfNdn@cER zNltyO@dAaqPxIBq5k&u!4IyB7qDP~F#JqS{Rq2*ceyaDvJrX{`I_F&Pkj%uCQq87krrziUZX zCFK4&A7FijKbL}H*qM94PdqTu8$pLW!#`YLD_T=ddQ&evUeO7OI?`-|B$G8Z#ULCe zPGqA+3Hmti4IE^TCWdEFPrlI(TznXkh8#V#xn`+2lqu{$TcC7;)xx_v#2;MYaD;q( z-4%{pZI~w*dQfHPU;36uP?2t0A?4uMd8xZ1E&t%!*DNr{wqdBk^Hf;CS#Cyo%Bx@;1K#-BM@YhOrW}OzX%@f2=ddb2C3#+q zXytHFH?;`%XhtP8r!^QNYRuB&_T)+llKzr>`2@Mx5+P~^0bWMU=%h8efc59j=t@8h zx_r7%Iwwk=3*|Bjp2zmZ$^DGgjC&RStnnEtLH_$76|7Gk93~OTg&S2#bM2L-~ zZ+ulEGm2-wd=^)QO~_l>KRybA!NKLmY1useb+-!<0A9&1ZRTPGPH$k_%>CPe2qao8 zC9<;fS=lZRf$0cmj_`3dzQ|M_8m*+eP3lX$$i0=kgq}+Pd&Bw|K@LQNd14FyFdyCo zb}qF1{%<#*2~@~m&Rdk*kGkF2%L3_v1xhI{L3^%=F$(o{lNSt_m!NndQ9HWCV&aiw zTIv=zA5AMl^ygpEWocdjK~X~~AA!ab`AK?yZ2P2?B*F8`JAHbG>KtfkRFFeT*S?S; zub|ElAJH#BnA#6PF2bP*vvLCwVc3iml-?t7jzfuFUtQ+LQZl*^t+x`zsYlJVlX3I; zF4q73R&hbmEa%hx zXItZDP~aY?ZJqLVbqNw9(tx|8H0f%C_kL0yx*!MZu~GhMLb9JsApAeg`u|==QKB;i zHdXJ>+POx#LP0E7MHt#LoBsn^e$mR%Y8W(+JIk$T29`u3mI>C`x1f*X-9Fd&5CPdE z4taPY`Y*bcm0BU?%DGLM?QLdll|-?r{Z~`Zr?4Osm2MvqPf-aVaxHo2?aOlg*_J6X zPfISeKMP0Ou*9B7u7hd{s`tiWI$>=S!Ty*~m<|hqHrNoruQOTX?o`W#v`hbW|0tKJ z2>RL2DJ|MO`PKX5j73{;FfC_V`p;^(1yYL))<$lwojEm9P^o2^W&jp6EJ*N4BB<*f zx);;iD=fqqOsCP9zm11!q}~d!3Ln#1n*p$tdCaY|#U)A)kc#yG`fAQKLBy*yPFjPH zB_JV@X9d%U5j75L(fkk@YU4UOk9xBK0xqcQ*4so1(Rd>23Z{3=xRm$69W%)b0*G)H z2Y-;vU}zC8l=rDhi`x!hcvC-v&jeRAsL`0f7QH^k7r&-&A=H%$0`Kym+Fi{i8|Dcc zNv-GRG%5Fu=Kb1=$4mQujH;MtQyDSER|td($y4BdTjLV5qHNy$>bQ1LE&pAt)I&=D zCNbTereX(65A{S$G43lB>6ii%G`R?kK?E;`(UZ#sWOM$a3w`~dr2C~j^mnqOoe!nR z7=gPDg)T>W`Z4D94Ta)l6z9q3Y^xReB@Q62ce@6ci3ND;7dck;hfP!CQ+Vem;GHh^ zXH%UI1q^`YXs@YIpvO;zL<<|E-*3DC3RhQjKY59-IZGaWL@EnJy!G?gFoRTd@UasT z4_KP2cqyW`;H>^FCX7?QkQC!a|0hz-05mN>yFHV3L#dUWBR*uRna$RtaB_s4K=ykJ zI*&MB?$BfJdmj9Xc~+Dtb~t9h6?4i1PJsxn1`cI;im;%d0uJR`o;wRzJt`7hd4P>R zkF|vcY_3jBYA@ZLNUk-JtwiKTGJI!^64W0$6O!Yz> zBgJOvZ*E@AKc4qAn*_pD}o~#5i zm7N>_3^nqZZ$V{GC?;&eGJeDNmepi66N6R@I8klJBq=USI$o0T3Qyq^I`^2SKFLAe z0fTeCd7WHZIxk7pGsV5cpDJ2`!~k&Cnd=I6oXcnGgAVzm8quKF`ud4MJfLND(6XIO zIqx8g!-3M{v(#DHu~RN4i&i^hZ$bIfv)~=*o~_@R;nwS7^!2WX+dwl zax`{{c|EXu+9x1VL1MLLsZ`C|=t4nmpA*$W(XatnE&Y=r>4;H$SL^-O@4=-p61wPQ zDs7wCGCWvJ`lusP-q413sR83s-vAd(c3y1PyLG{ojsC|;bjC%2deA<{^oPN%a4_3& zgL2@=diWeJ^rjpskMJl!hj!$`0(6H}bh#{>z9Dr6KlVQh0I zE-)XX0gk8pOv)Gh3Z#S&ywj(e#Xs|JVs@mP(2{+kVLViAs%qaqza+Pr++SYekc;Qk zwDg1Sn0>JsIgvf`O6_XwgqaN1VOtzy{>B-`Wg+yV=s=uA{8A2X*e)EHzw;9)9?&lMlrir2{D!4Bb4$XJ*Vfk( zoD<4U|GLHWrYc{s6sJ8DK+{)*iBZXbUlK3xp>n1{OQ01qA&&7+z@@pnBp?m=1nW88g;n z!dr9|{GT0_)o#K{a1Lu0&w~~~c9>6Wxv`HY9l-j6@$PO38{b&-AT;PU$?;QujRG<> z>+4KTz=LSBRWv7O&ww!WAEpJUl>`dVWdyaz+o(r;;IUOMC?EH#CRzc1DuPwzR(FQs zdnSN_oghQO5JOJ?%Q#Rivul81GWnxwa)t9iZ`lS&xlvfy4xPEsNfr0fj|H~x1J^$S(^1G>!u6B`DH8w|bs;c*l)Btge?O)c zn;@SS)vl|8w~=>SelETZ{cj6@T6!g~BDHls_N4gxM<3loO8?=JBA=&Wzlh9~20SkK z;{kh4Jzm9ARJu>@;rJ<>xnmrB+`|^(B@s)L(F1QGGoI^a? z$AhWZsq79AVGVd&X*W1ZP4Gz`$X)cmG>x{7R(I@HH~6u5lv-xE062!KuGiDQ zp$xt^1?Cjpa=vWDLE;{?T(h_s%TdD&lm=u`4I} zG9%`%^Q;WeW^r=mJ{#hXNUgF@v63kGpnq!?ySkLtZf+nKYTMX@h_{@`4e@%OZUYQ} zy~A8|rAJt&$vzF;_Mbx+A>zUidan1`aT@lrRy67^!*-u)Y1TSq4gh72;rSaV>Ru@j zD#s+-4@d}vzkkCNiMUFCNPZvsQB}A_9wTM;3f z;0*my>LWesT)9uh4{Vm*-zsWsBdrZO2*Hh<&Uwu#WnFqpw0nMz-HU5VnD6>y&< zYyq)v$`M!GIJj(`I<-2;JW0`Yf0icL7HdO@FfF_iNrYhr9hBVzpL3glapUe#WHTLB}NOS%HKlpo4G1>@*yzL0f)6@Ws&{P#HSyIxZ z>rae(ieU-M4+$V1ia@f1C;*)Z^w2gLpwfvLb4fAw#`Yitd&cfH?wwUvV;G;{s1f zgojB-^u|-Ap$vHQmIS=Ui(8x5SMLR?i}+uFvW;yAG=FK6jBwF(oN&{Fl6RH>KJ-?}LVTq_mU-)Q;B1&K$dmRG=>Z@q=;@)Fq`cJ?&#ke+d`FWw%)|4xl#`8;}?n& z`jLtsjEFFD|1oT%^p8QoDzyiUHzRnLrT8O4-Dj95>G+MB7MmyJp(`PV~)Po+#{D0{L^ z*IN%s;hTs8w)JFyp1R-|MpBdskd)x-?uMeP(cUIj{&?m1QDxpLp?RrE|Bc72jVYsO z5L(C(l0>tqND#Kd*7%vuVc};|lZb~OUm|DDtf8zac(-B+v0iU>( za7yN&4eh44M#pVEQc|Z$Z-oq1I(Je{K;M)N4!U*@z}|KvA&hzq!ZS^{35!$8*QTAC zH497*x+Hg)qcmL<+LOmvFHT?@_SDj5%sSA@FU0B=wYG8`_IP8ILEDN>|GWmhx>`U& zJ(b8^Z!C2wYsDU_xnhrGN`q9+Sb`@~uXf6G0LJo=@OdQ-Q`^_ZxX!q0Pg?99PBTif zG_5B^9n;B%B|>y*fXGG@%!71}efsaOapkNszPJm`*)?i))=sq7irPM03_bNE^W%ehP40 zYovlFOSqfj1rRMllflpCV2Ab@q)b>j5|O5Q&O_zn6{sZwt;p%-FWQ&z5Wy@J+!d*y zaKvah;EBCzCKf~=QqvwCjTRo13X6)7V7g1l@!_T?3s&HAVrcGTu0bW$KfXXx*1$W1 zM0Uq|IWMGKz>i;Bh;{NUwout&w?U^=x(q_DL#)11M42P!5?k&8yiEVoD{pF6oKaTW z2}5I~=|bq{ktDehT5|mY1|~OY+lqhgxETXMP8(Ii-mfe?N;{$96k8Til-U38>*w z2}|troiIS4lOC8s10z|d^EW+Y{(?EZ3m^dWf*tACZUuHfSSH-{FD#B9EolFNEyl%; zJ#2~pI+(Ij=tUm;g#XDol=*2hWv&Gee;&kMJEcig3K zXtMj>X-X;1V1F0c#In6oNwohcVaA?e?(Y#)!j<~+aeg*wzG*z{$x`daHw?>rfnbJ1 z_OIzyuYEzE9KQw`YPURYpXzh9J|LaC52-h?dOeR;5+wDk`$7(HDd-v2uieTXfqG&> zA1M$HAxUv_%iq2eZD%Si!H3Ygi8GzmqRG;rt7oyZ;w>6@bG=%9<$d8I!-B=4()Jil zi5%t-YvN&-O9n`DD^*BQnglK^iq9z2=J0@G+ge*)OBS?~j3rB>Utn+V5(8+b9_+~c z$mSQUjWicC;{$SG%IJF=A!Wb8XFP#(IG^u-jS{mi=M4}QBdFfGq9iII?%lcAD#5mJ z5;&mU)+fmgOJYhSOCUFn4smdJ?QWArhFCEfYIP&aX1JHvHW(JkWWYAYD5|QEXFw~L zz;A+R{Tpgqp!!VtclZa>GKc-uG*+VoT`RCb_-ofD?TB)q2s)-X)t#W`a&P${z2GWp z|NLP@QylY)!-%2Et75L*^|2CZUET}nQxtmI8{JCfo)pTjYL6lFDWz8d5Nwmcpeqz| z^m@u%v=P=PNi@1b(x{uZc}b^5RWDOv}nIxwl~Ur|Sx$(rmwA9WBJ`tT1&A z*)dl54Y#GCYDi*_?q$k24%>oCmwsa)2Yu%_Yms3h$zgSpoO#<3vFny zJnZD{5iK?^JO~&j9_VnHtdP}$;hFn)=NkG?fpW22SK6gC?B=LLkB3s4vj^A64y1~fRVGIhzbhe&Ig)2+zqUN%~Cr?`IKE2pK-81f$ zUU9|VCewBq`SEP~adPX^D}fTsG>kw%r&4AayZ#vl9 z|MlSlvZj?kg?-|rop|rhg?}xbQ0!&^RW4Ru@zJo&n;Mf>QL0(i)Wc@GRoSP^S2_-X z6RVPadnP{j=qCU{j-b#vn#|6R#%EQ?;E`-lDGp{obih4**GD0JKWgwqN?QaAPqp*M#GqfYytqpun~3|*0z2#<@5DP~i1 zaJX!}?5X$K`YoVqm;Hz`$kr9=9!feKqKU7gmJRhVi@|Q7iVgtJo`rLiZS5* zq>Y%Qu~l|tePOY71?|jps+b!x>K!y_2vlgQ6S|ec8n<7J12IeWsGbVQebRVz51v*t zMWoS$8p&^>t|So<;Q?aw&2JXp-2L=Wy`Z1^2^CW&J>S)SwD8mYq&|A z5h&)ZKMr>t_TPM)Bx6$doYWH@Zb7DcrA%ObFA3>!$Y)8y&ycT%Q1@Lm!|G=5cuZNFVSqJxY4UV$9;JpwlW1z329re?iZ%v^T z*^-#ewiLy=b>DphJ7jiiN>yKhs|T`8yewg$zSyeA`N-JnmZ^VMsaX~>95 zj_gTzN%C#5e4^To>jZO3h?5et=^i=eENRsPK=ausSrU>R7dTUc_l90S&(R9w4?7OB z;XdR~v>mHC4TjY{ZEWpS^L%op88m#%Oy0&GVzccYZlEt6y=Y+U)6vAgjip^s_}1qh z!y6M20j3r+xgO%ZMmG_!-q?UZ-eJ7jQJQY#yMdQ^5Xs1$6$rV>#TNJ)RM_0@_yB5T zf%pyI)y~Sj%rwj?TYCuZ8W$GN@{6{SU59a$YkXe>Yp#VBJLYO(xjmTmhd3Df#7kgg z5Vyi@Wxhh=hW(Txl=Vi94u^sZI>$|XETjly7F*8JK$y}NpzDcMGe+$nA$t*gDTs{D zmxTIh*}2fB-4KoXQ2I%Uf@SDquo_v}wHR&%Sf=JBwV4Cm#BF+uG}O;i3p{Q>^;S*+8(O5}}UWQ!JI%9YB8FA!Wx5k72uYFxT8 zp4vJ_7jf~yTG=#w#?DzHE+)^!x797O&qZ4KNpZkq!@`Tf^(QejCTmrdPv2;)7sbDr zQ!zy+S#KXMGa<+)4Zbl^$(XcvDkHkD9qsq2azcov%d>mh&g2{=m&zde-Zb^fuWrRJ zc04eWD8a)#nwPMsLat;#2(sf&lc|0Ke-Kg3FGS?wqN*42uC__j-|!UnKz2O)DT+tw zFX?$AUc1HH-bL$a1XACx^P=iWRH-+`gWsQcNKwn@csPcS4F%9zKE7Bx(|*BOed5|_y9pYQiH#D`aCFj5%0%LI8+Lrv*mvWZaOltZiwqS$(FQk>37JneUyuYS% zU0LSdqd&w0q)JiamBR5}UYhZ*@=n5lJQtB%8FpLR|Iuql(m<%Np7Rh=>WRS#W`XGG z$M%cjmHq-iQY}97p<{cT=lu`5uL#TxK#H9URY-F8V#0ta3|Wga6V%}8ZAiW5&nw2U7{vdBthgq zhy!^_fbcO>KhHi6sc|UnmU(*R*6h`OS!yjwOcBN;=0^>G-xO=ozP1HU4)njb^VVN0 zKl`fVFf|hMdh*9FfbJ^=3Dgi$<7i@(dcz3Jb}Xhq!VYYM6Y%@Hc)(;HFa3TB1hJV^ zQj^uad3FSb>Q>{i!jeUdCTk&cCd z_k#`5Dd`qNEXd~J7|?jlw%qxQG_u}VRVh*?J{@8{@Xl|3yv5PEVlvrwjq%RInKZ-x zMz{5)0JdSFG}A?e-EZ;o;e~2dGE)PBeH;9`aqELs2gbuMm66(dNOH#K9iMM+T7aB6q z*HRTcj|we(>eL#59l@PJHLe5rQrYVub;w+F{vCxUyaRLxGo{l9cul&5MwK;5QE!DX zG|C6mtn<7HcZi!c2KaomGUUa3-P`%%m$e4?(wNT*5yKw6@wQc}qVc<$KeoTAxQv*; zDsg-)&pX@S)oEMjFxvog(3=c<^f5Bn0IkhHAGtD^Fdx3=lopMB!IHMEzaJ~sE*VrB z6_&GAq#a(46EqY|iBZUN`?J&zF1$?D1fONRm{#dr66*%zy0Poig&H| zp`SqY-2F3fvI9Iy;E1^*=N+6E>$w&XjQUq<84gjnR6*``6~Tw989Z3?fC5v%cP0k3 zukn#4`>s6my3R;0>5ID@Ptrw@%ivpql*c*E**7@35pgH~0X4GL<0slbe| zIdZ~7_V1Dw6CignzqBxZ(J6%X68!3Yr2ybK(!|~U3>TNt5hgy2tPB)MSS%qi5{GyF z0fi>(_gPdEvZ^TxJIE zRtvU=k*a~xPxbRksP~T4cNi>T=w-td)&@gRzG!KAnsBVns7T<+c%57!5A8CT*NtJ>x&gv8HIs1c~ zccJe9bwmL3tl~aBPNP`W@3`t{RqXW_W|UDWPL*J!hO&4;cXexAo9|Ir{xi#Im~^M6y&(m9K`N4hm|?x>HGXMj$Yh}El3*!UH|lk z9jWyHSDMU~NB;4ryDoPrX7CA^*=$Fr+g>mqhTd9}< z*$m!sc zC&-Sd{G$ndibbK8BKhC@%uCB;2bR$j@7PH0%OCrDLVt83CTUG?1{BAhe?&J$?t!4( z%_(`MXEx=Hw8yv7s4v42_it^=A!HJ+Y&yr4m(blOp0^>{T7TUZE-Rra=fxPDZ1jBe z{*&)GZwp2`Q?jlZy-EsY>!YQ=M-NZf`!QXTWQi481_6N!h~%Uj&KmN@;6n7ZbIUK~ z>!?4F+++byfGmAeBWc*eTyh|8KTmh+P=I-tw2FJn;sXjH-sy_!cP3JaMLnL8nqx`bIw%Nn$yiv9{7V9Wn>^Bqz)&zW`mxJxd{pdhwH+CtVHPNFI#M`%87iOPqsgD9&0oDqWjfhYo2Uq z@KJKX5U4y1Jbo8ugV{Y(j%3=Z^!|pA77X&=Z{v?$S#ui5=)B(58rxNBhz#4XhF^G z9OwRL6yhwzV>aO?*A`Q{i7(}m0_xAO=w&l?Zj2*2D5}m*jH(`c|NPLMfAZ96<=CJ> z*lr6nyw~BN1YY5{7Huu8-lKtAPZ+8P`rNpQG~D@mA0`u`pPa9>8#B)g;7?oE$x=^Q zLW(Ruf$O9pd^NlPPr|!%*n6MBi?iGl0bM1!d!*~OaxTKaxe3r65pP%DDJJ;TD$L)v z0zWIeu#aub`<_4CKKsJZVOgn38X$VW$D%}8{{iK{ciU!0Hj)?6Ri^EITtEW{_VtW1 zbg&kU2>q9V!p4*$b|v%6vxk&GP-r1l)S6cCM8N*4f!#7}C%RNQ+Mn&z9%<-#61Dyw#^>{dXh);adhec4Yk&HYTlaep>- z3%``x;b)1{<9%rX*DKO`F~{?#OnGNIu5yx}}(mPmqqu%jNZ)z??8VJhC92`gJp>*f z!_9SBC0g0DT$A}CL}vvnTL$a1GMSQ@9q|Lvv|!PP?;Wu1($5xZ&eIrJkP*1c-w*~V17HNtcXmox38LIiAe-ROCs+h)E>bLPxNluO(T zv5<<=Y5a3zc=I!rKUfs2ZN7QcWXgk+fzB{&Jol&p`};smeQ`1x(InmD`DW-aI@Q^; z3~8s{dLs6)F0{rYq0~0EH#;Se@-_`CwrI=^+~k^Nh%6I5UEiPi=C;A$wA6;&@+SOR zQ;Su}^Dr#25e@Sbuhs$g$2kp=iIRZ&j_@@S*6WQn25kR8GwPj zwzzkL%f@rrmUg90WbZlN7yZ4aB}oYrl7ODWgbdcArgYuwX$>CxQEf9+&;Rhm$AU}y z8+$L0QGle*<6ht3#yr8v%;C`df#R( zXH&1w7}FxBRSQSTgh;AMoH}P-kKHQz1%@#Ev@paE~?d*h(I!X!3`~UCqW_WLum+eG}!uaiVttXi;(#uhy=e z@HDRIlyu#`BhJ>IVAyCO`n@PFw(#@F&vX2Vhwfe@*+&CzqTO0tyi}P{ zYH6E+-*rNfM*&mmoQ=P7v=ZHdo0S8}K~O!~@|YlEB&};eNe+q=@n%FBA7LQ1lR+aa z*Sn=X{qrFM8Zt|MtU}bP@XOfx7(Z~n7J8W)&p8RE9hxxZ3B?yI8O?0FH(|F)$wTNl zQa<<>QWck;&8hA!Q#}$EB!U*7w80DeVC@EWSm8VV| zv))^dw1n<;k6xf$cM{?S%#7FVywkU!~+=-r1tYi zze8u7=igVlYItS_1WLJLklSK=T!$$X$!5O((?})|Z5iMrwc}qQsMh-OZcb{N3Yjl~ z$4de^lKJwE*#7@ai+9K@b_}*|;+_~eJ6B6_IeDfAkGmy!LFlIRz-J7v6R~(L?pg@c zcO*jkwuSI255%W{Ox4r0OHc=|a!Judg7k(D2Z!Evoipnj8hiD(g@wP$(s%kdz98Zy zt^@VfZI9IM`?^kAv4++wRRGseM#LOzvzgu18GYL8EOcR|>}ZUV)Let44;X&NNF1XR z8J~1~@)&p8oBbZAE40Do8md1{2X-o4XYnA`K@FsOQbjq&az>DarFoo&WIS~LYsM4r zCYt(e^e<0rqu!6L*I#`zO3=2ozRpPwSGnLV1PR%y_TuOJVQjljCEb<8NYV4|lSrHHO#lRBKOG`Z?!x zCpY7JZ?MGvVTnk6YYBCwzQUkpS9e5{5kKOZPz44mfwE}q`uD}{^4>^X!Ud+ecDrOn zQ-dV@@jAzqwGqMSQUOFs_*IYkO-A*4fv;l-gJvd6k#iM#2&xXz+ltBx-B0)9E~#!E zc`ynFEB;a`^FO`~xOa_WOEoaL-tsNkK>`RIR0-(xIJR5|>B z`1-#+_403;oT`GV{~U{c9KUcvPLx&81hnkj7AUO9qm~DAGo+#snW5(C|L1aq(ft%8 zSaB2!rUwtKRhbZ;CpnH>ozv0GPi_S&bl_x9TMAV*(i`m1lk4R|kfU)*fF@EvA^RV3 z3fLrL6`WTD6-T?Z+)U{|?*$UIz{^0Z|$xy=-T&N2AF`L^1uDQ zn-TbO$mQuni?mzpsI_P8-VY-(yIqDSff$yK$+d46@Hv`x{Hpm^}Q ztXLcA2uf9_5O?IV-6+60V;<7vePqUTZc)2O@A&T*kH9^N5$v;}mI`qGw4{z^75Cf4 z;UD@iAD>;ny~xyBGE&UeS@YVumK^z-{kWE|>9r0AQb4oNYJQZ(lNUyUBK&aT-UOD1 zb@+qZ32XIj(>Z{eBCBFMqz&~~WCFj^F0G(AXn_Ar(Lcn|573s}s(hj&WKW*Ikk#L$ z)oGRNY`vT%7nV$gSZo85$U`&Q;2)T0ULuSg?{|>qM!^cYH5ef&FTy3?5QTpnl+bp> ztNCR5abOcwO>pRg4C3+eA1gdlFwPyx$UMELld*h|ltaz8mDfYwXMki#-M+stdd|1d zw)H;yB)}GaL*k)P^*Q4Jwq2&zerobZTMS*vk;JI{iQX?>sLuXILW9=8L7gbrLYkw^ zDaKr!ZA~LtQQ`dYI>MZcaiFeh)cAtVqiFG%m41l=h_S&CbFMH}2AYVM`%958_YW#L z1DZI>mVLA%xO7%?)})GYp!YW~sd7JvQ0`Nr3In59-ItdLuL2ICC2BNb%v>~QwwWQJ zzcWp^ro06r5x=H z&OPJvyzl!z=X^LH&UNv@3vcdy@4eSr`?uEmt+nUu4-7oa+oWIS>3uvp2_hq$)O9b4 zl$r_2t(%Lu(~mX9Zk>;X?6QQAVQ>FZnLA^(XMZ1fOVBQ;l(X!mj5U3dUMK$A@B_8n znEM$m;bM2Q-*WAs5_v11>r->3d2hJ1MIgY3cJ0-STtQPS?5~o*$CtmfN2rwiHF`5U z5RzaKF=?7_?wxVS(F6*1lL6(339n&$`|6|`+lEx?_dTw2hv=*{1%*1D*j-)(+5i7t z6uVK41dVB$jZ{>qyC&{Z<=%mO9}TIdIwKlh$lb#d?i5PgOX0~KE?YP^RlO@9GQUey zm`G})u-boOz0Hac#v(<)*?YQ%zMsZ^gaQv(6%G+$lvzVE@Gsu;eR+aZ^bl%dK&{G& zDGu3QpwZ?R)1n;aB2LQ<3FQo@ScK}<$sx^HOng?oTaor-PQZ*64_j{q#tDpTo|T0o z9w7nf_$;Vp0%9YnKBICb&WXBk;{?20pnB)8sg6zs^g`v`;zC+GD8sXrhQ^N~2igc& zBG)teUJ8VTW86R*GGICq8<_9iU+sTgI6|SYdUso<^8hgyMZ^ebkMses60Hsmn;OPi zKq?&YU%!+P-^bVX)7Q}*YNPG(s3W54m)zfag(w*n21Lk&5scQ!%1EJQJ^gO?fzDKp z(5%Qn&f+-2j;&5hQurWSb95rSbhV;wc5LfZ6wK0vcy93D?jo<{RMFJZZo(N_6uQ?@ z#-5CcZHTssh3^YXbkIaQg^AikITd2;22#~Mte8oICf!QmJaF_YgahGdVEX*>|1RH( z3|Uu%|6RiR`X!SZA^8G1_VAvx(%=(2wZNZM@Zf8T+O{K99ks8tjf*< z1h-YY$U>{n5d;bI34Vots(~_?<|j^{ns-=pu1hR)?ZaiT|Li8>Y4AXzP?Kz8@OJuijt6@$b~q|;@AZpG|!33cgCsqveq@7ZIOuGfi6x8S!iko%>5?- z-=@(14#Io(!T~Wn0&JupB;_g2$T21brX5gv(1NRL#Edq31-KFW$A}T+(8#fl<-^HQ zB#vv56)cN0Ji8?}n4Q)n-aVG_q{_Qqr&Wq+Eeb`iJZivtoTP z6h3})!18z8oc#dLSdfDv3ifcXmExZ(rG$(0UYFc`rjvEw{S0gsoU>LnS*%}a&|mT* zrhL2vlROTI&2w;(^uM(Hw&hFOpH;tUlZDtN*VWNn)SY5LEFsvX4h-Zco z;L9+E#mhPbIuHV|kqoyhrje@%EaZD^V32T%U3Vp*{4g#{`mn%Lnh{xawhCPdL4FHV z`d`&@+G_b<3RyW&R|y3%0ZK)JN?(y8H~Lp16I++y63Cqd4(d3>_nCQa{7ge{@D;Bk zCvl5_Hd+CsY=bK*pyCa9%ogzlkVnZt5)LySfj##PUQ{L;BJw)5-V}t?*SrDyJ0W?V z$v|B60f2S@RDo^qPiZL$ru=^sfS*2o7U*{YsuO^s4+1Me9SSO0K%$-je=<*K%xdmF zsW4W3p?n1dnLtfVU{EBo0CiB!J4)yjy zh&VP9#3a<${Fj1P`WDlR`MYP_-Kid~``7 zGu-hKo-`@in_}8iwsWNJBsNeT4^FJQ?Bnc546=#}p1HHyhr?tKwvKi*H%9{N8s6z#wdCsx z&jKr)tZ0Q`>S$m8Ri?-B6{)(}litHH>AbtHw@0mya7s<9!^QlJ?>+oQZV|<+71+v?1Xnm;yYCNS`{Hly2KGjf>bdBO5~P8ORsH;pSAQByEmmkc~S}Q@E+IdR~hlp zO+|?Kbls1wADqux4VV)>4HYPVgzZQ;6~2L)Vfxb#MHKHsy0XQtt~Z4~#NW++H-W!h z*0iXKjggr@(1AIbySE|49)Bymd`NPE8L(KE?hk7LPaa70YK$#hCpte(M)-S6dZYDM zxr6#OMtfjsldYdjqaTyP4HG!MlI1i2^Hh#)bI1ZhjSHi2Ao#2e0kT<)B4}-zM+W5w zIS39gr{2PR=)AAtL-+)$#L; zq$!1cvhr@7L-X4BI~PQE^9}fC3oc}%&JD8R^D7uIqi0yhI4XYo6)W{KK$YLhwDE!V zw@Ys%dE#rw6zStZ#};?V`WA`!gf|kIT&1(}~DcSY144 z@$g@4+KG66a8Bq~Ia?569dUjg-q_`=2> zr&Bp`2y>US?*=Lh<7wPg+MnAsLHs-hiAqOT zJVY6U~F2olq0(=gpDVo7F0e-v5|ZfbQ`GXt$MRbrzJ6^PMJJ!svZjPf~Q7jQ(&bIF@4d65!b?aZ*T3asS^X85TVlZnk-t2AXyPgu^L z`rYgo^1Ora{(`KN&(E2*G>H~GOH`tMdo`VVCfh2ef1xym+ApBrrTz| z{)=E1OtC7RX0aSU@S~+&C|6C8-pTnRV-sIC*?~PO7K!xjnMkfr=2bp;mBDYxLtR9% z$=muo7c(d{l&2HJ?L!Nix%CO|B2DnPA5Mf(q*Se8Bs9$$4aGTmq+rc%gmWqFc`MA= z7RM9)N7xAY0AZ70HmfAJMiM*8d-KYZM4D)N9)|8fFIDF?;Nhvsh42~$v=ifWCDPZ0 zu_bm&79HOzBNq#6*;rH-&S$x#`$Z zQ$ZAR=OF<1w!QCoqh%lSno5+cB+QzwOuv?q_Bq%@bm&d_TXQpJ0SHuSBjO9)(T*pK zaZ|LCxfRoZXVsqX1ihMrtl7ZM*1Nm*%hRs>63tXUAb$>asfb&D%Xu?S-jVg*fcfmu zJg=8N%Kjv5=Y_^gSyFatlP4rCj4fL$@z$rr$(O<0by{opc4^m74SS)V%yXca;Ct9D zeD*XgzQG`|^Elb*8am<&!!k+q;8Fbf@gKhr?&9Ze9({%KP9NxA<|y~@nH8{aGGfTG zZGIetlZNW?Eb1HXsCLaNHNt-6zo-g`&^V?(o*TF_iP$ldzjbo(KbW$(GO!rSk?DEW zSVCRB-obxc5o^QkQV__!|I<#Owi-+LG0nDwa_KEu3X!_O$G6PpS#N|jl}{L+1WMdD zkbHPlYsZawFC$2VbZ8?H^5NE~=j%&?);d!PpS_Q)gxtMnPbcmJ^nwTQq)*suwk&JZ z|KL-e$kb0vp6@8i;>^5tIIq++CNs`Q{I4FG_Tim0_|ZUIsvQ#Ia*KLLU1vV+7)`rM z_k?p&TP1i^#f{4oq{^-J=W#FfxTR-4V^zPyX)IKJ+j~*dTMOr0oU-SV2iUvhp0XOw zHLAK9rx6z;Ip!N#4h^xFo_Uga(g`WEoqcRA?)+aEDtb+;CjYGbk}4CYqJmDG!@6I& zi*vo?Nl6&Q9u`0?G0TY%VTt%&NSddcQ$TRbv>}*$WyibHrL8L zy)QQ^A)y#7s({8xjOC%AIx@5D5=LM%btY0BxK}(AzBM(t>A-W~lJsGj?r`-{h=PH|(tO=i7a_DnXWmF~I=% zHSWp5&R6-5-wQxiG|PU??ym%tWoeqn?tm5v2#gtjAJfjeU>r!^HX@FH$3n`v^6B|p zjHz~MdlEs~*?w~wPL?e#B5lCmuypwP?&eKx^V0fwol?-=S%q4Al>J*Tz+%Ff*FN^b zc0V*qV0_vgSt@XGsJ_VVJ~}{#%)0z-`H#FhqN{&O5MPj0Mvgq;e?s}yZ>eAlLfa^6 zqimTos)b`s}28Xz412zaLNj!b()}li{UZ)5C|KCT$5EDcM6VwTc`y( ztjgJ;-Svn5+H#*F3=F;UUGBG9>crRJP2zN8u5~GHmr)BtKoeBN3sg};PBmUae*{EZ zdc3Yp#4n!0M*?Y09~nslMmRxAywA_d_!YQ!QRC_>yk@ea*vrIz>a^zLKU`PF_=qyw za0F;jFt>7={X4hBpWW^)oX7*|TlH3(?&Rm_m(qBzv;spT+zsO_d|4o?igfDK5y8C?Ya*`bb(AW!e7wL>h%HCPiz1{E zegOnFfTaa~{_$rrftm?1pm~f9&DT#z#FAk8m&igC0=P_A#}E;1G};E1*tyI>DqC9x zc|;Zk@slr#z`72P2dX;Upg;5mxl&C;(DIwXS>Mw~5TLEYRJ~32kKTp}!cb|TtwuK>R_5=3l+FfSx5aPQ z?&b;F3qH-cvjt6~v;Lcx@9?FT(@ZfYPu@Qmi{Px$#kU`PoWA+wvEQCwV^#2ZkM{&{ zNJYt*YX*x`WBOxV2&!dFI#9>8 zr{~~w6k^}&F0n5$Z>NS*1~l7dr2XfF19-J!&jFw3Tjx+f#S4VjaW%_O_^ZO8#gmH_ z*?tJb)ZK25(fjt};9pJf;EIBb`8XgQln;;HqmT{?4d9@3Dvc&~(KgKg--s{g$f#aq zBD%1Yw(b9vm;DocePWVajm%(3f?BYo=?<42f#_s|p3R#G z-uqiTipkUnvL42m68#}rS*!s~lHKh1ktO^GG+^4{dmoS=sXrhwh2mC)S(3jwd25T7#CoOSJtv_6NI-Ja4n zhSsa&aL|G2(hvxaz-|y zn<%_O?<+aNYpbIFf)NeU2 zA>Mf3%K;hmBjMLTgi0;pK7O-l*Tz>2D2e4vfK%NOp z@OC`N`qj`G9VoauZhdMD|7}uto`VJM3FU&2(~H?p_YnCjr)PvEq38|f9|BghYA+=G zX0BZj3Zws(w1pLWI=6dse$&9h`#N@5^LfnvMda`hO_Y|+L+pCj*%A8xXy!RU|IW^; zzoc7w!-Sk)dB&+U5FP_K_>9SqL3~4TJjK|<9LY6e#I2IPxJ(&1-tvDr7M;8bTQNNT zf$%p>gKcD)P8}2Cw%oaT$QE3HNRcDJjvm>Y9(-}RPv+0mR4gmipd<53$dhHTje8U6 z*54o%H+LAQz|3_48hj1i(QR4AAyqRjFi{2(fq?3QTJH6iy9w{a|BWCle}C}0RSdCb ztD{~JACSl%!)SU$-&5DS7i!5})hs70E?QExUMt?*qk|+-oJ?Em6g?HAoF?gcz>tcp z-Q0}Y*mXfVK#w=o*vjVzg5xd&VXLp9sAz;fB!*VH$=U8wYuLUIN6?n3AsTnHm$Ti} zdr#o_h-Mx-pn!I?6f(^8rA?Hv&F%g@8Y?BnB8wchVRB?!;^PtPfLMn)}4t=@|T;pb-$;B@U@P zk}$G9r+QU-eCysV>060W?VzW->cTBZqQyB^;7ohHvfg;sp>WL%KWH!O1I!8VK0xwX z&aWrwG3{g&Iq4aw_I$5z;sUc&WN`8*g1vp_o>rZB09bGBe?-6u5CP0Pg45baIDjWGi>yt?!AAm!OiRUsbE5x7wiYr(gB~BCKnMV;#40qvk}81&@e7C z3X!5rQKYqeN80mIS(R}Q)YMM~YnKgws{ZNA2{uWDwBK6;=_WhLnZ=e(m-0IO&$a_z zPn{<&0Yeaqurb&$KHMb_Xj%k^erZT;bz^5d{TrGdcvq+2B83Bm3 zDNLO+t7N8X)I%m#Hak?xQbN~AVgsA*K}hs}=jh@L%<;*M!f8<(a*nEE@dFxkkMSbu zFx7TisrMx1&xh6mB7qX%+LTv}!-I1b3U459!Na~Ej#5gv%9Fz3dWG(EkahN> z5ETB^5KV!QJ>@SAN(EE?*okwEawg3M;6V!E>WAVd2wA#Z(aHC%Y3l={M!a- zT!H!R#Z$`4>x?%Hm+Er2#X&B9R=1VU+D4uIvR@u`&J*Vy;_T_TRAA-pyiJm}0BX?A zA8QQZ1e(uvpFK3CP73a$4<%2ZJI*UlSgqLY^8Fq#@m6s*Cw?jM!S$4CJQMw0@O9ye`Kg8E8->Y`4+OpE7tO(8JA`WJ=V99~x^kK7Phu1t&uXJ! z+g6Sf3TP>L)xs1(7hK8E0OKP0JrHjzH@D>Q9h2P{SMwe9k~85HsHkN-^p(hF9jT>oD(vAxCWaddEofzv=}+%p3X|4vB0wKh<<9i|jg)&2 zMM=ao2#%mTFPyluL>iv=ctfQzaj$XXnl^NI@NN0Rc4~a|Evvd>3899PCFigoj2W8g za1z^`&_b=MrUFVG4T{^%Tlx7fugS?jhtAI*iVmPA7qvS=($=}2l z>}$(nb@lBe!!w&+&$Qla__f6q^Ox{nIYHP4x(^ROPaf9(`eq}m-UTb6jc?>g-`bOj zA6UNoRj^}pW%!=<#}`J^8ka}-OMuWPUzNlpuEy*S+}!=_RX{rgIwP(J#aH?b%f}@Q z|1FW%cq_6kXLCs9P^h+ZXv-`QVu|JQR ze1SWVEk3!*4Jvuz;nXf4%?X*gBDIsEtI5Hg;@*tviR>|cnur$jZIA`wi3nKI<}buZ z{!d}!a{kF>BWb|A5mV~v9(|To&HyuDBe_j%$?}b*2vo63VtM5RJs!;_-$30bwSTjG z_hwsZR#7TBx~Y>d}0V%-z7IhFvjP??;$8IR1}nU^tYPfokOV#<31sp&Je;N*c}0nvgYE^Vr%X!M47jD~pH!8QcSwno4xo3e^Fw&HSbYKuT)44<(! z!?`37@I6_nb^hh2-)Bvbz8^@$p#cev{b$R_HT1s zS$d#XC1TDAg-LLHgqJ=n44loG1T}N(;&wkJ1He)f;syz^G=CXBujNFMiPtbcesbD; zjE&9H+4#nZ&lRhG!N65+H!lLLc60m}$+J9i(b^M=RZ}x&Cvb- z0C4YbQ@$8=K^}zyK9r(Q&}vCq9NrH%@bhYt8Jg@JaM$&yGHFHFqkE;{7$(gKDgzXe zLA0y~60{B0haqz`x+x0OgnJlNzmiL#cLvc)^5bYR>4T`uVSHp!;p%j z&GG_mn2{8B#l;6rHu49QG0p$F1OEh)a*>Jk zNq}&|zWYCghX2{yzibmAZPjQ=6j6 z{9Qefk^^#NKnG9|$OKL0(`X58{tLD@oQ;q#O13iX>q};7zll5D@!rR|2^N;4MS(`tuVn$e9Jy5BC?c{=;Q*WWw zlV!{IPEQf71dA56(J%KZigSj{F;|X;=sxyT1)3vOfjeHqE~@P0>mQr%&(-E_;nk6z zW~fzun|p4|St1LxsMpvwoC}Yr{mwJ~#3hRp4)5Kz6L-8(MKc8QhAb7y#nrJO{cZ}g z9+^-tSc{jpXv;kvPjf#NZx*m#-GinKRDbN*y7k$GdAVTXVA=~Ir&A{tSXseqr^JD@ zw8Wi?e1oK&$J`_z2k)iJN3xj=**+!!zm~JX-k!0~Ib0=e5^>_2uZ05>>Gs6{5kZ_t z=CSI$%&&NyRGG0ncSGraN%F5uN87w@C3KS6*MFQPCAa4fkAc5U*gizh@l%(@toKx> zOc@))>o-i@dt%-;Tb_s7@kRycM>Dy=3}iF7>eFf@9c-%-)Rk+6Li(w>K24E)16GH+@}Y*+fbL#_6?#dtkR5ZC)Q35iBLL2SCXK4W zc6O~$%Ep%+%zM){eJyWGw4DUZWFqU-774UQOQqymycgn1bPzfo6pnpn~XZVw_6tgU9hMJ*M8ZNG~}a{ zTZ@2y?R6P$oBtpqUE<}O{U<+PVB$((MY_fSQ{Z{`nGGoqi}_n98<}LIaq7#QP$PKB z>eR&9ZT<@`u5EI!KxZevfx@4*smHkyry9=^mt}0wWY`&Z+gFurgXbT@x2$Y796Xk~ z1v-qc?(fTGn6i!eeIEG8*X`HmIVmU!Tsm}>*t_S7 z>wG5(JIiVJ4bM#Oxs}mTeDT*g>aWrkl4B_mpaAy#5yc0pcJH1EpLd$}VENgKvypaQ zl3bZ-b#H+bm9|l3@tW?b^X&v1)$l9@Z->Hzfu1xwXDql%l-zqW47U-GRH9p^rf1dR zDmga3;`{XIp@3Z9drn${`%_jR-;k9aeX3D2%g(4pYL1;3Q+Iq>MQFWDRr1y9O8q_sNtt5)bX$y$YJ{mYq2R2Apf)Gk%{!1KBhmN# zcnhBxQSBBXpfUSFyyE3|&5gUStFNG{U>5feDDo^EjA-;6jDFVS+NgcR3HkSl)Zamw z?wzTZ=0?@AP3vk6@Z2No3*lgUHWE#O&my155egziR|WM{+`*-<@2~EM;|s0a)*My1 z+OB@MEZLi@@o8&ohiTaG+-xXO&{=Do$X}Hw`&3+!RT@7z7}y{3fkjifNkgb61O%;m z76SCn@6jI11vpNz2~o`UZ3N%Xb&Zt;g{tM8;C>JlATJ-(a00)v2$^*g>`fFxS<16b zO0&s;su5s4TP=@OmkiX)`^3Mres%TuEUu*Dp@9-q@&d=*XZ+#`6@j9Y zXsK^KnRty3&b`l>DyS4%tf-XOEkB_7iRw0$epl3)wP`I7_XnaTu%XvfdknD4sDQ)u z2bBi{Dgw&tK-XV$L95>+m9$P%ZlaX!zxbo(tp%+tgvL=H<&7fQxs%>`m)a2ixfI->j0!DZVSmZ{wFmAC7v9n3nNVx*{~Dh;AjRObs|8=8p6n|CF=93<+7>g^e0gf zaal*{7%=r;Wjj-WjrSXM#1X2RDOmhu1VUF=AY(5%IpDQfMK!fyO@wzWYUMe@@e!=W9SUpQCV zWRtd1K=!B5;v%~~$f32clQLS~H(%Zv>kYPT^_H9OlcFT>chFA+Sg?ETMOM>9g$~U| zrUoMv42GU1urvduAy1P!N~56F1EV0G6`=l8-lUO|u7MmT1LEEw7Fjr?|1u}G0mZZg zM0H0od$6qj6Yx)q@AMbd4Z{#|oLYC%!B^2fkEl+!)ma|LYymg|^0?az6GR@O$thXd z0VZbt7Dij3Twrx&29AIC=SRqA-?ZAyg;y{OkLgF&Lv6-hua`=ctHSDIUKDpW z*r!tAj$@*o2qk0lbhX6rmohE1#N51#fuF&Re|%SEF3m*TUco?1G#}mA4=M3T*=5hw zgO9{V(7omR*>A&Rn^ERh2Gi>KC_Q6pJ=mskEdULy1tewhOc6YK@p~Fjf%fOq zElFIUJ8pPuxSsgZ|y%p4}Jp-q)DoBrnM}xk)#tgDL_{h zYd)J(O?BUr@C#p1S**4tk9`Ks%fz@SbsL`1n_oy#{KmIS<2YmjwD}Y08*%cg56;SfZ+n>hb9^PBkIkO`4$GG$-NZlTY?22B@zmZ(*uMb&~6KK&HkQW zjDWQfR3-;EwdV^FyT<4q`wF^~?u@H>C~yhCqiZz_UACB$8O5C?CML2fK zCBdHj0YsoGCZSnna9-$J?XI$;dawzWzUEnFfrgfzG-RekG>5~5$-4xN=IlWecP`oG zsN#p0WHau7R(?7NP?0jxqkUzDben#d}!r0Am-VTFQudAVp!d+WD=ry=R7xHJ=iOhSo|0g^rpJo zFghVSdP{HZ!_(gZ+I8dMG4JI}ZgVGYLm7G&`gtht=}eYvvA)WwDt$X0%6#DY9BlSQ zbIHhnM-1@|`|?nN;fE~SMR?#2H4$6Fr!Mqz>^QZ=^9Pil!2Fi`VP^o9HyC`*3Tdrg zj5Q2#)sre?!_oKtCc(dOc&Kef^o~HFo^+%rO6j_+IyTWIgREGH_#$wG7BGN{q;S&l zlD&mPK=bAg)E0NFbI}pUlpLC&7L-86l>B{RsK@|iIa9_Ara)#bzfo-BjwoULpb+5@ zdi{g@(h;OT!ZJo7H5Ikebo}Dp<+>TVA<6FIX0jDbCcHL(dRb_4V&E^?Jn%wxrofbC zSCb`s-xu2d0<`qFJAXifHzfpfu&DjC7v?6nkz-@Ya1>0rdl zM>v9$&&R!X-rwsi@~htstEN%AM*7CyXr6KxmGY@nj#WY7FSt9MywBe(#%kNf%LKQu zQ?Mrq>s9~|!)hk<9`_O&M+OylrupX0I$LjOZ3SmET$%}|FHCthT{?j1SWn7B+X!`(5)_|_ zeo1T686cr#V0n_DC~a05%jir4awjdT$=@InWwuD1nfACO5Ww?F$P59)qGJR&&BKCg zA2$GGR~0~-AvTn=`qR!goh zA=0wFB)w#EJOOkWESoks=4b>=Mee`KxRT&82Qz5)+GpQ zd*&_NNy{Y>=LVzDEG|q%@i-i|7b=>tX&W7PT&_{boVaNV*x-fmz0gqzfWPLXO~leJ zkoiw%&^DB%TELS{R$GaYTe1WKTMz60>8pPJbjCJ-AL1Nt1x=nZhrgW6j7~_#dKfII zsRUULcW1M#7ae&1BOL#G6D6|;fX1b-Nh;$dbBbbL*vmB9Tg!%}#&l_ww>^}emILsL z{5kPlLUABM4b4eWB^gI%(g1AZ#jDpb_b(UVc)0e?@nyPUFoh&%dU-BPa@Eh#n+l~A z^_=R*qcsQ}7V9`8~Hbl#5X?XwJW~zAT}g1-X)`9CT=?0LE*)e)Ky-WAl-TB@$oYavrrm< z#G=YNrogu4xh1=g(a@$!h%QZMzeam9>N^Tcm_?-Gn)FVGlqmol-I zC3n*8Xodln8v+1A5`~q|CBUGPYetp&z--{`>O;teaAvK{+?(xWb(jz_ zhvrkhZz>vk5n(#is6z+UA zu&WgB#dArdWTgRyBE@vzy;bZe1adDtk+Y6%fyu_3Df5M<8%wA=>F{WH3sJvfK+d&T zOFwo^SoUlyk&uO;RTX$C07oAfMF!Rh;x05%*HLpcROlB{nM|x(E z5*(;1bs+!tiz0v&Xs{?V1C4Y@k4ba!0kni^4mtpF9p*i1Y zezbgDvZv0XT`8^EgE#yH_N2_R$ajR^w&fLy+T^ndU|hfl)B}q%yD1nG>JO$m3R>aM z&84qFF-b<_BSdqY7Y=dSaS-s>4uBUn9!T&mhc=)B4BlhmGxMC0?fVgo5Yr`UAx2S% zMBcVAGB@dpL(`VYWDkf8fC`&+Zdq4o482!!j8>Px69xcU0G!M#9cSfu#R3p04Qy}c z4m`+`?aP0LcS%s7(YMgQD&{6?;tIqG^d%a}HPZ?0OIQ3I?1=$tMkB&B8F&_ci(@U&d1}t*Ns-F`uY6=ab|aPK+3Zs7&p(<#&9@b*uqo6#&#}yT(U;tXqpd zHGew5=T2a!FWX_$2F#yJl6i3e9OB$oy(n(t)wF95lM$bDVEfxFk&zU%rVg<1t-zj4 z0*k-JW@u-I^vtxCO4y4^73D;}h%lM`scAhDFi8?F7AM@=Dq{-63mS%JzZ3&6GpM~a zrW7~DQV;I-dW?SY?YF!MpF!1O@(R!xLTY;>X3R}A3g+_6c{gT^pER>}vV`HJ{|D)e zX8+LxqP)L4`b-_^Yw*K~$GL@ZXD=KvffK$qV)Vx6J6r}l@p$yA@x z$Ew!?9+cHL@1DYBuyXn0Not;r>7j+Hy9McVk(~#`!U%glx{0!vGrdAj+nIlJP|Y}j ziPnG#Gp&{^V-&0VzL;yI2^i(4=k{tJM=k%Rgvgjt<5eurUS;kogX4#pbf<#BwVrF} zGd-~Afffz&99Ew9-6SNVzUKYZYDKY631(?(w3P;n1eMNN4SmgS?E2*UA_F_I1g)I) z0~aX6W;A)YX^U~9Zk}oF4?6vL3dVbA-rYyp^HQo|M>O~TP1#~hX5K~ayqXXq(S*2P z0(u`eUYs&YC|RM_TZ&wMA;Y34I*TuK(`J4Rw_XOz=tAi=6|Q`{iriWpBe2B}#tjCX z3J8Jo%uUqnq-Wlk=5xsqtaqVuD`ds3y7tVwW7mRyUXAoWug@g7;kRk`oI|tpzE{iI ziB-4FqocG%mZN9({h(gl!@$9*4bEoPAyQyD!aiRwR zkR641fV;G-zH3WQ=^@mTF$4s%+1U~mXXM`q0$YRSMXAg{nYC~hmv9GFt$1bH85&q8 z2Di_%>{{zhd97V48-iyMz-_gzohpGMWX{xTM*(i4$yB(tG7J?In=ehwav2tp?Y8uZ z<&JGL)IW#S5~zIe=&R-%x7vnw6<)=0rNs}VI`Ia7IAN1aM0w$|2eOhrK|Zu-gF!!m2LzFWb$FQ`eizEIjtU2a+l$-EqJ0X&}(krPsb z{jmwIdW6>6r%Xj`OXMjB7o{omSZI5M0swNydc8)NLk45XW(%BMzb@hn3WFLz} zm;mb?AxNcw`SqC2b6m_QoSjE~f>z%!0~JJRUfV3>e%^!o;CO=xNUa5=IQG%wl2`#- zZcDciVNAlKOI1kC2chDO6Hr=AFm83E#hW`T}1f-7vcSfoYQ5-ZKGjhSE(gtGrr&fJ4fXx6XvWFJu&A zac#|b%PiBkXq65+7d9n7C)=|#7saB>+%gsvpQA{n;^u)+3Hi#x(tJh_KRQe(%)mjQ zVVtNJRnXy~_q4GApvv<)qPoO;WEMh~8Dqo4?@c z>*F|i37#V%^#o^6dRygGB)1Nk&2&}A03Op@Y6_>Rc}mQ?q;K!6_fqCPchT~WS0jOQ>pfNrOhStw{{#y-Q-;b*d@;>Cs>q~lc_3HyQ|5ssJ%LjXBx-FNAYm= zm49%|-_$!idG|eIx%47mBaa@AD@RhRdqn3teUnQ9{ z@a0yr%r}W!kKFfhZ7WBGK;vxpl!@*p#V_698eezv$M|=w!JeH0OPKNns8c21?Ap_S z4(*NDcZGM2pO%N|wK8r)$L?d{#$kMvejEJPEpTEoFKG!QcycTfbnHy4U6sEHM8w9b zVdeL1JtwJr4x*MGd5CS}-k6)^#}Z_htHm=}D)%d4-{ZDo12L};x;#R!#tD5Z&>r!N zaNgc&kc94entF+j*yh>yOqiNpMTMp4)cl91W|%^k#V@})$2aOp?*mk_; z_$|$TeEV@<;OoWnN-iBA%T~k3;885Vjqlm?g3~!xnh^0w_eA@M%}@Z3X90NVJY>pg zms(9St9bvp8hJZbKAz@nMQ}_eifC<^F(&^fPh+4V4=D2A|~4QiMwhFb)*f z$QzkQe|OhT@YL3Jq>&ox!Hq3CxM z<1KSq4BLqIeZ~8Y%CVei}dfyZ^L|&~d4CR?8eBPbdC$hdA@^`xsyz`Itu53u#GFm8HGVch$<~ z6u6mrmIv{HvV$05Gxz%ag=z9r#1nkFxR35#Gudg7l+x)*oW{2Wdv z=;@lNm9A%$Ui$E~8kWTpH{Jw|vXPB6+x{roF0L4S^gm7XOEN0 zqxR2+Pd5t|;i-mit(=kF3GIU#1`Uz533pC@0PDPQ9yh zbam+^h)F`{Kz?QC+wlABpu6wH)6VU}`shE(n5@(*!!T$tmt4$Mev?X=ZO6B_2s}W0 zyCGrbHf+3{;U?R-2L|)V#s+r&3~Gfr`oCvHVJMH>Xxw>lmFbwGo;_H<7JJ{8G)&QA zLd0gPRp32$qlOf)w;$M^DOEign!c$Yva91RfAkr({o!2ae7^F{>(;YAx`62H-Rv4L z@hH^|@e|vdw6z$5j)xUZk6_dkGe@R!KE8H9+{~>ZRC5?7(Cni{JE6hS@gq#B@3MQ>;{;1ivy(%TW_eOh9(0Y)Fb6kL zkaJsCpXd-UXkJ1l`dq(wcI{o?>-Z2F(l-622B2ekw;GxcY=$j|U5B}ii?YI^2gTHL z(B$SD!7PKNiA{OdzUnsI%e^tw<3J(DQqQ*#=HA)e?IlkV`2uFsMCkskdemmHqJ3U8 zVO%_ox0MI$(cM##`I2@CCpWrAd~}r`VmLzW(zH$KCCGq=7>K&O7~&B}utb&kJV9L{ zni2^5sii#hTsF;!N>+@=E40GN*#lB0#Gl01+N(N_M7Jp5HdJ#_6y_3g-L{Y8skLt`O1Oer z1T6RCjNdp4N%B7Nw-gpM|F#f0Rw!MI#C)Wo+E$*ECX@~K@hOEG!Fz4BsMq{ZwCtY& z&Bxt`^)H1+rr4m|~#YU7qxv~>pb=&{i9##8qa*S>o4O^-p{I=q*9mtCi0 za{ZL>OPv*+gk)JY@X6|4j0b|sv96jwtTD&S^F>E>m9mv_o|}Q>f6rfU+H2u;>KrKg z2d$F&|8>oa)EQ^Y4|A{ZGmT`RP7o{`5gO7Gr^*OQMtv0K{vS0FY(-#e(o3|ZKw^eH z>bqcmA!t|UbYs$ayy*V(?D6mp$l|X8zB_(YlgMh7X?mpuI_RWwk(Wn^J4kK1ZDD@?WAl_V_lQ@Hh*jq*ZP zUHyArPSWcX|F_BQUwfYt%YNT^?mzMo!)*BfYVXR!q5R&yEMHWpY$;2Uoly!gVU(?G z+4n7j82g$%R3dB4%vaW5kz^k+j2Y@%wh^+8WekZS+h7=C48M`y_j=!d-}m3&^T)ZK z>s-(Eoa@}reeU~n?)%)IM~O*KKW#$)E3wh=!bVD}@P2Ln*64C+Z?pQOvQ?FR%Y)q0 z=y|#F@$XX2OY!i0638`Y|LhcNCmw%>8*Ff$@suUlNTzpE&(f&9z)J^DC|#bPsp0Y!=)cZPSxLmqkIS3hLkPghY^ zC`|iy?RQ9XOvh!k2I1)L;q^yIzc)a1V<0CEV*N6h;#T*+`akN@gEu~vO0eAD==lHm zaHbVs<&Rp*c$|}jx$azPk5A~lGckoq&@YO(E7oh64a#irTq8qE=3UcDwKlounyv!^ z0KlO>At3nER)!SjeVZ$YeTJOA2-%FVhh(s|= zU1Y(!lP)VW>h{Q2Z)nNAG&?io-QaS3ZSD>40SWSyM|VF7yb>94kPbHP`K7uz;q@XW znc;AwQv|WT*-fT=U?)?y*bxU*Uc^Rc1Huh&C2fmJT`{jq*)MluDkppo@Cpclcttuy zN!zzRc#ZeyUHG(TRMvMgG6wh9MeE347C&bLmogzjdn9tVr!onKh1;JXgR|T5RY-0L+tjp=|T#;r_>;+1JUrehgO)m$g0*$oQH( z;sB%%>gnGqRlRw(zwqf$XU}7j?~j<3FB>dF9oUbp1;m+(ei(=5!S^RdH~!&ZJbylN z>lJ|f)rkK~s0O)ytEbMthrlmbG%ZapQa%#kM{Y)2RVGEhpT$xKGD^$c85JTC~Wu`RM$>_SK+yqKckR z8uYyqeJPlO@A6i_%N-prQ9z|1n#I(^MHjC}J`hz*CuJ$1+Tx-= zELVgxEguy@^KNe2SqvJD)5so-f~VV)Vh2gnAQ|tAd!s?Q1w?Z@aTSJ5Ept;V!tMRU6Vwe$r?6?vnnXxyok#t3AS!uB0 zv!@=rFu!PU{ZfW`}eOmxdkjqH5mFt~YpeZ0Tm<^6I&-ZPZ5!e%-26^P_6$e|F ziCOUTI@c9l$2P4xK_&KqBi=Zpox#c`Qk5hr6*ikwsB*Vw2vy<$V4ilS)L5If_BEbb zM;A@bc08<0(hP(Hy7At50&+D>%(a4Bt027kksGqbr+e8xv&~u-7AeFvp#Ov)%Av*@r9?! z3jju@*;+uAijB2`YH~kS*AsxsU?AhXg%KsPZ!sne6_5d1#_2d~)guF1^vz%}BkC46ghImx2}7a~{v0(SS8WvEb#Q zotu(*Twn7P0e0xNe7wPSxl4a6L+%fhNqbBwB?7M77CV)ioGWv(ZPbm?*`(l1gYb!k zs5SE`w+lB4ey{N2S~hSR6Wzid>LgaWI^+dMl04j&gCn)4`*VM_Zc5)n`qC_Mu`MMky_vG{EwDV6_-ks=C?v5tB`>HQS=t+Dj82_xZF|DvEz+=iuwA-<_UcdM| zb8UBV*0atJ(!V$&SbvAfjwq^ZvsoYrQlM)|ST>*88&XktMe$Mt7Zjx@tkB~T!>6jT zOAsBbTj0Q1Kac^+Sl8h7NAGiG-y7llfF(7^!gPD=%LcNnpz7IA+W~{Ru?Mc95-(oj z!pq1$v!{HFJo73GlqYsRhesG_Cic78aXl&J7q@SNz3O~GXGX{b=^o8jQM7hPFKIKJ zrKt0q*EB^jLSyuVKv{0QLjdT3Z|aV4%j592)oF6soeLd6aY%;$X`gr2b>wQB!b*7` zKHka%QiTin4XFYo8vjG$j86-%%xclrsN~WI{LfTz-&-cfR$E{Hk}S+czCgtsCx0km zHumzuPT)%UjKIrub>d{#LMBC)ulr^5^>Y_~V%f)7(9R2DL}FBR1K`wHy087~k=k(8 zYEhHzT)B(Sq2qOFwzV&b9h)aJ81Q~Gb#nzr*7-3rnGM{2y6cIng0{gh`70?Qq{20U zK|fl<{Yy3GwnXdh>tRTw&saH5@P*2`UsSrY(NS5qYXHYbYmkHlb|$z{dF#dkhkMy{ za1^!}xqVV{^bW>m9u=C z-$-y|VzXEAPgz-<$ty8$OZIpi?WP}f1X?b^Kpmc-Gd{FMpn_h{2np>}=RUa_vmWe_ zwCtFJxd%^Pkw|q!v>eXT<0G^Pv$vD2XU!!syZQS&JtkGxS{^``1`vLG5f~+6Ux=2< zAwpJM32XW<@`+pkXWR50MqWx>W|qYSKy9^dNmS$2v86bZ1nLmV%%x+wzGd)&K`TJ0dPj@1(%u<>CVqNX?l#qhmj~ikHNO|5jdDlwR=7x~Z))geEDeVxlKCSIiJh8B#%syYo@Yo$3{y{^FGoj5PavlWIe zWHyudR^r}*ZUp+~f4C)oOGANpag*_Qpc-~?$X|0w&>nC%oCQD4FgmNDq2DeOAyA@R zg_M+s49(){V-BwKgO}&iG%T#+eYE#kZ3MiAUmL}fI$zwqB*bWx-h)SalJN>yk2Hff zM;qItMWDqF9zKz~wf#K-rWq9DN+`R3()@|at~(9&lVAXoaeOju(S+34dTwR*#wokY zDBL@M2JAmAHK`D=?&q%=8vca0#HaNA;QHTfHy?@{VZ#_7wy+9M+%WrE0E~sJh%Ccb zbv$;DRaEX$<~3Gkfr1W}?SrQ6)#CN9zfDq3kK@m;bdWHlwR zu6;h3x}CvC`OJSHetqycVI1Y2eJiv_yz%e+FjYzPE;==KcY0vCMXCjZT`Ab6JrZT- zc>;n%)>51_X41;sD`o!6fuFp6$&IrMT|Nu$F=rj#1vgC;fpQW6@Q$j9MZg+I$!+nK1IOF7Oq&zD2A9)`Ii`15*iSl~ zy=gIKJ~P^T5ljd-a9`Y4a3__C^z8TRu6aCY_f{gpw8T`YGxyrATs90@Qf5XW}N}|H1Yl7?qdHc&57+5OqYHL~SV25?e zJ!rR8skg3bZ)>g>8}h2IuC@P_9nL{B6mHwNoV(NA)fLm|yLVR?vkuaBgoZ3N*;4C? zgTSIJmmeqaze?P~)$&@$JK8(QFzJ?zNeyrUIy1P`Fv`CRz z{S(A|tUVw783k+;l!%F=p*IdQ*me^yiipL%$6uMATh!w+` zd4I)RsZtB8$6*mfS*(!>MhvqSWxHJ!8*ne|hR?W`-$li1r4S!rvuj26++NufoDMVm z8a7=ohQm~@owzA#J*8=LrpCJdlWDR7TYmgFdebi&BrJfr&#AeQlT%IXK919n&USU3 z?ixFsTcr)O+OIL1wo_^*LoMfoo7V2~J31Cq-G|!ga3Y%q!NDSpGP34Zfd=);Wv9aQ zk&gFrSZZb8RKM$?uD=fuTYCg6Ca8CasiZ`Q*MX&US`jHt3GcN*fj==HRo`4w{c%D2)@ z5A3A$Ecjq}1EC8y@Y`Et-HloHkhVls=8O_;SaH>ol)NG-eNn>qpWLB~4|O|Q@~c=c z{7yM3b9X&wB7dYt0B`a0I8isLY{L>WirJ10Pn(J)IzykogZzL9Q6C@`!sbT-TwXz` zAfR(BB(}_U*H&v8Sv+dbp7!*Jbs?UIEkIv}#pV{B805Ey{L>b@&{N1fsUkJU)C_;h zlIV3y@$@rHJT4o@Nxa{S@^>~b;cPLJ+0|Gyy;%N#zUC4OqZiA++}z63G;2&|AS>#j z4SuaGwi~^I5&oGiQI=nD-{`IrahaY&sUYoV&p15Y_nps4sD$8PHP+-8p!C}kGNxz1JL$rj0gJ(rU0k0U7J3GU{ z(Zb0~OT6)b-^oJNCX%J>*FQYNx#dBc2v5$)o2M1jsld$ zsLJ1Sdgd|^`4&@>5lWs*U4Ev?C6p6-4*gnNv6LlVIXJ7qlFxJ7deTtxh%0zFa$(wd zC^LGTv-PpS*dcEN>zxkQAQL5o+F#OOOP)6laQY57&D#TftY`lp_T_dysw*i3Ip_|7s@8q6tr6xb9j+n=Ch}eNJ$sW<^6^KuE2R-ari*Oxo{r0g zPhPzu7kWY)jE^eMDFxrHOz22BTPRah7rB4!yWd7X6`dqI8(s{?6Y(VfekV&!?jno8 zNzNXP((aRl*;HjM~XxdlVv=W97d<(S&p7ptlLWboBH+7i(-W~S+SnG zFlEHy_y7v@IMTly^Vr{GDF!d?$!|thWd`3sTiS?sR;_JsE!$grq=u2E2j8-d+xB@4k78393ecQz+xve1M{>?GtU zZijii_D+ne+`{fim7ZWt{sV`R(Crd6?~@xVf3Gz32o|wXd6&_A_h}x5dDcbh?f@al%L4MY96W{`!2<>j8p2m?O{P$iyq%OsN^Z=`S;vQkI-ct>nY`=B`(jp#h1 z{XF<%aq99mvXji0k8F>bn1miLMmvh)3wG9}MGWz(4Mv=EHYeYU2H&dmPB0aetRXFh zoieY=(ug>A)j6*m&k+&V)u*6@v#rFr%g!Dtjc;_XeG9Sqg`fmIZuqpZ*i$TDjMsB) zaNDT2MbT_WbLD}d-$i2pmB{W;TELT)j3K|b&d+#bvz^56!oyfvhBv$>Kcp`6@$5zJ zu)K(l-v~C$5#f-aZ6JE&nRNAy6wVda7#0IVZwvV-n%C3B9ox|BWB1X8wJ^F`F}4}4 zKS+(#jmk;hawQquRJ@Pzo|06Hp`$tzi$wb)@)(K)e_`@ts7OfSyIHn2PuF|7b(Vxt z{u9m@(Sl9rWtw=;Y#;@1Zyl$5-7mWcikI=6#%Q6DL$ywOdc*C~8sRBf;zCy3W139A zE_&M8@M9N+)DHUuCFk5p_$lc~p7{ywYi5_Si*1I&as(X0Hqf+m5{c!s{}|7*CpsSU z9?Y+vsIp*Pvlca_JW~;*5%w5)yPuqd$aHS|El)E_A|zG{V`iuOZNJvuy_vgUG5iboXF%KgJ@lpAfzx`kn)a3Tf95X8 zBo%25-4LSD2bg*qdzNT|N>4`U<}>*70};pu28*1sTFcLVd^+j#)q`!!A0*UV-*|K$ zBVE&YGLJq&uVR7|n+fZv2r>>|*||URdrcJdDFx-oti{eE!HH$?-3{0CCr^IhcXJo! z&pFQV|MW_VssGUgjgp_I7W1=W;0P}|p{2PrGJbvmQMYRLJ*hlhnB)zM-rQ=O?R}HT zI8poadMlRF8A|x+MqKn0)34M}DJm9v4{6}r)bOsSIb(-x+cuwXJNeIYz7#?A? z(-+8|YP?a)ij>NhX@2r+LIf|aUJgwLF&1D9k*xTm+d>zL$SYl|HizijLUz_o&DTh) zw~PlOx%t+sHjPKyGHMl0u~^m?6?x~a^Ce8vIiNpwHbx2MSD$Y)t+MGEbVx`HGHPb zYX#0e;ge4d^6tn@8uBq*V|Cvx)=y=x)DMWtAj8h|lK~oYr!M$j#<#r2sJ{D7orKth zjRoPzkMJT}^)>$yzd^&PWH>iJOD%>kAAWGad2U?J;MwM;pl>SPZEA1{|Gs=Z!~55H0rK$-u|73Ij^fpJV&i2yI)i?(#_)>6djZ&$_a}B#X@L~jWufVg$$fo1-11iHU{m*dsF$FVmfQF}Rl6>oaj ztWppu%Q=sobT(8a?mvGodZnBodWAK zXO$X19eSu9lP7Ym{ies{nDoadB$Pg7+Vg7?Ipo)*ivx@WCmt&s{1i{BsV?}0GoHWx z@5BuMJMld-`n8JZWbmFa0SK|UA(U1oIl@1Cx`r(Q*Sjqe^Pz4wepR8-mkfv^hu*#k zeU+Jdr_I82C#C`PjwlTw2h;ZsTJ~hllQlpYf;+DenXpU=F+?CwG|Ki1;UY!Y$bIvvA5XF zNH3~Uob`;ZGCL{Af!j<0uxfVOUgNXSOS>B-%Pwz(UkY1y z1}1y)|KBX<|LZQ8^^nl$t}KSL?Ffm9C$AP-yfqGS$&D%71)vdjOLGm*6(d^slG5g- zDVp|H=yay1-OOU&$a&ul@bmKa7wLRm&pHF8Qm5D&d3{4e7ILztrsnWbgtvzW)y`bQ zarNc#hKf(M#(u3^6qO|CHt7#WC;V2-un=Cum1CyLdB z{^WUpFI-Ct+Wcb0QI|MunvEr~j% zMI;qfu*G5vISn{qe zF01+aW2bvNJ2!44P>IaDilW8xoE)Q)goFg$ewoSGlWN^s>xPAeh31K=gFjv4C0dmg zzntW!2we>dm9yLtQLvUix8B^|8ojpF5x2dkE;FN#$ir%2f zP2+F1QJdf8VFwQ*<9d8X#%!I9T&$l_)>L+OcEjv9I|kpYlRv^O)6>(>R4erA-m_?A z*VotII4H)n-5mAzjEty`HM!e&eiu71U&hUTv=r;t?6O6$1rQ&?FmEuEBUs_<<0EHj zx!D9kL?!7B6I?FbxbI71Q>{?Bzq{rtbuy@Tpj#BnU=VK7tk6%}U0aix4i38MI}&?0 zkm}>^*<<8)xpo@QARFF%z3pPu;AB2Ck}E~LE}L{RFgSR8&5Vc`bhGlOE37w>Mg6D$ z{Vmfm+Ah&vsh57;dxN8&@eE0k7I!z7mw!^JfjP!-sGf_bUTOVUygL26A4?;at#OyA zG(0)UPnpd%nDLCs&CTud6m|atzx4#|U^>70-)gtr8T&_6R1*aDyEC=NEAj4Sy0zO) zjjn$QJkKY$U;VlQUAHU|rdpg47{3DurADqrp36r|$jIk?Id;8%RQljI5XBH+VhYhciSTj>FXTn25elU5^70h6!$X1Mhx_ZLEkau&} zJ2DcNnx5`-XJKr7@OyeX75G29aN#Ms_ECo_eY{WT6(R(s6KhC9HdD~`)vmwu`hdJ) zwQ0!AN>b7kGA7C6+ru83=6rte`Z#x)W`%eVS0F*e<9iz{pXiNkTy{%Z&icq4>dLx0 zcM9#1|MbRjvE8kqvw0YZj2(?rM_cc4<-p8^|%)f z37^*$P&YF6cUrB(@s-o_bN`OFx?I=i`@>cZe%HHoH(>>}wXUt=D3zb_GRa7>b_2b&X#cW~kGex$sKv8tV9Tte&<_O)#w6xyK#p<^`dx1$Yl{N3@ z>N^y4@$vD|d3bn$8oW+6nN;1g1YPNk9RrW^WMiGi06Eg)bqi8K=68-qR<^b~lNT2r zRu;+?!#QGYBK+8e-_0QaYMUmqX`K0z-Al#eYsF6ejjJc+Ncr>LlilG`jO)_-(#bjnka z?fw2jiw{xM_wMeyy0<+zhGVbfgQna=Tsp_it>+(`^mInH!*=ii0FgQ`2SsRK)h4w$;_u=dFV4J+U;4OKjy#D%o?9A3m%S(LEU% z2`mtNR|72`!sD?3{#OLxS~*<>Y>gioNx>{AW=G}*SzQX*k6q?~Q) zmmqj=IsT8kODd62622d<9jlzWBdMydJ8*SJ-UMjj5y3uR8Z&(xYZQnil^F!SiI%(4 zni;=;1QZ)a!gq@I5?rQwxcngT1`u1-1j|xFJv?kwsRqi^nLk3PJ3_Q<;zGz_`GK3| zB$$LR#Di>J8k2%S5kn*wS&5Fi`x%goEJ;81IjRyTMfWoY9Eu((s#;enAP(7m_o^3= zL7(q~Jrp^bhqZwk9F!D$h0;>u?T1$tvNh0NnXU_zoL_?aKjIy0i4K`XzTSEGj|hnSpI-Bb!a-#9x-!oKZ*=5ecs2=HTOu%(QN3|3{%B2vy$|s zjcB>9PeF+DNTNOA8@U>&+T&@O+WtVs>sxU>+1Om6bBP4T&Kq4_XvfP* zpPQ9;Gs#1d32N)z280i%JMs~ep&@En$9@zt0C zBz$hoW|u%-cGjIaU@9TQFFq?&d^&HY8~#u|T>B-AIV1a#qBi-%p{+zFdvrvrw&aI$ zn}i&cAv)B(=6DUHwD&$km@B*-z<6cvU&f3fD0#D6`o~+ORh#u+Q0GVLTn-b&r;=%w zDeGS;GMUsh%#MNM=J7WTbIA|<+WKB8DHs% z?KwQng#&MgY%IfESbg$2=FGL3lP%<#W4V?5hsUO$?5$POU1$^p4Ei5)nJXeB%ff*Z z!|b;K<*|6On?SK_X=j(kgolRKjOBQX3vNRAiyd>u`y<{=)^udBbdilBm7dFE>bCj> zfm-%(ZDv|QMzOf9jj!?@UT7GY!-X1A!!>L)jwKGS z4nKlyuxSk1=u7$KLsc#l%q z@aJZ|>D#xJ-90^|kujy1HBd*HZ?uNM({Z%(*e!Et;thOD@$he|u&I8=w)inHgksZ+ zq|f3buB@Bg6rIUvT7CR~s)oQ4cuqRj^*`sP!BR>&AZasAxx!xP*JmhFA8}%`o93y;yq-IY>1Fb$H@192Fe%I6z5AGKGh&;6= zwo~``ZkY4Cu1exP@rTwAoaM|R%D~TIfdL{2L(h-)6)e;}L*K|f1TnJYIw;JMVr1@p zY1Q6RMO27~X@?&ipTriLXpJ?QK#pQd^gavqg!rlnc!q3ykNJU`Xn{}>w+wr1MDLa)NbY3X*SK9f}6fg`$peM=fJT!{vNN$c4cADbBDU5*#q z>Jgi0F5#ymgsbH)@5JWmG1DWDl`a*LD0D~i;lH%K#H8?k_XZ7iv}pQIH}NW^Bso?r zHBjG1ne}J*gb>x7diz24Bl@mn#vDx!(Q!#kirM6Y`L-JIaT3gQHndk6lOXf`Pr0@ZtaDlLN4-d7y7cEd>EQbS?|F( z&9qbkM#ulWj!|Qjo%8d9A-;3^$9*3*lAsdxc^>@*8Xs%XE2sB}9#4}NF_xQ*bRh7F z$J+7yGg?~OATCJvufUf-Nk96&rA(Xq`ofVLeh3S2)u%%n zT1c=|n)T_9zdP`ZF7QH!#f$DzGP}Ym%z5Nf-pbkDyj~6c3kM0z^dnUk2mt3b^4D~i zFgHn-1lS~H77!nY=JKdFwJ4Sd<8$o8i7+`VUp_nT@z$n9iv)Voa6gk|Q8 ztVvp0`te+|XF|2VhH$osw?NErO@%>I!7DfNu-o%R|4O4)pETGt!&aY5iX1B$8JXUp zz3Z;;n0^yZMslrjC(&8tm^GjUHW2bYE8D4=*zr1EA0%rXi;Rkj67f9BPgkH7Y6PIR zxN2?AyhXIX*hbT%r5h0uQTNBBV@>Q8R0H&DbsALa>gqJXAt8=BW%iDR1qHC8&+mK! zzfrARWbj>UWZpt7o4gYQxqPH){~2aF`QeKaKTL|8{noeLA3Qr##DmUlyZ2j-vJ(d9 zKZL776>R;n8H$ll4_X=*;;R;rrKrr&_<)I2GM`t2j^@Dip`+vD;pru5Zwn+u;Oh24 zECCMQ#xEI{#7F+HJ{o@3phc_1@6=s^A^qAh4W-D zqb18@by%WQ3ZatcVw4+MXDL<%a|Sg0>X!tTW$c!VFC~A+;aiybXkWP`41^Z5bE7JS zic4Wq^uO%+CG-Hb-QV0IucpW_br)O-w6#v`Ui*w@`q9S2yGf_yf_Lhw1=<>bfJvTq zzfT0e&$u)8Wgl8iF7tAPknkN<8xzaY@oYE7=fA29J-hQaOAO+~-<-zt*Wr?UUG;D{ zfT;Pb=|4XILnHBt0Hhzj#Kta|Gfm+pm!;BwQ?Y4LowNf?Hh1cEb3RhW$Lp2j-V5FFPu??vvuT~7GyVR6R(%|d5RG^ABA(r@STQ3T%zUiftv@A2NV)AcUh-Q9?ju2G*ZS;;F9FgUs zYLQQ8q2)4f`7#m(-M)NhkffMRNjlTXRXQ9o;yRU7?MW{rR+-Vr#iz1iI1M%Me>7PQOKQ z^P;26z0zvn4Qcs%ds3`j-kBBT#T$2&mm=*cCw6)XZ}9Z+>cmXtjtVNii-Vf0+f^?1 z&)eRs(nWI$3_f#R>L_j>f!9Qq?5r4dX zeMv{hPpe^cUO%DlrX887WD{JKE%9L<$q!(5j6hIAw?vjug|ZAQ3qF1@P$a3P_%efh zw60XQ2OrZU8##UiC}_*9;{^;Q%oca)XSEdauH6P7kcu0dz&XZNUqJU?cTu!sQUI?-(m8tRfEsX zev}vYpz{wU(z#4Zgq9e*{mYCPd?Zz?9x|{vLbM>V#G>UndGH{h9xTcL1bgNYqIQ;b z5{w#Cxa#J?jV3M(zT_l^F7N98ofUezr$yBcl8|nMWU5N$k<|c`KOe54)d5d>*g#tZ z3rx@!A+x_QRD<@ycXg6;Sh5?l97JA2SpvEa07j4nzFxv!_)5MXR6tA3SO*{{_#=kj z#Z6=Yf>KB%D_SIil4~gQ$Qz6B|JM&_JWz4ONH);-_pVK#sF$fkUcd%;xOo}OBV;D8H_?&P zC51oOe-eLaZ;33OM!8!-lL&h0_v|jQ%p;q8i9)iF0;*?T%tV$h6n$BF;_5M!B19N` zg4kw-7H8bpj5 z$>)iU;*bKOZ;}vIC~;@*1TqHS=PF|KV1#lsf?qhyBiamX1aQa{7&Y|Kff0xpe2j@i z*AkF|RNRCnW*BF%BRh+nuF4!Nf1i055*pe{CgjFmXr*e7hK)TKS_8< zTHS)OK{x4FGKU^oB4!McRz*zv`<>}(8iDt7MH-;crxbGA$pC!sP(QLOuiMV_0Kd&I zOBvk}CUhhOQ!}$f|G63~Lcl67-`IDBV)w?!$7dEInnP;n>FGV>ZTgcrA^-b68>#o_qz-uCuYiBuR)bN*z3 zQiiSQxbeio>4`m4*r)eq!>0fY=3O~;suLQ#kYATaqIxn%_9}P`S*Oa8Bq(kG7 z=BB2m4M|-Q@3U3mmc99=4GCR9kVk2LMjrNMCY>7gFXA%ZSYAFV_CDXsfqw&*bC_8Qb2hTL|~xT0D?RL#@pUmn46!D4h_Y?DAnZTWKW;Z8Vycs zxZl2gYfkK+p1vRy)>?Y;QpCn>e?jRfFYn}?J0Rfh#Z{g0>s&H{zN3U(w~4r{ZjXn> zGH|JdH*@8nb}&s)?@d>)`~+OHJt*BCQ2?6G)s?1DG3}_$@Ahi`wR`iIWniI|dC&l6 zKYD5=M>V%`^xWoGnK~E?xNmJabl@(#ySuAse=(DP3}aNYcrAwHOY~jbb(()Kq*UO# zyPxYFZw!ku?GF7oy}3MAaoL?YZ58ZO({J!T+wtUOX5L&}zA0Y011w!^V^b4T$yfcO z3vVy4J5n+-548nf*Tzci>SfD~p)Akh%Dda^#nh+feNS8`WF1crHgf!_w}5m(%Qrj? zT(Qq?{QkhR5=4l0si~;!6MknJZu_$5Vwjto8|Hs^X;GUPMP12`gNTaL%mcVqHa|bV z>Mwhwq}hYFVA4Y!;$%1bZ5J)W;Z5t^-Sxf$TF>g{$_nB6^?qAV`*CW7f5Bl41wSjw zavsf8rICNL^Tv?8Lw=LOwR%r&TigBVBA7W~XmE0l&(=Gx@N7Q&Rbk*As3e#nV0VRj z^YsM^5|Tb8kLmZMMZar~7^UZ)TOVk|L@Acz-Cf+>+YtAk9qpBl`+g6{9|b|vl*=qu zl5YXVbqc3S4ph(H&n3A2{gu@W!t|X^4xmf0!Bi_sLHs*AB+RMzw>2&xzjAu#us(po4ax1>CO4FCH~yo`}&!xg=zH#@975? zqt+W)DXA`8Mn=Z{Nl!j*?h81_QU*g9sejWeBq@l3fm&w=j@Z&c)5Xm zioEs_HuD3qI9FJ4_3_8-;2_6qBO|fP{iG zMSK`v#tXm&65Yqae}lJ0Odmn{Zh9!|K}sAU?P)9$Aclz<?k!PWd2Jpa3;EwXtbIaFz`$7$sPVQC0M-7}r%yldvwOd?CJ(UI|7G5%Bk-FG1-cViNO`Ho#qb4qaO>f zH6eLewJk#r2#4oE?D~dIx27WtHGzM>(5!>oHx7={e^9O1^`-EQ8vxmj?bTJ6*t;@_ zSE*+C%x$Sw^?A+L#Cjl9U zKA@7+SVk(IF$xc?AOR)33T;nf(@O8)rX@S4p`h57MH^*H%QP+l%ObABDMm(Y>P1+vt+oas*}4rZVreD+xOvzgnvP1k_CfR{Y3L-c_pP0d3M31iGTIS&hBmtF?;CIo=wyB;Yv?a0Cy=E70&@M7SZ^Y+KYgaQ2-$b zC6-am@1+lZ|8Uwp^!6^Q*5*KNy%w)pF$~_lw6_@GLZUDj{^H!cZt*&m<|asFj7Q;G zqh$wtl&DcUJ$ttn#l}>JGwRDpWK`7HWKP0ciqHkAEUWQA=98mdAu+cdZHrV3v`(xU z4yMiz+4W!yz6nxFVOJ;J|4joN5HO<-1yk2BTRS_Nu;5@MJjus%1>w~UFdOkj!IjBj zE)M|9Dp=uLRq4J5v+d!Z*Qo-g-^2Ny#v`N)BO6 zmYMeqYzUzpFn+$*>U-_w#5que0N}O-e#a3m8cl}c-(D3h{2to(o}Q_&!9hD)+w9=r z;18eHxg1jHyFb93a6}Q|;10cWY0Aidp2y>0NhzIk%s7UmxvYzs&r`biDMU44LpEVf z!XXgNM3B@00C-zRkBky)P~$NHKK>~D`toGC-ZN^=4R-t}6HtwCwf8ZoJ}0u;t}y-Z z5cmg4aA>@#WVpv9#$@Oxbw5#HZlU@MJ%Aey0vGr)L55<721{x*N7&O;Pd`h|JpH-j zvgtw@=MVE&-M1l_q{<<5QQS0GPi z8qec(DvjA65H>paWMt1{ z8##A-#tT*I!gi63HHHAHopRturSVCVN$C~8{!9w)Ua34U75_56o#A0s_635Yqob?I zXMMbPRolele;E2chvq)O(B}*|tnt0z2&*~rs>v+Yul1=+_JckLh zI+(JaH8wQJboRyCb~*o{3AN~?XA--=aSr~Jn;tpV>b#+>nGnFFCvGX?Bs5H`htJ9;-hmYD%12}MVVFSmtuk{^}f)gO`BnVBmV{?A4E)|Ufm zympTsJxaK_x%pMxSnPIvwyOduVk!Po^pVp6&IBbRYJaZbYg;!uZ;3;1Z!b>OIpk!7 zlE{jPPM97R1Vn`OVA#p)a~k67K*D7xtd@k@xSV*v`JlTd^h`1T)}(}laS=Si1Ny-Y zG^8r8j?nRu6fi5DS1`yge%cYp?4vgh%PA&56jP;8*$7MW6szWhwS^|}NsSz(U%qT? zXl%?b1$#@6QkOqhcYeS@8pr}8;}{ z20x8{B;?ir3J^x^$!YeXTt#YQG=_nkuT@oY=clIzl{;}Sb*u;_rQ0z=kNX=d<==mP zovH?B;%+;gAJbSsqn1Bvwe;fQ{IvDH*KXMM_WO3oowlqPytc#-h;~X?@4j!SK^DnK z5+&BzNm6z2QBWNs^HltMJD#-m(f#&hsaO%OpN?Y(MmU3Pj6QNH9Vq#3(7Ra7FV%l> z0NW4c%O{XX+=z;bW+Z(4n3))@pjQ6^jN2zxcadP3599dWdZNhD9eBV()$_7lML~#bsEZ8;1B;cj1n)akUux^ogQeg2{-=;6{4$8`ald&s?R#UijEM2zwD7-t zFQWayVoL%FE_Jh-AwBchYP^Iy5Tqsx&gVx6S3I{7C^{Q>jTPIzrI7cBJs=$I`NhCeZ>`1aJ?N zlc+=N&@HhlI^sq!Njd8e8qO(J9Z$C>@3xnruY|!KM7cz2$HIHS+LNPoace}VyzML~ zD9{Zc7y_kRQVI)^)zMF=5b%!~Ik;Bw&M>x%J`7xc*O)(3FAcRBA2hFpv`HVW`y8oE zmYdxX^l)8);Ok{9^&_mVVH0X{_Mh-l=p9dyJF(a4lR{%{XxZC8q0+wv%2PM)vcXHO zG7}qklEH1#Nic2Z1*Db|pRTT@FZt>hz+$^oHuU^=H!@~bd&x(Te?+j$@!z@NjTCk4 zD7Ytx%EmJ)AytABXhZ;^s*yt&4ID*EjE$`{@LLjoe-KRP%yvj-5jA6)D0Y2Z+JB9I zvS6{THt8pF2eIhtvJEg7fjy=7@CA#@19{A2%%%?;*%@eHFRguHc$kYQ_%{HY4Q2{8 zsM1>Z7uttX2NRyiDqTapqobp(%R!;l3AyXJkjtjS*y!jXyf%LAm?NLBCnrF60gU(oYtbh6)J5Vr;vU8aWR&s+>NEZ9biHAs(?CC2+&5kslx83?Lvyl-MxUVZPK#}TozNsc)}U1Ke|k_&hl}L< zJX*}L6!S<3rJah$oZ&-E%=;NYZ!j1~fQ7z!2LTG{-+T(@&!=~=1+LyEc)rT-SRky@ z6UwOCBz-W?dju}S<|-<{|2U<-MUk!7bf+ML9p<630}k;-50$ovRkJMB?f&j|+tw+t z^8+>qM7#X^#@W-$iT6i8M7cpk!ugt8ydVaxa{_LP^_G| zCj#?WqD5g$9AID!(1r~e9Xl9j_xASwfW5p}%}T@FSQ1{wiPj-D!Vd19;NajHtRSD` z(Bv9><^v=kDm8k0=F?O?uxz%T-jT^mozRp2@5azzbf_F#1BRTkvRgn1iUjJ<{vWVR z>j~OZ1e0&plO^%NcoD*Fo19_oJHNkLL{q(RO3IwY$WqH^`5yEP1eq!Wymkn_=368a zlv3VK)@bVw)OnIH4bVbfOT)?N_G*v}Fw8c4evNg(0h4{oK<=-#wZ2pY1Ox${is^V! zHWyd=RhOA`QO1kSpX9DX5mbO=O95~@dMMW^5@N{LX&tTas2&HYJ!I-QaTbOE`f@YY zor}X21F$9P7V=6&RJ66Vuy6x{U?LES_eo{GD-Qz+EHTxRYO1RVU=UgLee7}o0{vt- z*q&tj+TPyoY0Lwo9&`(?1^S|J=l|_Wi(jKb9wO4aK=_Z2nHH^{2#sW`743YG(lhDxe&}%!8(maBxb&_@`?@QnBmj zaTvYQ!!vtw0rYY4@g~9`!;x2QeU6PCUI(0>#^=314@EJ^#sN=(Hdv&T+{mUwOFG@IiXdU%Z$PiuZ7F7uMp1w)~SM2!zi#s9^cO~(K!D*l==hf-_K$&IOm?S6Vm_L%{4eyzfm^&aVgTHP zE7+;KR|b378&RZO*>Y$+%$&(>cULw=VPQQT%Qt^~iP2%5s<6{)cvIx44`vMgE+vMr z1cLcTaR_$?R_8!}zs+@)kh^uVJf61YKMz?-0x!>qtM$Qo05ISc{^p|#Ph*?YI)go^nXg|g29Bo1osa(P2*tPBJNXh9g~+oFldZT zwtOYSsnP_X*~)2_>QxDTH(>IKRcJ8_u#br2fOsFU{!*LIddk zvy?f&G$e#qsl?g?Z%E#qoK1kk@;15cZY#KVIr9?+_=ZD|mO>N;)i440XE1k%A-zh5 zyD4~x9qzxszix<5|NLU+99;Y_Nr0^W3R)J)r_DjGsTAt&tt~^Ysm}RImOsV!`s~!j z)%7;Vy4rZt6dlI6=5XkJ!(#uHM?ei65*|B<79N{H9BniVdk@y6dI0tftVLE@t#6S;E-Kj9%@A=S-pq50Ar=hILufeeL~ z8IR%Pb`JQ|LJ9yO#x^$B(_LLFsh7m4$3UE0u7#Rzy`{H{e;AFrwck=@hUmTp` zodMt$+5q3k07zyB&TABGcF}j}{{W*VJU}242oH;UIrOgs1B!&Ar8}^U3(C5*5>CNR zi);mR-Ky1s%F2BdPTpAhXoW_yX9TKQaA;ovMTU)P0{X)`2C(+3q!CN}_94ds&8U7s zs*%a>n863BSnO$)=^@9!X6-a!sag((#2?Hk2S}SD@SFm8tCt3(zw!Lzu~sW#YTh2)!f`G6`ve!=KqsFJVe$nv7ooj+lQ# zSWWSN_l^@s$7i;qk0yA>ov5ZBOs?S?jAv4=Tl*fYgVYv0*b~I~w=!7A1~(*e67oqp zUK0%!V5K2?JBA{qfGyeb=H>+y#_DqE*DrP2pm6g5nIFRCiF5$t7(L+i6qqS75_}Jq zAT5BJVb&ga2l51ykkITKlK_H7bT(la3t)l%#K`TV-E@`7&enM0?6I0Dk(G~XH(y-4 zJJ1iHv3FKBH&Z7->94*qoMX>9G?4M%F2Lz6XZC;3?lV&&@oD5kxF7P`t5iTJ^(iLU zAcg9tr=^(-``!!+F9f4-kN)*bCwatoX3bdqk_!P-GPF%tx??dRS$`A^$nPB|hXMiPb^tE?{ga zf`0?5l?1F9r=|!r1A5=m46t>0+R5LVQ%Y_?-xxdqa=k!K+RONECObuiS#&%}7a;Mv zLEg*gCQwRk&@(X1*R%|1i~eVXK0Idh_?PIsWYpTaA>>)LQBnOrz?!#v;X@X948j)7 ziu^Fxnu}jphm7XRsfVN5(?d+r+oj%t@erbMH4V|bAIp-s|9RsP`oWVi@LZ=YAs*fV zllB0kQhE*Aw}!D$)_~Z~DvBC`M47z)rS)|;FmY;Wxg2`DNRO>qrl|C)1c3y#wLfx; z4&D_%0JR@hSO^ITErOYZE76Y9r;2(W>i=Xmhrp%Kmy4%{wDW-+4VJ@8iBpReIJ-w!!EwSW8&gnguezg zvj^GvBS6bRlsrzN{sn`aI3=3HVVP!_9C&!3QW!kEl4Q(-IQp6-Iaftq-Vuq7qPfdP zYIbb;X}cs{m9d<>c^qzhviICn`N!$Y$r>BaM+zmGsTJ?=Y14QZDP_Ouy)IAo*IyOt zoV6}cWNw!YhM{|zR^`GVsIZJg9haq<82>zUK@XO*qi;X`s;d(ZW`ysMJF(c`fpuFW zU6Nq(76VsU9sr<*^XXq<;Gtt_Ty@f#p7)OGQ3^e_pi zNJm2#bv2`to@_)Ee(&oTJxdt1HR*7{;}Yj(fCAPBbaVxbjE~aPLLkBUr|!4_1Oy-9 z(%<1Vq+EvDpxJPHQPzhBnuR#u@)lRHxmIxiuVaH*#DW~8RBCd({c!+SiynJDhr|9e z>HF!o*DR9ZhVseyd4So9CsQh+Mwv|rvNs`b{74ZC3ER2r_ ztl5b*_@(Y+A7;V-u>m34!DShzay|qP%(Um8a$6>vo4l;WTv`J?pATrx851d~U(n(K z(^Y_nvYtH;PuED)59d4b(fdsOayjuOVb@faSZm?i&9St%Jy$1lBe|aCQEs(YjJ6H; z_>`ojx~#X^x#Mlr+V0Jcm03OewmZV@(uJMg=SnKwfkHW-gHB?oUMw$wNubRE>W0q- zWcbKb_rK6eC0M%F;ZVr$7?C0C1ax*NU+2ing3Bp4f51&-?)?4RBr)5s^Jy)J27ag5 z1(dp)Ptx%r;?PLBAQ=_v#l{_%?e<^P9mh;cj|AFp_XS+p7Dq@iM5QJWAaUVX?2Cf@ zZcU@}{r(%P$fXGGGKF{HTtA)|{Jkl_J$-v?CzNNf}#QLCH}fvZjnh*SxEa zp!u{a88V4gW-*O&X@{6f@vmyJ6o7p6&{U@{H}6j-1<$~JMIgBcI9JMNEx>W-jOyK# zmYQk?hSM5LVy90NO&uH#EJ1qOO; zJnSD-{HpeG*}&#v3=pSJD^v`f{i{D^L*#PY*%BU_n=yp_+T9zHL}nt%Q8s|a#`lRk zL4)_-zZn3;lCn5q&ZIGRVD#q>Qxbr=8?a0Sf6}jL*pT9jsP1n?Scx3Z|lixVgP)d=dv~}GrzxpwGa38U$(3~ zO8b<_2A@i`O7yMBBt+pqwvNtx{e3VVmh&bQqo9D zcb9a7(#_oudd_`+@BQvO?tkAH92_3?VaM8QuQk_PbC16vmUA4T5@c*hl?*B`P|K(Q zN&R?p)w@agsj7FXx^kCt`5K(apZ@XI_;5*wMFXJ1mIlS&)tDC-7rjoRX%yb>2ZoRr zpKAmt|K#WAM|*mJ<&h_vF7^>8{+q@+A;D$ww=2i#rXqTZk)I6xL{&@tgJR9YS1ls)w!Giu6bF+)7JqR8Hb`J%R^!Z^o7_V zomocAP@r=P#NCG5%sAJy73AWTwobofxK6`=T))Ynm8*t2HIXG}7yK#BE5f#uON++L>B%}S%yq>7cO z=fOyhpBhjLH;t&|^L9OKnu|C^E}|VnLqi?x9Ub`*3Nrxv#1Q5&yL!wYjSz469s*c7 z7nNoZ&V^zcQ;3JE^@=78?;wd0q=*k6f$~s9A|RO1xdx_uEYR@LHqY*RftYb0Ensr$#G->@!=-Hty@takBVOd(aTVNwDm03x3SY zAU`v@K`3G$9i$(%3(~j-+t^n*tM|Yj*bY-(pD0fi6BCo<4>mAQKBc8C)*+yg&^GRa z)c2y81C#=)tEx^Efrw30>GDCI2*cAfX}VK3{Z>D!U%4gZLjHSO))g8`8QPr%ZluYD zm@EvDaqb=T^uc3fW=H|K;W|4B;z7^&E!7&WZN!F|0*5zVtgU|LBvqX}o%4RcEwbtP zON$+;p^i0EJ^JuVC4KWvg=T8iM?N*9#f4XAQXNhDOTmnyxY3vrAovIM&%Hzd)X;nw z#}Dj&o)O^rCk0Mt#GuUwoEg?9%3@##ovVO(%fztbbvlDX1%>zB^L z_~K^uW@RgYr+x^X6is+^U+c`MM&H!8vts2{i)&TEM3<@Cgq+dk=OqtMRtHt0ELo^ML&&f+>4}naJ z>Svqdy{6o;k}`d$elhg;MMB`CIG^Rm4+MYY?(m**U_L(d|PJ3fjWI5LT^2|qhu=!KB#_Q5==WFvObR&MHR$^SmiRZo7)GASdFZF9#oR|=_ zkmekW+s98*AIP;k>L}Eo=B(g*&H9t?=zlU|q9=7LpPGep>}(;viwBqoWJg`YI8r62shp`<+z+Z=cqq zCady;gFxl-B}mKn%f3)Vx3)ASk0nPAAiQ8XK_!}gktI^G9p882xbPTgIw{t}zswvw zxspZFt=mdpO#BwpX|aAzE1ec5nYsLo^0nPw%U7#gZF5oxF4E#o(8;0b%ikN~_EyZj zJUpv346kD+VNC>&v#wXl-y%g6{dgR`9eV=>IoNwcm_hnpv6H&9ZK73Ym714!IgMZSJYa@7nvP$G7=ec+Dt?dw>t^>(>) z!!kDNo`>%;G*y1s9g`{FY4v}S`ypQ~ zk=#8fS$DGI)q#5d>s-#)EH!?7FM~&aFhiQmnWEL9nv%Ms_kJC3mhd)veRhub6hiNv&OeTYz7V&qTpH zD;u94Q^l_xpV8css6^A!eZ+sVlwn?Lou}x7Rkri|#A5RK`V8rbCgBbr$F=_D36?V{ z#O3^#j&cISdnjBA7!2~DeWk?q>^Co=^slzx`y-s5%AR<56|~p#qy;EA@jL$qX(lm% zfm!FDGa{J|K_6N*;m}UFI6t>wrqdTM1yu|O0ytriv!0cLl8>6c>a?o6`NL^$+Uyc& zB5Mj{HjoV6rl0y`-Mxck+SZOn)nQquc*)RLAQcwr=74-_ZgF1Fq{eZg#Y~U~Z*CN0 z+arH$=%~5;nM4;&7Diz_FP|IU$DPJh{N$UX@@FbC9jDE?q9Fb0S%1>zQKhnQQ8?*6 z!(@ERVEyqS`ga;}p22v@&x_%Fi3=?J%V(TbCnwL}AqzI4sOUA+k98>M(w=`7d>co= z!{9hpe0lZ2)$IWrA~8)gPf&2M+3x;632Lhav?5B|m+(qN@NZPX1po6!QZa2DsCaLkGs zJZBv|TcdV;eRAL?Oll$;IhP484?n;7NLv?a# zN18^wo$CNjv*4p#y5+vPL6wiac|kkA0W&o{eZdLCQbP8VhQUBInZWgtI@5Xwa~uZ* z{hIF+x6;8-Y-pV9G9$Cx)|G`A0d^{1z0U8Shl>yD#@U*gLC9TA;No(ZT*+G4l(XEP zG-{wCV)*5HIg%LmCHaZTSnPFaPoBNzTU&QGNBPY4fV#nAH%r0G1j>Y);T~G8Jj2WU zj&wSGA0MBi96+riy#@5=>YqEhLm!BdNWJvpKejfltgP$+k}XNupMuZxknT84f5R4r zf2KPlA{3oE&Kii6GyPPpulhSAU_^*OY-Umet}Q%1e$q+ZF$W1a#3t!%&6~@Um6o^a zTI;Zv*+}xEa@z4aF}yK*9j?6tnBAkOJGyyB3J~@81`H#TDi;1jTSqyTVXjjojC`LA znI~ji#QKEv_KzXcv?VXtg>~MOc|@gJQOdad=t<_RhwT#d&?qX|TXwUxJnpTIfuTv% zr*0;Y*{?x*&lPQ#$<^*)zt(Y{O9F{CARv|kYq$dXGKv`we5zX^j8K)w`*`}*43g<# zMV(NB+@p_)V8)A}%9F1?d6fhqVAiuaqV_vmPf?hOqHc1&$=()@v6>bXntq1m(tq*^!2=;mx-CV#41g| zqg5z@3G@RpK4y1}?D$)2O^09%wVQBI^?~pwE`EfzZ_^T>>_RXfk(r^1Wy%xB5;_f^ z9)*1VRRj*lXO%`;lSvl;C{cayqt=sKznJ)c(!!0 z%c6(^Lhlg;!{_Qc;hVJ22i(;;8S(*B1t%Yu+PrjhUzffST~b<7$aL>+G7^EU)WrS;%-QiUqH zgyC6~Me?u+_(r3Wan$4a8&mijkB_M|0)v{BrnYo^d=1G(5DqDZA3eJHwhv_B)gVvz z48>TG)dp@~9CZb+*HIrNh4VHsSGCyvz7@JlLpkyMp>9K?u)ocG>@sFs5-REIkYQ^= zNmM}0XFT;G-B97_W0qgZ@14vGjjM|f`Wnv*qN%T0$G*#tbEVr~nnMHa7V zsn4!H+EC1j#N`F5rN<=I8mSOXtOb!8i|OZOAlB(x9WSRgd-(o6BlfSQm|R$jI!{1cI<eJZ=gz6s?JM)VKjl>9& zkAJ>vS<>f-m!x-eCu#ltnV$24UaEQMk&|oOV&2(GOoE*7{DZ>y$?lN~CpS*d4X>u{ zB4@Rw?%7#4O;9KeX)U6BM?5Ni?|FPiM7?)RI1noU-WKMIb_3Wi@@C7K<~kRcX0=@& z^Ypwu?GsyyYh@ah$@*2+%WGo+|Ij*70!U77{H+zy>b!eqRnxC3p*$H` za|h`|T~AZ#mjvZe9T|*u7)$VsfuQ>Z2U^u zpoBdJKx*NH7W$!qvQZ>U9R5FGcLDs^O;La|(^Su+1$=#Mo2g~-aXxbq7iKYnK0(a| zX_mRNu2aS1DpKhrO+Z)UI1kd9^Q=h9!j?`-e6!VNfC>u>`%D(@!LDMohHTB(gG0TJ zs@-b!Nf82vDvU)AJ9Rs%{du<8XE?4k1bt1lTG&q>N4ra2y#`fC)tug0U)T3lWf!E3 zWaLkiy^3Z%=7Ps*u=HU=tv_Z+=BlfdO7*0L7gKc(dhsV*B|{zn=z^&tCgy1uXuh(p z1mws-e^Dq&B5S4N-9Mf|(wiIBk-zYD0`}l%hDh;L>%y@%$}^WV*o+xO(-@=F=#NZo#Xe~LP)dwfDtb-`}*Xb}ztrUKi{dZa|nGL2u){OE&z zC}uCUnMbabmeZ-Qe1FuBX7Lx88tVe$w+pTErSj{2g+TH0vw9je$k|&`35HJfVkM^F zBONL6?(gvX@+9p`p>Y!qHHnumUy1`wD!$*x$9(9dq`J+L%XP2LEU|ELJqEA)If#c9z#Eqqk9Y*#1Oy(&P$l ziclN=O$d{ZB{%-~&Ggu{%=zydAcPV6B8eK5Z+dw&_HbfLPmFD*9tfHytZtdtSG!&g z&eb$uHfBlpDP?^}TS`|L$2QQfRj6iaEbW{2w3Q@y_Kio64I6=2xHD&bj!T33x&6ZB9OH!~-J0@th{sT* zRwA_mr)}YKe66HROnX;z(Zev=*yElzYMZhoL|O~_C*26F*U`!nuyHdO6yil3FJ5@z z1D^N6c%iDXQ7~~F_TSS5Lkb`BUZqVkFCroW2HhA+>W6Zw4kJ}9Wkir7sd=iN+iW&q zCwCEZd6X-@G^e(ZE*6mt<@yJ&x5y3u)#0$z>aVCz8S~{BqRc?sQz1U@OKKlRz_G9M z&E~tmTxU8gG4bfz@bZ%PRx-|$hz#W-15-z&u4Tc#Pnu=L2yo2|Kl>x`y+3T90MKw% zVxoHIKeb!jQysW7E30mx* z7Ke$8`FJ!NR+7cghaE(?I&jN!KvQjNvqTEUr^%%34JYDTpB>mbPsZPsP*o$+4uUavYC=6L-6J}uUl=dj$1is@q$xE*o?@UN-N8GWQ7VyRM#4w(8c;pMh)0Z){$OsVzbra9oqt8~tlDu!xWBrgm0Wsub%nS{LTgJe&gwCB342)|L<-A7 zm%~#zL6ABsn{iFYGXeJJ&r=UB*m+9jlST~8h;zL(JDwEjzF&(~1RWICf;@KB95U}7 zR$DL)@c_Hi1k^g_Ay_JtGx*!Nnwv(AP((o+|CQ7j_UZ&RbTb3m#EG1ShPm#^NoS7v z9xl*e5esD>m}^j4JcJ{Ix&I=gf#vy*hJ=(T)C!ap_qqUFPh!_8!MFmiCPvT|%U50* zXsDH@O!aZ|I|;VRr#U>>4i-lu%AhBdBMnziee>Vk*wfFE2}R zfa@bKrfj3DN_f{6`!1EcQqZmJwlMu$kH|?Q!6f&rokq|4Yzyg*ii>#*zE>>2uG7(WBeU2415;mPp$!B zhQ0t~e9R~M)UkQ)v+N%s;ck5IohIK8@~K>%xytD>csY00ERqZYpiQjBplbtG+S&M+fq){34DX!S;VG|o*fCVQ2Xfj^6!KFO5+z;`x zdU%i1({=ThcD~x-7g5TBt3Fl@3Q`A=YT87h0^{byO+NKxqv3(uX0`IAbr57UX!I>9 zWD5a_-E|@~m7ARjvl{N7(-RR5lade^5a22LDIb{tvPrh{E_$`^VYwet0@;Z9A`ju+ z;idY)rL%p3bH39a&7H&hqnpr+WGs*8&4eey9)aP`>&AC{T()nHym`;;MAUSaIE){1cPNaDS5*05FX7$&(KpIOp)bofI|V+ViHAFVi~7)-){ zj0FaMl`Ng2FXUO@Dw+8$UJsssM@S7&EDRm8V0d%dF! zg{NM}&bd4+3yF5;x2BeE+411y%FKYencL;fOo7S}X`pfuE;e?R)ks!1imOjXx!~tJW_ur4Opua1`=ggCK2L zTmB=*24;1o2|5tD0}l3vhvxr|IfhKAm64Ir!X2FK5CmjAo1h$C)i1io&(L5M#RyL1 zKWyx;We#A-G#cMEDt_Ail*#-FXz}v8a@x_ln05Ys#l!hrB{Z3JqlJZ|J&JGoNa6xC zH_u<4>^LWsPN-DDEvhlH<1~^{S)xXSiXlgFn&n;(M-#_Oh@~6(R1#QAf0FlvY&SrY|@TOeNou7xtwVlwq z!B$I_9745O(%lG`%r$nK4H`(b<~!#-440Lp6`IK(Q|GMfH1@D25YODF}X#nxPtn*;x=B`Eal8Kq2hsBA^ zA*Mh&{owok#w2VXR`qRL)!kMI<=iHK&vsb_B_-j&B^VMH9yrYT-D3ey1 z?NKCw8Ann{uDW@Gn&XdOu9Qn&03W#SdHuVKkZGj0HDxAQvaqPAsM+QBGRvl56xaxA zwhAZu2ALt^ze_ho1FyVs29q<@UAjYl9&G56Hai^~JOabx2! zMc$Bzc%UXFxhmY?-7I}M5fHLP1ALupwcw`RYBbjiRyj;z=8ybBWeAZ@K$B`Cj#2%q zw@x-I=&gfh@QrWLu5b+D|9Yr*G(t3^Ky2aw@Z_q~0q)riaQ~Ykev?%ia8yJIRAXn! z-Y$&PYQAAXNjGUJc}sjyRNKKp>C~s|2gwjfRvjSva@JMW zi?@uh@x0Ylor=+tnOEKq;NaHzo*HW&9UT>~DX+@C28BUjaKt%t{xs% z`9s{-Hw}(^cfSp;_zoIX8~tVyUILPZauZ;lUkNH!cW@%Z^EmIj&n%RzPt{4f(e?>Y z=mYhTZ$Y5QT8&&!B=@g(>l5{sF95ilKn`G*g7C;eGa<3=r5Yh+3;jr?$f{MU7qeBH z1{j1Q63#$|@=o zlFfoey^>^HTvHp3XA84bhLXvMMjy$;R=(weXiT8cY&2(b>vRmri#c(^5=wpebMC!+ zkTxGzfYy6be^$i{)Oc1t;A0)7Uhd==TL*G9VZ}4?yfS~~@_M$X&EECQbRQ6i&x@bg z1w%l);1FOZ3{sM{V4zdw{$n-|t#M|RrIDMB#>p!w}!325oO5CS=b zs=X(n2!DO0MI=m$#Q~_&rGeI@o=oRHfOVjK?XV>?F+!5WFk?)#o4arw#+vn1F75ug zBb@8$@zF>#qZ;V^&IA2#N1slzu}5pq(GwtMBRolQ+5xXPaa)tb2-Z&fUo6TxJ2^4i zJ3BAF-yX}C=S09m`s?PrA0wIi|ilbq)yjJG)Q8mmMod+4t~cT z<~I!(_|5=wuB+hR#|_2*JI$gnn0e}I!2TYl?AxT|U}m-os#*(jw+Qt9Odb~AUubqW z=@@A;C^w)m)}&Z?x}tT%DH1~CQE_Gud@ChQETFNwZ?3Ob>kcFYb%=Q9cxJN@rP9~a zetiU8Vk}`{;j}2VoOsyvwY5eA++S>xe`EhLJ8%=#1F-*fX~SU;9i}mmoVhDIjEPJQ zLyw>z+2riR!uYsAI9WK2ktCE=-FDRMNWey6HUIsnZHw4^y+vrFAEG`FZKO=35HJv+ z6k8A^XlYHQfr}~)1r0;iAY#OITR_9({d?-vp1^ABPXNL^G9vrC zY4+`&wx^M&_U{7}+n3Kaj@?}d4I6Xjd%F6jD-VD9&suJM<9P85WctwJ1hMJ9=Pr)` zokd;tz=Fvmr8ij^M}VI$XJ>0$%jbUnD*^c85+@IIgI|B6cRecjP52ko)axmr0KN*y zx%}+Z)W!+1Spg6wX>YNtVMD3cE4mtMudcf9AYP;FE4@K0q_OsM?9yN8=;_UYzArwC z^yxci4<@xTyb3y;Twb z#gqe~wGWQBWuX2omf6P6+S<)nYIXV3Y9MLfE-oq>TA_63G;D2a`}-rvzzfpPET=oX zQIJQ7$iw{9B_)6Ofp}KS!QTFg4Tz5f`aFI0?^TzM4vT3F8l4Fj0F}$)Qo@VDHaX#C zo5%}JQ7|iYS2aM@r!d7)l-bC*yxqdj0|G_X<7i6lFl(4*L|xqIqoA87ZYVaOva&Lj zyiNk^uVKKzvSYbyCZJH%g! zm_l5u5Jfip~o13K;8jOu(&D`iC>$7mvEnsug z#4rQB`pH*7-`rYhMW8H+ZCE)I+JG(y_`58HQa!D~Wg!quV}L=gXjYjk^XavCivTJ{ zBNHFI1KK}{*{=wg{&q?B=S`Q}g*0Z{?p(%PkfK;`rl885$m>00U4&qG5a;Ypv!^T=J<_Mf zpypH!1)mE+L;GY2#C{SB@SeKuZh`@$fVOjn-ku(U>6LT;PDaWRu5bXJO%$hE0@>7E zpo{ay&d!cB1CJKrpSGZ>B3q#~muPy)#D5~;7D_k} z13cJlH3MY(!Jz{VzeFj#^2k`MGL=Xb9i3ZbesX2#brDNZxpxe0g5UrUg-132eb_~&N73s!$(HPpYi#vsg)D9sDFTa4YV`bCQ~LGd@0Ain5s{R%Jp(<=P(Ol(hIR*og&MRHphBj{L|**u+qqbhX~Y6?XrqBR z9B2kg5d`!?6yx2OkwXwHivAyx?Cc{CLhT;2&%+A+$DlvAi0H?`=o^2=)&OE0u1s_f-cuOrOD&A@VZdx65W9bnIU2_^evq*90{6_Ey z$%g!Au3?$IA|@UISG9P*@LH=UG4?jlb7yK04W$599rrRk8(E#J2>Nra=|FSlZsT)*4_gwBQSZgXjU{nYRy4ij^LpPy_x z?iBqn%O!wA^Q2ndkTdb~H}dkENZ(BMJ*N7K@$2_*_2#$TQ4ha9gglF2YNmKd-tR{o zF82q!vIqbPv3^^?9)&&*5fL9oYoDG&xx+A;rm287jGXon0sMwBJbNa=VeHd|~4wA-nI|7colRPtg)y-UKmxw*A_2M4Vw=ptG=PmlH3M3Y*Cy$$|Y zc_C#4crC|h^+%-JmTh2qdOFzn@roYN$ftdc9d7Fd zJ}=PXr#wP&!}6hVp&2#uwAm2G>yM*UqX8Qa82*>S5()hJDT(9^;K!81$2>KII)RU& zq3*gCR;DI!KFiC?xM@JwE^V{v4Hl=@TdX7 zN75|@VdBrHF-mwFTaxg*9$R4=Lt_H!u7~QqVUPGhE`dj$?kAav|I_Sw#^=wURZ-SH z@C3tyLJflR&IHp(4&;(fBrIP^EMs4SKTCFmHT0Mr5ZRw2vL79ZqXCZvAA@dodwY9C zEt^pGU8A$8htE_3;2M{F{nfO_-sdB@B1kp7A(?(Ee|F6dlgn{ii3aq2rcm}ZU92Z> z+4QYueN-dUBZTi`8tLinO(Ed4aH;tE^=p$QdigM8^W`=}GtjOIB$OuAy$sm=HThj? zW#hw}KjV3i#CIKbs{Vjt)t~RUbk_s-RY7!Cz-2go__{WbQ&x~{^))hX)RoK)2WEe_D#sX^} z1Q~e@)ZbzOI>PcJnd!?oJxD3`wzg{hiP&9(v%xv})M6k2hiS=U|+x)aFTEz9iF2hF`@ogZM%( zkJ6zhMV8S)v2Q(qRz8Tobdm`;%$3TanAY*4J}Y?Y${KXnZCF!n6>y>iLB{trkddKL zhb;9gEv@V4^W!gToKV*1vNrj0CZJaM1-s&*Fo&`6`E)AX=nVv^NEUziUU#t zi;=8=NHhEe8lJn)4k`0q>Vr6kS(m?Ol>#P`np8&g9%t92PQUu*)7*T=2X3N;0wgfTb9RaIe%d;KQDr_ScP|w5xJ&cIY^s508hNZBukT@*+h0V{Fb7;VZ_b$+N z3!ZEwi(M2RYTjkaa5G&fn=0aaE03yqa8^;k{%OLmVw%0(9%2yuv0Baqhx6kU3f%&t z!*>Y#3J?{FU#N2|%$S1w$N_!ia@eyZds!IXlF^-LnoxW6F2(@ocYdSA(h~UxE~vts zBK@Emkp#*XmnC04$c+hvfs)gaR5W#QX1BolKuY~CxO@jIoW#&BYk^(>koR&hnXnvQ zfB-|tv2D9Q)8NE~@*`Nu7z&qwwcI8n3zW=6der^&3lhS(uviX|3$@Fh2HDq90hm;ibT@ zqC-QOxq1NocojyYujDc7Htq6(#D#;h4=8Ch0YN|w!%p;u{;h2L)z&3pBON|t20ux5 zLJj!)&+R}8N=cXb-9GdTdJS!20NmgoA1A|7Hw;(ufG2+$Rq(qG)Z3|@BWH^N0XkC{*j2i9LqudDj|Xttm`Ed>Z!B@`P5);cJ_&q zAYCH{=poFcYb*?%W6~I@)4`fn0oqZQj=NL8LoHx2Bq6Xtkm0-`bf|F_1%M2^yzjFG zM>hoqbm#Mw3*(+fRzp1$=mJHnA&gj!h*Si%QErpfbUL0l&SX0uqLq-ALV{Q9QT-eP&8kJ9-7g#_t8!Ktsi44aVQJIh zXjpbqv()<$i_qh2g$ni&#;`cNIS@XixpArgcoMfs>H)IlG(0{Rn>o-bM8O^IMkM?9 zK9Ug%2OsMMzex<8eJB>ISQdDFtx10H%W!q4TUjK|Ta=NjUdgA!SACljEhG5~;Ay`$ zkWOr9@}RlxlgcOYA9#daMfhO{P*N&U?PoqcrB2i0nZ!6 zA2%hh>1y>fq00&+cnT$-prG8CLSdCQArD#l&mkZ@M^A#4uKxM_(e`}( z*)Vu{D{>}6BAn=bZ(Ez;6)3e(oAs7K1i^+Mv4cAkIGl7Uy%Ogz^A*GUpH=-5;2^d{ zXOxNZ9UUDkBj-O>FO~vH7xq9d1l3h891pJr{^$D_(8CtVdUSev8?Iw)R@Dt2Dr6uh zH((S71=~~bUf+?Kf!v*>mWKU;d7oU7Sx+a2YJafYVaC5}p!$X)*Bj5c9gT#hu46%RC^sI09-JM}2__;Y) zL)c~xkstR_$5OorG)A(5a?T2BjG!Q-E2>PXnoZT0FU#s)UU%_5)xPwLx3_^CDJ{dTA+!5uhU!o5L_*de)VVI=cz@nXPWsFv|h)1t7J7oVJWCg#bAO}x~r>&G>#_-oJ*tVEfyLsuw6I% zUkx1|9-2VoiHL8{&RXNF*?{oq&c&Fb!aN83m9^KEX5=%HJjH+(5DVS*pM{d`@qRPy z*?n*f2d*r9I0(BM2vQD!E-hcD+9M5p@#Zab<4)5Il0U~k-DCj3+eh`!8q+m5_V;fa zf9Hv74Dq%&q);YxL|8`$w(hdm#YV~pf!7abe^*sheFf4qO*9V63T0c>=$eGlGlwW3 zixyhs9=-VlgDYq0MzjJDy&=q@5cbP~%tiWVxM9ZnS<`skY?k)q$>al@ZgoIS z7gts~upMWsyU^w`OH>)7?s3{)<@2dV;mBF&@G)N&fC8X7ZmCHngJ z0Jz>GI0%gd6i9|9;ik=Q9n5KN`?Z>Vbof{%GXooaiC8T}bDb~J6X`$`t-D6AP<>Xn zsD-;JXG)V=#v|PR9B1|@fL(&CQ^mpI7*S+7)HD8v7qLW56sA}s5nvvq5D%}Eh>~Pb4vce++J#Ec|$tDWV8*PV(jOa>0@T`L+DC^ zXz0aU0@vs*C+NnrNYid>{RuTBAz`?pmtn?Z*s&yX`@%I$=FM{*jFX_4qO4>S%fSPp zrVYU8Q+?0ht^hv*ElBc0l_UxLZk&*;Y#(bm7;tjs;FoD8R%9aR!C({; zDtd)=Lh#&tb$3@Q2b;(s{&f4x@U6V8>~3jFIyn$vSA0VBU~>c!7SkNkmUqi;ZZjO} zEy4jdQyD`K+hk_Z26TPisBoZDKKSGOS`uCX3Aic3N3T~mHfo+_WZ%4eQR!e~bH2A0 zuPg8Z^cp^BYHBJp<&+r4N%_ubQE|A$yYvkdPrUS#Dat+-o%qife&Rn3%wqrqI)qG6 zz%4U?J7!E&+b9BZv1c9Uf$+Gz+q%2X-HaP;>7@zGmJ?Pjv1M0NDunXcV_?VgZeL)GTZs(N6#C`r-c6PS41@Nai zR_-dP2D79+kRH;RJAhdAoM@7*%=s z0Yn1uvGqCOcx=niYxoP0@@q`^hJ%CCRJ=wfL87dztZl00!1tyBx{NO-!BftYVFR#$ zsVA{@D$+b&$-EJDisdn=3-o^ExK|#pU95Z(bj{Aq{q(dTUPFE*!*(dD32p02r`~G7 z2t1E^jzuFMn&Sx+i-x*B_X19)Le9$U+|4WZOM$FK*4j>yyHB*ti%|oaK-Dhr$#}1N zYC^)MaI=2h`=L8njd-_w{`Ix+UR)M#?rHvK&-6c!*$$m?b-bvQm6UWzyJVk`m-Yau zPAQ0}iZ8E^1}DK*H1sfx=(z1wY1Y=(eojs%lps}%2l|yBWsMnYVCs(_p^;cr5_7OV zm5}uS<0E^g~ikdpfQPAuH$nvMUgSTl>!ex+2 zE%`)xVIMqF0u5{QUz^`3Dk_|AjRrbvT{dKwoPnwaa9fb0+voo8TwO2eR8>_MY!fjD zhi(aY984X7{5koROy=-vBY;dkh01JuV$3uFNZc8e!9(|1xLt6tv11!^f;Se1l!jS-oCJRzd1iY=cA1`R991T z$pxP{2bjFv+(6p(rLDl_CPjFsn2gAtU$qGxV7W3*f`b1dz-CBR?YBm#DwrA@8*2eh z;YR9t{w#NP5WMPetEW9+^!hc%3jn9OfffGz*_O__$puj5v4P8v@*tYIl}{76N!Qgt z#ecrK4=9Cq8oBNcJ8N3HU*C3D0bwb@AX0R6y@@qYp|BDc6B}kTnDC_a)YJPJs~~uz zRP6~Cj&!xZ20X2xbpa@I4|nKDoAzYs(2$WEgt{`U8i4Sjpjy1!MK?`a4A9I8kLG38 zZNO$wc`BF8DR;=Vd4~eb04mvx?d9c_xfj}jp?Gj`;N@q$zJ_fIn3-EtR8(^yz##cd zn^1Rsv5`hg49=v8m_045!m z5+84Jcx$kf8Xq4ING<5ycWt3W8&`a zz6I3M-jW7ZH{V@v2%>cg_ugEc=`t}f-MmZb%++Msa0Rs@wqWg$JJ5NW2Y{jk$<_uaOrlM!=>SK@ z7=IZB-EB}=FWTL;=w3-*qzVC>my(hqwvpL6)BQRvI=Uj**nb1t6l@K5Ob{li-O!Km zH_IX=aZw(_-36FwK!RQoCTpWjC8h5}W zdryUDu(P%l5oW=+LibbR8XqU=5sL8t`4fKRetLg%oyvBBUIAs@=#eLwW1C=a?5 zBdI@A0D@le8A`gV>BD<`((cmI((-gC9kSqgT94ScxKt_xxnMMIQw@`$xf{Aa)2Mr6 zv9F-4JZ`!Du|z2OD)X%_Ws<3uG@^om^X5=SJ@EPK;^%sTbmO?hUV%VqE&t1x*4>pa zr~sZ&DtN0ajc86V2_8KU>Bd)Dj!sH)Rq1ZB*=MjU9eK|Q=EDY5CZF&_(O?~)M+kBR zS~CQy7+LI;HK?dbknm$<0wRbRG6w+)_yutbpNPv86TekB8ZlypgzKBlP?o))FLlbZ zeUj*bR~^ouz6(ctx*$rlD8uV>8oc{a?CVP%hvUV|WqPfpCcTtiKD2E81R*T#i3}YP z1V`iAaw+|{y*`t_Z>MyTvD+NKN;J~D;E)3nU*Aqw*0Rb#-_DmGo0*6pp3IEdHbZKr zziYr>9rVwBic0W=>9*Qt=)ATpW*HxRR4e!>qzVkp2MBVEbW2V)Lw?gg-uME5mb{ehG(J691oP!pQMwT{~)7pg$fv?W$GSkQ-eM%*K#PrGlTRGD%iV=4X02 zX5lmms)ihO5f%6c!Q&Qa2*uP`6PR44MeXOaZU0Uk$4e8s*>4#-l;O8%mEEynQIp!+ z9-tX^?3VI_la8ogDcPSoBb|~WlT~1 z2sKIVP3zvz|Gvj%V_@|%Iu>#MtR8eDWKBO7B2o*D&Wd)Zg4NmDdpDsB9i@477yG~K z{U0-1u}1r2lLH>=z{He)*Rl{nZnc>efRSzy{c9)C1^$nn$O2aK*Z!gVfg%pf-q%G` z`H#iDmhiTr_^v`1lDAt1;lgiQhKHjqN90q5pFH>K^(17PZZ_xsP`exAWpLY@F4XN`re zE9UK+Q&Xz0`gbaW>Emx(H9na9-)twuUKi>Q?ne^l72m7)7f8ZDq;NAiAh1$x0Sz!)f-i6(M zRq&_;4CP5+xuG+G1IHQ9%k95AwvQIs-$KZUvqMVBTZE%sT);O(^a?I%gdlYyBYmjZ zi$mx1|A*QC#0S>$U+2Ph3%t?21D%V9?&JLdTv=Q@jDcD?#{Z|k`7aLhpS>B!eL19J+$pVL!d;|}3mXR+YI+GqkNn3SXy z_=*2^dHmmf|34n{zuZJvGvL4>4IOj7zIVH>S4oc8f2Q&q`QBx^Jo<0WKeUrooo5PHTPd!%KzbeBA{mVzxM`$2dj#Kyo{mzXVRj?u&rVL^HkK#fiDL) z%tw1&=%q?8aE9$P^uJ2F)|e)*Fl=nRgsieypn_BqFchN2X(hHIvVa7Mj5VT&tR+sT z6wygPu%$_{88aG@U?Ny>tuXl%swVZ?*xkeLe`FgG_>qF?)?6K zeed~kp4uJ(uum)!BF40zM zD#JtLuz7_QHqn+m^Z2n%$U=SW>FJ5{yZ67`Te&(u9`4?KKiLLM1tXC)e06Y+1me%AfPx1VfAj< z1F+thB=I-fc1t9jOgBK@&;i52va&KiL;*^w_V`%g;m2$H+y&TOu)fc3Z`ZfB#unC9 zp7IXji=RKgyM^8a9`E&-%T;c;b?Yn52wq*87fa0`GFS+f^tA@A4uw|DI68FwptQHC zQ4RQu#bOZB}_Gud$ z8h&`~lS1i52{`4{_+9IYXBEyWMS?&e z_-<3^=6tDS(v51QOC4Y!U6kE@mJM!TX5zkcbUP;tqV!szE-`jyfA6o`@FKU)5A~c$ z)S1JcDxidF$V1&E+V))AffAeG&cQbWUI@8Q2~#;hsFwhAz7fG>cE;=<&qA#?_4-OV z?8^O2EC0fcpq$W&cGK<4q58)ir=efTu_Ul) zhyC=c=|MTFJo~kS4$tJ+*o;xssdG=wn~3*-OuS1bP7TOPguR_QG#B2-k7w3eFB}+# zOXL#jUqbR%wcTss7w3MBR0hyg$zb9sFdfKPfY2`KTX8A{f)lpuo7Op(x@f85;3%`2{PhC@Y^t9 z0c$()sJq*yEsu!O4P1wFdLOasvCq7q2MgW~KVd5x9z< ddT>23mo_GqE^6LyTidz z!YRr~YJ0=)W}+DnZ&CEMD3@aA$uw2ac)jOKF;?ayHJA|MoBD<8wix9W)9}-HC$@d6 zhI$}Dcx^BoBqZ=I=m=|1GuEYI#&F@OJJR5+P|)K`99#>oXS8C7tmN_#^w;g@&$N)^wtqD) zS4O{CkB@z-DmF-G z4I%sNZLzfv;ycy_jtUBEGgj)U`Sn82$*p!H&z6n}iXGII(itj(qL0U~-YQGX-Gw?H zZ_Sn**go;hnk;W#6*O`mpI+yCHrnp^>t-po&8rRH#L7b*I(!hV#q|57WH(3{bL;ZGkll+mt zR9`+KbN8zJZ3x@9@bTCr12*7*(}75DRaf;XQ+9mVU^?xyx5e)I@94N;Rc@yYV+zPk z?mCq)P2$&DqQ`S0onVTpvFUf9`_(F@o-*j4x`xv#?_UHIq6}gGk@&N9~sFb@>}S9 z@CFxHl_MjWY$>D!ZvNvrR@tJ`eX8iBJU#q*fr^QK5%e~TcePi6L2JpfSQ|-`%=u57 z8LShuAHHR9@u|%6j`Bbt9<7&)6Q!Mrt%IM9xmhPxu2$yD-h=!=8`=Wv^5o-69 z*VeebEMkcdwv8-ON;Q6AHMGP>72nT} z9p7r{%887Ap3wPe`Xf$Lr7Z5dZ)}a_%0^H7xG0qxO;Gy~iIqWxWxQVU^+50Do8>nw zSOwg+R6OMJRK~Sr#x$qD6APr;G0@?CF6cfr4c*k?8GrdqVwe07aPTSIx>u8J-TV1{ z-?tu)EhCCLrP|ancf)@yCo*^rX(s>gxv6fE*G`ymdg!~jas(YbSgM88hZ9<>@$0JY zs({y@3>s%-Y?iO2+19UkO;ZSi8k^ZjrZKRV>inr> zAzFph`YilCAuGb*A8~4C{kVmh0q!wa*UfB^lddFKO}yzsg;2SRFj{hpAa`P`*!j@E z&MgMS_&(D<-FT@A1A#?jL>R&!v_G5TygdJ&#P8L4=i}?qpg~Vx=|Z3V0Fy#O?c?FU z^qwV}{l3@AbmR5QbwIr!f1{oj|5_CULk^@ryLThqe`fY|s)80Thge?zkyaC-7*b;U zn4Z3!;k)NCDXNTu1gYlrg=*ULoaJ-qD;{~QEVhfRsTlv6D%TKd+E%x+Bq%qW^jf~m zmj2G-blOw4e|f@S%I&u%X{ZR##&Y)UB&E}{=yUpnKi)Ujpa(>hXOQ@K@u}T)3|-tW zPL9H}vLr@&L4SOPZ+%#%%D0+40ymeIMFaI0*deaz->wk8yX+f#r`2dJWwxoj@%u5a zwur$8Z*xF_LkCUX;NzXQ&~ARXL*y`EdYoJ{18vmWq6@oK>`@%l_WMYQcf7oCZCXsL z?l{pMrPsi)*oG!6Wjy0Vn-r1)!dc%^fa&LIbEorn8W0`5x*$J{4{1fG9 zbAJ7nCBq0{K#-9zK}eK19xmj2+(cJjuUU;Ytk&+*5= z==wmr&{dulB(o@euH#m|2xFCbOSU1>t5>A=hKGNU5x?3FkKo-JFRl%kIgJdMxd(#r zzOs)yVXBmxD{mZ6sn~vW!GNG2fzI%J*I`!N;`v@})}#lPG6jW@5>jMvI?~~5#q^gn zGd<;3L*g!V&BxJ+7a#EcO8cm@?^ckIlkc_JoZXtqUqSTEDl|q{af)k=Rx%hf*ET-A z%Sh7*{^4QO&@t~|36{+URBtouL%7+8)r{U$5EpcQtd+PH zE`5GS%#vFY0_lxL{pCYVnF%h}tPG|f%Tax)9x69#zlA6(9|ec=G7j#An-{f+Z& zU?G-xKZ`6eW_tZCh2;%Do3r)Nb)OD#_si^hNP0#|ZIafECzctn?jCpd|$Eb{c57l;k(Q)o|3sz1S-HF6@W{pXDMVn*` zc+|L8H>JBo`N>sy5#O4KKDU-|Ejxv+t;E-x!ibdfrTQX4?)lfuSmCOm&NUj%SaZKe z#h^55tO=gt<%~~2HfF~4u47p8^INs7^4<48`O-?gCc3uv*RQV5PFa|y?kn3v zt2hA`kG(yJHP)$Uw;E~&czs12>whdU3n!@pV?{+R@U~wy4VB1bzgv0V>5Z!O0ozsc z)C31!_5zDfC39WFqot<8XzDU-_*v~f{D3c5Y1)rL?f7wAh7opsS%EgTL}^n&qM}#b z7lZXFz52vTNfD?D3^~IAQ5hgSd_z%>D-)UPsXuodklh|u%@m#?nf-n?n=xzVi_ybM z4&A55z#)H(3_1vTFB=-@GqV{SlTmG0eESOv3zG4t<^HZ&6tfZ5GdIrme;aFAN_=Se zG}-%aP&hZ4dhm;{k;*>d={{u>6-wf3^q+Ja$mCLZC$5MmUoc`go|_o17&ncVC{~YK zg~DPTPf^!}+ZStjos@3nw6JGCL8`0kq4wN%vFK!-2)&LYPYUc?e@oEJ>RQ){VR9sQ zWBHxs=J&qu-`@O9rVD((kLF5pbz|~}D8?`%7~%e0PLK6_wmWlLz)dSoVZv6E zdsOf-Qc@F#MtW-##?N+f-o`h)c&m2F$UiCw+AeTJtOHNF=3{sLDO?84lWiKJ_bm*p zHw7M%6<#o0U2DZ8a!ips9vuS#DskJzsC|Hxv~=IQ+h5FGoim%B1M7(K>S;orQqaxY z8b2)q9SGdpg}L{9j=PSH%`5Z5ug^B45~by__Ql#|pX?0DMc8IDpWd1!DQte6TH?TJ z`u(zD@lIraG5m|(LnE~HU~PL!jyT1$*H|E#^dp&lAsY1HXSlW(FYkcIzJTDHt!7M7 z{#UofJ6jOllm#=>BAp{8ei6TTeF`C$*=kVw-2+vpM9Ya`8d#tsnkop^?Y?p#^DmPsIw|MJK0d z?b}g606?R{-ThY1MZgO;ieBDG$Xn|_e_VAVh@qJ0ek``t#1Q{1lLp}!t-pmMIkP1P zIq5{*WuvJ`?hOUWX$Bd74X1zq($r7k;kfAUE-w;96!*It`t?$?#*a~Aemx)xnej^6NLRV=8x{Uekla zrxoGOTXs61uQO0heOX^-y`~@J_&&Qz*50d}5u-1{^_BgmHS3_>8D+mFI`2&`yFCI4 zeNW%I$IZe>(~KvERX0|IT%nkGzC-Uvj4I+S{@J=l<-E2EpOn(l$s1gJ(#7MC{Z!~9{VoE#!plej$7O4%KrDh^n z*L6`2-0|~1w1njc9PX9)=g3z7y-hyubJDj@~Ay~JtG?qj0-{k ziIUmk6~hg#$^I+v<5?$-zUCZV_g@b!EW#da>z9rWr!=~f6LRMUJV(8wMi|6-{pO7tn{N4x78X{#bXHc@&1O6ij8|ehn0Z>_ zWPN1yh?PmFY@$1(IR5KbFOQZtdvDY~^En&q>(4M=(;$0b{&OzpbVs#C_j(ee#!5{{ z2>kekI1f*qmuw8d$n)*dsVTnsxw-46GYmNZLV=1$k9<{X%Z z--nVDy)CAn>y}T0lJ=rF%YOdRN3_a9qNmX=6VT;dX4HOpCcSv}ASh?0VpYE7NO{p_ zZBBXhcFg04wwTH9e}^vd$1&&ia#vMGX6E={64QwSUiPzR&*JxXcWaMIqmF;fJG;0% zg!Kv>5aHq7*;razpKisA-_Kx^kTiRP<;ka}p^=7M&3<)Rbmp-gj)ZxLG6v05TLh_R ziTDst-#dZNWXJATyNm4)DIY$3_+flf`tijb3I8i&+E3@DnplGmtMTG|su=>VY2~+$ zQ{`!49{&FCNq8M5H-F{po#y1fkzosK0J=KC1`X6Osb-*|H2H$JalA1+3Z9k~Wh zlxVQXzb9)l#JiJ9TM5dSk2ifzLLwB{_nusQnLOl44H+hhE{bc%^z8c+}{3>XxQKoa20plGkS8>V$mH@6?DDD5SPsHXWz8V*Ji5RK?f25{=@9#4)gZ%0r?ivLqoW7y5BD+aPyg=Ee~rha5X;zGX!#UJA)Ylonm{98 zTV2f=p{|<5@#jx}e}6_GK8I-rc=TWYgP9r{;lNt~V6G%p*;G_iPQ9_j+4L%@9ga<7 zb3SYRI8!ZN2YOswT#KhPe0+Qx{Mp&Lxw)(&BDb^OTvv&*|K^#E4R+UAjTfo5UP#w| z2L@SdP-iPjK;Gw4FRzsyih}i1zdf+6ckpXN!${NjBYQi$i(LP!<8sMRk)08~@2uyLBBXR=Q@^DQhj$ugUA(krX!u z$NP5(dvUieFUt)Zw!aJ}vz+K=wZ73Q)86uIaG3PA|Jv%?Q3$r6>NPpDQeU5sF{#cM z-ARRZ7~xTSG(L;%-0ZCA!0>Q=xCnAgFd01C1;%`{C(FtCxos22lU5?g@pOC8L%mth zoeqtSa-$$8CufCx{0m>b>7~xlaVl@6b3MHF@N!3Byu>EW9ykSNG;*=ttmYmjNT@bSu-(ixb&;+xYG@r%<^ZA}` zX4t(>pq3?~_wj!a6c*+|Rk>ViaT+)VE|)mYp{%UDzPfty*Y8)JEb(H{l8wFn6&jxP z_n{Qs7n6neM1}?i0nZ&;Uw(>)~fVtc54$riT%eajQAz5&-y5WtWkCJ%+1Ypfu%p$dR*A>@*TQsUdtZT zxC=H$#?4Har(|Mb=?yxG4)ld=I(>P%q(o>0Mt1t;#mbME3_?;d-n#=3z6BF?Q3V16 z0|gg>+m3Ma@c2CVT^>@!yni29g=_D_Qg^QGc>Ua1IL8?X?TZ4Ax7BDu8Pk6EeIU>31dLD zlR|~6XN!%LVr8e_dtDv>%@C7p376$QJ*aBGA5&-`DlSz1^gSe}Xe;R3)F*b6bkB{S zviSW*3bmr`}Tv?EriVY7Lv1LMxrbtz{p}DGeeYWD9{n-HsSNKSH2N z79T%H{`*a{pn?W7UGrN3JvC;!HfzQ+^wJ9HZiUcZd+$^Td>jP&n@C9+(>e2lAsf{W zu7ib%LPg34&;Q<6(GT~~I~D@n?xw{|zkaG1S|lc&(D)t#Evo}E9JsLq88}A5KVV!S znMm7p$A6XMYDb@jI45*0nOM-Z_w}FKQe%>Q-3fMg7|U^$OJP8?haFQc`v*gy83}&} zBuMH-6a*+JF-h#KeYig$vti*TKLbHD+8?G(eZh-&l@nl-yUl;ul7OtLEpVL!frc247<9$F@rJ@c;QwS}>PUu1cOBAVKfiRZCBvYXM_$yrT74=xo zz>oY1#s7SxYA}?Vk|lvObywblW2K`Xz)X4m&rBZ=z5I^6hc{%r5Ryhp#CbBMY;etR zwnIY!dG&MvC61KCMk^63y|%!*7;lUNm{r(>1DXM$JX*;u z&-z$sXhQP@$|4iM4^%a!#<#>j66R0T&>KbGF-BXxpmoDiz=JVH=NnrqWfm~Lf80cc z{{CpGDY^n(<^E=cQ2Glu-3MBG*o0k47INHtSory)82}%YzR4+#)BviP7?c^|`H(K> zh2-jaD_%F<2@4oYUV^Vwda#@ZSz+Fa2bf~UYeiMoe{T8T;TD+o-jYAgoAsX^K9;%sF7YWA+q~v&w|9u&|-h($a1GJnL8BY}}*_>$OmNl=mNt z|9i`%Ryb`{ITDD@K){GRNqrTo_MMy`&d$!%thbog}-z%KAJQTJE;1 zhvYZQ)7XuB+e1~E8(XB{@mRQG&=>+XUwBp3KR~sc<38u)iK=*UkN4%kI0>Jd*D73D z+d3|wucm~BH>}<{ZC(_H}L&@}4vEyy!x< zk9LjS)Oyiy9#l}bYV~E~u?oFkSEqdzFjkU07w!ZsaK6ADcA5YH%G`*Ep&9MJ^5yIP z!8?HuZ8ihS!rbzB(za0RfkECw>p$XtHMWz{JX+F-w$}3>;)F9syd!yz3TF!>Z{AA% z<$&cl|7pFll9P)_;_g9$j9NGiU&yA~Cnyas41Mz48H<=?a_)SJ`7~AIXv*nVGM47? zhA3?TvU7I?=EGJ#a)tH4M+&tJfds-zz|%+V~DIu|GDXEh#qi z-kS!3bqB%^y~wwJ67Sa5d&-@D&m4AUDarE!KRN4*0gGeDhzkoll)U*s_LBsk+d3lL zfqOK^VE1UeIGgmIDUmMLzN7ppW}4z;!tSNfN(J;+UiQk$$~XK1%k8tB z>0(+sy3*9oMtmUnte)MkG3x$6>PjR1?6zJP{uWA6KTT-M8Q&E#46CRAoLcaE1?}q` zF`niZI%^Xthla577zot3>h=3A#n4`y)TFg&2$bXe)W#+jGc|YQ!s*(;wXR%mccqIW zEi_H>XITxmMX_Qiy$zhe3lQ_<%`FFH5z^Orp$Ga>A24|MI4#db8>`80&bg2#FOLtd zo}J%#{WX|u&YU&V`>uA_o}qVyVGq+rVo1Rxsh^pdVE~Uq?!}*lh4EVoiU$QWe5G)Q zbe)X7a~>Xa7aE19;)Q`iY=?5wv&eATqOzl=p4q&$H!)X+} zmWK3CT0V$@T$jiVM~1a!^1M&|V0MTB%U~_~?{uTnY?r21Rm~4_bS`N`t*O_&yuFLH zGkUOpHcR=R+?9a5x6|+?!R7J6UyL#5f(Q*1_D1nks~W~qLdB~S^UFB#==fhfn(-E<$Vp~3I2Iqv3Gg#H;RvO#UaF6F`SnPUDO{(ZNvJ}Eb%72(vwqDRCv$b#43B)>EO z{KUFt$Tm5YoPy^$lE$+UdfMFcHmu%w_=N%k>K5&PdjU?k{DP7&tIUU=KE>c#Wb;v_ zq}zF}844D>w)nb4vaYV~Cf*-LTgCdS{Y*0ST*#H#(K^(ea=koOo`TOJkd{=;?X+!c zytr<2laX(5;kFK0RT?4vUsUmrJE8MJ{myqS8XFr~g@r%QFZ6Jo0VMykr?1a_xX|$U zWMh;8utdFvVKR4s>j4O3-<@lSvBJ<%20)!3Krvi$a<;Zym#;kHB8Rai`Y*funC!)B zV*B4HVL-h=5@fAoXDa;Sj+%RrpO-hmB(>FQZ*MO(mHa-iEr`QeBm5Zg1a-K_vI5^fM#ugh#@%5FQ%Nt zc(i=D+%1w+`7P3<<&8RV1aD2}#J z^Wi~{cuV`H3;Jy#_w}b%ySy>mA01apsNnbd6KL$h$JWpdrlar}e$vv?GQQA%p^0js zXKegij)mpRr?j-RMp!lAWwxYCX8VMVo;$jdNcAAay8~dn0iV4>{ZCTY)6^WJQ#lN~ zWUw|l=7pN6HWWC*#A@epUJR=M?Hs^>hiw3Pwx5ES2SS3GKhH>7U%54t7&Lq49Y4ox z3?auX>5o@RN?xh{up~J;9x4bF2pji|CeUl<9O4!vu2M(c*_~9c_gIFWeD|>{o%q21 z01NjJoy-XU7;jkFuoO?IO7g~XZ?zwJ_rYP0CVHj+zNMq6!vs+bN&-^MBmDaGAIzDq z8IL@P2VkS;o4gD+UMR)S+9O^Sx3}cp6$8(0aBlqjo*@ZwSSFH#$J2Q*!d4EXocRCwI^?pAEaMPAH>n#Mj0=c|-=1hKI$VNOe9*MlS z&|dyE6NiVhvLk~0&cWo^AoNYTP2aiQ-?*BKLMBJ-H?lNh(Els6F-_zoMCHc{>ko9( z<}jftsc|wXnxOe;<524(vuw|5E>TJIBE zwXtDSl_oca3Bk5dho4FfX`}3s;?K=8?RY-VugkQtEjXfR^}2>2L45KPF38ZJK52)p zG8<3o1~H$u%xE<%GOoxxjlUc;z8znucvbTG1q^g;*`dvjpulu>-6=aw2j)J zGC>}Jc6Upe>lAQA%q8jXP(^cr#O7)xGEr>_~pyv}tBTb+KZn z?iUJA`yX|)&!xxwxG-(G3pyI4de5Dywj{b7QG=`5jj5Hs1SfwjBip|dkv4rY8(2-eOB56p1P3W^`@{w z#TW;AT_$^2k9o;uG6c%})+!c_B_}uYV0Kc}UH=9-S^o+*Jguozu#R0Pj`!sAaHA>C z{5Qih_JG7hqjBiC_0q~fjft~D?q`g8D?A=bos;N;nuIu`D z@227F8Uv`;3(QKG4@%|y{|Lyhje7y> zqC$Pp{Nq+qFUS_pOnt-(sykFR+k20I#Da;O7+$Kgtl` z!f3&PMDU0lMQbW}RfI@%@$kn10}z1Ej?4}Qw<@LV3s$heec3Gu3Bq1qP)rA}Dn!UG z5BMqu+m`*`n;gZ_{GS@!u4{7fDr8MZntZ%z0?f)vZ-l5uo`wJKsV4@oow!9HLC&~7 z1_RcQ3z#671JD2@069d0%xaKLa+uX3fe}UZKd2(4`-_!jfhXy>nVq2h-@bf)HaFQ1 z{A{96>3?62_DHkgRRbbGY$-ROu>v$<=o2f5A&S(;L5aci+3{s|;5o^^r~ReSRUZj3 z*g+HEacysSkK@3YYW`nist2Iqdod-|b`34K;UyIiBmPTa|8sq5kaIs9cubRbk`S<3 z&D1q|YIs%l7lmrze$VNfqQKt9$d@K!!%{fR?i0YqydF>i2v-H7luZe*YOl?W_c53r zFK&$CA1eWiUdRDIi3=_!;Xg74-Z1`hPZ$ka=889(oQ*ICDlDm>2BTIlD`UKN;&Qe< z>9h&UeVXl`!ctLwb+W;O2prqWsvD@S2`8eov|)t>1&8-uTVr&Mpu)jlte(Bz<}`3_ z+VTd|(}qGk(D*}hk?QAj#mHvZD3G?rloS+T-t8}^fyPIHp)>uFPI;eP;C1jtVD8-` zngDv*(E_O4BsMhgvKZEv^gf^yvftude3J0ID~|UxFfziJ;>g{>-4>E*FJV91zvv3b zXa_BBw@GPf_Az!*6&22n@b`ZMEgo^3t*xzKVko;wGp8zOb@H|4bDUDK`Wy4L1q4om z7cYPmtvBE3T(_{W@Z?cyY;c_3_erEzMcUH4Ip51h)1MydjU&Yv;{gAtc6>tvuP!g4 ztu8>3yruLoc@(L8um7&E_X5a;f~ola@a?-}P)>2bf30>a*gNfo8fYG={(Ue>M-4or z9Tl1>?Dg}?wAI^H_q)H(p9dhor?$1-6}$Z%JTh&x_H*C4X4E#JQ%b@vFxxB!{}Tid z)}(;+gifvXK)lqX4JZ+H4h%$H1TK>V!w@>3UbqZ=q?-jo{1}k_3~X(SdLQ_l=R8h; zAWBk&5-{87Jg-BwBjmOgNqP4ug8)sjODY6=x_5t}W%OYFtNOuqbWDu!SHFu_lGlJc z*3{IHetosDO`|1ovymOt=6KMM5BJ5$Pl;aZK9)3mPS*F%kB^&k znO^0&ePT7JeXJlw1>BsA@`JjU)@9r^@0lRjZGlrqAnezqm!s@aK%g-DQ;dsL@1WlI z5w16Vzj}wFM)_y3MBpz<7y>at&@h5k}A|fIK z)6*BfW@@ZBfc$%PAAw2Qm~WPTO~&UmlK`xAZ$kU3x3@Q2=7RIPk-olveM3WlqqFm! zT4HuMI=*jwvX|;~=y64P`Qb&DW6gc$$aoJth)8D^o^&WX- zczBomSrW6(c5Z(DYJj`@$=APw$t??DVVr@#sIA=8S@pf2cbqKq%sOx@Oy1bu_We0D zyin3gT!in$McBWi= z&(5@V0J)II43l;HT%$8-zWSZR;`h6DqAJ}A!yoIW3=a;V&+$nw#+gIy3!giOiIQKg z85^tP-QBA%Yb<|c5wICTv>;9SqoZ$`T`8aaTYZHyL40yX65M3^*3G9)BH*l_adUA7t0HlPENNJ1b2tzU*29~*7-6p6t}Rm zvooDCWJ{)T+w6y!fI3s{X;gAy;a`^1q1jnMo#~tKaAz$oEvUR{3aC&Yfv6C~W>CA9 zzqGWJ21*R937MHDN%xWPCWs!G3Z{sPii!;Z{{D?Z0s;jiS)zX4zP`Q#fV{N)Zub0Q z{63h^b~v>1m;EX#sP>D*J~ml&Q8U9Q-63Z27GEY?|duK>b|?TH$Go) zUuOq|0@GM7E7841!v)LOv@Rvf?TJ#nLj=26gEE~Q0-IiHBqYz?)HDKK<%*&lbx;c< zj*xl*!aKSy-m6!y7_>AsnM-vzZRkOdQx$4KVIe*iI(j+^3yH|$V-iT!LOBZFnyQ#b zF88)fb_8PI!s(X9AR00qxF8s{L;`Mf7&!V93)wcWgFgvOOiVU9jB*^c8G`ObFCb4- ztGRoJrz?y$ra{Tn%DX)*JUlL>QIbPwP>bOZ;a!xN)i7*&+N%zFJgRkcr8~UP;ziEM zW&bPh$2|z5XCq%D4)tB#+}u3BMMYx#BKitqxBJVZl^;B(h1nw1`FW8NAv7ICED%sa zu1UpuN+$3c2V(0Wsi3OrS*elVD|@Mw0|RxdcYoT0+G7pFHEkc?4hVjMXC6yjVf3}K zB_?Kw?b6i}p}22Br`a*J1Q_d?(yRKi41ch$U+}Q8tH}v;$Mi)&2)Ay!_%`h|6#c7c=zsIe9~0> z+S=N|_Ed!*a=N==*ax}D_c-u(^c_SzQL(YNnCmcD{bH+69kq*SuQeh|#+x^9)H!E6 zwh3P-xVpM#>C>%LP%}q9zT1$qiezbJWki#Mrt)KTb@lU>x0km|ASm~jgIxUrXPPhP zBd7&Y!}xS7jju>lZYYPjddt0Cf>Q{;fx!Eo)4ui1o}tcBkyLdEB}<^n^mhhY+HY1L zk9TM5ETO_kZmE~shT=W*Eza|e*)^M#>aZ~SfVKXFWo!j2OUuCLqw0&GB@t$Z;GNao z+79*|MdmtzY~JXg_fdmz7{eMt<^Sq}}(-0e`D-Yg*%S z&4NmME{1H52WkxlF}G=1Mdf65Uk#$yv%GWZm!TbR4wpI)Wcxg}@JP5&$H#h=4aKX? zLm6j5Aivgf3z0JPeQX&DeuWEanldzAUKib*Si$e&@Z z8?VSoEiGk~TO}!rEr1-T0@6`A$Bx5Hdr%NY^@lzc-9NWte?axkrp@=vkZ$%FYGI@< z6Zb}vNL599%?G$TS){h*F}c$^umn3_biTZ~I^7!gY+5UPY$$^Ahkz6f_y8ws59KA{ zAJ4a6i8LrI@Zw<~yk9Pd!}BOW(BChczZ3#0U{hx6oVE4!MKqz9gm}M<-bK>TV zpZp;O_$VEr0`?<@&~d<#MWyHPXJ<4m{Rv3wt2q5irY}qcQ9|$%{h^V17$8drb+1R0rK28L~dE)&5uM2?cYE{NJ1kt4XgyqtexgUjGd zxk&`pjTr~@S}=;5cLcX%KS#YUIabNiDGPR_N5ZxrD;UrSQzmF*J*o{Ep?go2#Qnlj zp|8P7N&WG)9he*f=VUrSLjod^Ts2`iMR9u*$$Wt6)UeQ(4azl3cRYnIVrv&TPFH?@ zMlQ6L%My_6#6V3V#o4(CM7=%QQRN%VP6AyP*q_a}d^jiYtiJ~_*dqdS#5W1A87da% zJf0#jAUSx322z$eKK3uw93I*n&^T{1HiQHJYCv)<*EFMeGljpuNIn}K}U9C zr3CDy@Um5ef)}b(;jS(ctk>L(SHPTRjFWz(8TT)?6zX|zs5!bbrJf{4Iz6iA;osGC zJ!xsi27RRAkV4Ebhf=hjXHqn%anE&g8+@{=f$Ijbik)~qdJ1VP~S<%p!pe>nJK%i3={nYD|4h-#umHpx-N0RwJWl z{GYK$2J_)wEwxH6BQG4lk2|m)93@? z%@Cs%bE;YEQVS+O94ZY!Rt`B1!jw7GjB#JTUWm^T?Ez?<+(y+;Cg%UGv^HS)=TG)% zs2!2=fKOPh4V0VwobxT;At;{lURI*q-{?DatY8TonusNX*SLbOkq*Dl*4ii&_H)m~ zz8G~sQ^wChl5qVuvqS$6UkH%D3fvCH^LDjY!;c0U4+qXqoS zU=7qM&vKdT%S)#p7@W+^Wjut5{HagXK=#M2&q&=|l^rDF!7o2@V}4{pqAEPOOv^() zWC7AknEd=-6Awc^XtkLabDAC|etDVOr7AG&UO~h%&gmI;gOutzQWbT&LvFQOL(DCny)%&mT9=Q1g&Hjy2UQa(& zQidXl?c{uKp56S}vQnl(;wIj$5Oxi&kuIZAJN=ksUHA@mx|0386f{rGsW5mCrP_}2 ziP1Qc1#==QqdFWqL+YZmspM-41Gl(ix^;J@%k0hqoJ2AowbSbM-WTwjB4I)zAAnY_ zL6!YkeG(6z`XwA1 zLG!M7S-DHA{P({Dj#$H~G4KRi-JPAC>nvPX*gQ zg3;G5=wLdipVidVyl{R$wXKU5fFAo>Td%ob`@1tl8d5B4CbE_|QBxwpw>}(^{qTpo zdA)t+57(GJ(?TyG;9bOx%cy-FnS^Ad-nv=sP`3517CThU6B|s%1nIE6Gg=&CtIssV z!6UGjm$y(MSudV?e06;-Cy5H}G%V49i?{yCzc<{gc;X31% z+zrk5c`q|MBU(U@U~!YcGtlqX%q~Gy5(yd#=kOTRAY(=Fg^iEX@p45kq8 zDDz2DwMFmc*T4FYxN@|S!8$a{F9DV049DVGI@YdZk)LP|!51kwDQyRbG9HyZ986pU zG$aVB1FV&*K=|!DBqStO`J^8}LSYZ(Q>E=Ox2$rx(X?>IGi10l?3_j*I~0pJ$2riw zyv797mHqr~uF1Xl6MO2tlvcE#KcE@yR{lT*!G*UVx%-C$p)O(nULFd6J?no>QRZe+ zmWYDDU8(0}6YQ#8%sQxygiZ|)9Xx6cC3tZH-%s!)A#_5ln+*QV&CDszugU^?&DYp` zTd%`<2Vd~*4u?CHQ<7!^7L@~s^UdePR`!yV>bDSto)e#0VxhRQOaV^nDJySvGrB4n zMCqEoq%<<3)*dZna}(+^9Av((ILgaL{M;cz7w=o(ov#n;jJa5Fj)uo#KQX%ip;Z;0 z=L=4KLBJ<|42wnC zRL+u?nS8z1B|NRT5)&LUQ9WJMrniTyc{_1>J=&@0NK1=mKsw_&Y{`!*L5E|C`Geg7 zq7kaa;O5Rw3vuN$-|?o~ZB&RkcG=SJo6{{VQqOt#P8ZOX$F_v@9{;SPqa*yerjbcm zQZ_0S3;y@2hK5GwG6RyCwlij8g=kL+oj-2J3!NDS;+U8jaTb2+9Gq;=mj*Ht1_(2b z1Q^JvsVj!sd=!N=2-i|@2~eC0f&{+?dvWQuB@8p1R3>UE9I3(MNhX=Ru6qIR*Hr1G z^q$SQhMzc)wV&a@7!V?RBM!fzu2lo-t$RJeza8@mCzuJdYn;ZvEha{JFqW7{&ip}r zVf#sgt{EyQ;_^#l^Ia}~e5m*y%X}vN0)AI(YkxnG$j@>N6n!;CGNiTToCYIbwsSHq z=k{k%QzlusJXe^;o~pJRd}QXA%-MrffXme(RWrSQ)Q6hBefvGQV4V7OBh=PNt&k82 z;Smf5zdS=_ejCsT#s5kf+kAe8Q~3g1ZHD!Q7Tqa7U{#uJWqslV{E>iv|1QA04#38~ zcx+GDlM>KYC!=Q{BZ#w2jBD;Mwwo0J`RztjMC6&6_hCoW!K<#{IPXJev@mZvdVc)4 z>+mAiegT5QEq2Eg$xIG@p!m!v>bl5nl1d>q{5_gPC?8Ne%$-;Phr8 zM24F3GaNyU`P&#ceuz)T@?&+p6unqjN!8WWT(Ba* zhWG4LAyhOp-M%0yAm)&S?=e$TA1xymM>T4u$>ikVbVQtYc6RdAe0V!z>*AkYC9e_sETkgMz8mR(Pn@MF^FcsKPiiJNgEs-G?R`2 zAd7OgsGn}^=y544K5B;(5=#O+Jp2%^y;fXY9eBZ_8Eu^jj8xzMQGZ~De5!rSi1hH~ zk&=iZiq4Nf(DH}M2sM*1PQM-kXq)?K)f#q79n@(*t;*0*ExGgoCy*gPad&wHCwy)ktMIQ3s1wNM+e`HPXYbs-JSTd zFg#1b>FMc5+HMDU|u3SpdU1>4(zUcD92l9d`Hk zvw}-hly~ z(;_E!ftrYLbMvFOw~;j%)uV2R6W<<8GXc!tAiPmI(v;Fr<>gD5|88kS^4+&@0_v(5 zky58I+mr}bs&GaQl(o#dZ))Nd1HZo8WT~jzS+CN+;?3Ti;!q0P&Ja-@ zZZo;IStk`X9^QEGuU|)E=f|PCUzbiZe9K0V+-P_oV)h!rFGp>h{1H}Kt@<)@zF+te9Z^r#*R;1SmR8F~`{OvPffNW6`UI?w+2apE5FxO*DLk)R7>vl>D0R zw6YFU&zJofFF;?2YG!7p7N{E$a`&=^IUx*dn5VoYWE$2sucWrV(7Yxh#7kiBsjZ3fBFJ5k_>Q;pI+$JgN=-@j8?O-d1^N?_2}|yWulbkRihB+Rgho z5|O_Zfs)Y~N<`&WgrCF%I)t;UZRWyJ-pN1TQ+lSZKRlwGbD1hmO_$9 z0=U6D3JMDSGOGKcLp6#?#-pf|nkRD&j(0c(z4P z8F~8z0|7@LB|)8#!&C&$=+~z5LkY!|GMt9T(Qi;Fx=?DaJoaKeEqyZ_3SRm0BS@bM z3q$e>*d{%bRYZv88h<21G*UQZmC3_J3D$>Il5k<5D9^AG@@~Y-{=z*jr%0+H60VEi zD!1Sfkb`FSMX8u{IaMkIUujDIl76i3-@j`-DwRv~iY#NkJgrxAIH?8Qr%!*KLT}#r zlcQ%=k@*o?wJ`5m-L^m6RVDuPiWJHb1Ahf(X>HAepFg%d?V7GkPs-BLQrf8z^W=HC zN~Vx!S%@#4=Br2M@-s_xCmNHkhA82G@$}UJQFPzm%Th}(BHb-1AdPfOC=CMAok~k1 zEg(orcc+4MO9)7Jhax2*-S1tW@9+Iznc10p?>XmF)7&W{lx{ZzSF<}w=s?t=Lgi09 z-w_z=*NRyLRKTviW8+bx55*6Ui8%}Px&p1av@w+BMV)3e6&%qXT_PZvjwd#Nce~xb z8ha=C>kHOMmUum|yxgI)UFuXClnhzTl_rJo|489B3kJ0lvlja>BTvD-bUfc@UIOB0 zfdC3un;{cy7a2Cv&BZu~LqFgJ5h3AJBfvqAQb=gIMq0hkDX_@UcOLtrz!T80oDleW zjVPu+qlV&@0tBf;<{B+5G&ClRUz_?`(RV!plJ5~5Co8Cz8L8P!a*^Nj?c2>PXjToa z$$5Rl^Yh(1H6i%$a0n6ksvX8LYkBqfJ6)PXO&t6eL69NJ8dMy*A_U42()ijBWmG-4 zBtd2nW3@@Ln)zbN?6EF~6DQweYM8_r7dWY3r;Ol|@otUWL?OrFEfuj&(FP=I{cE;g ziHnFd7Ykb)Wt=0Bf>|smt$dAo9Rgi_s2S`uph;zfa!7-Vg+(5cd>Vj+e3uJv^oc8Y z6ArmYsdf&MeP%DK0shb+zZ7+}T+sKu{ptUmFFK9akJ2TlI?@dt%}wU;41)?QH;A{P%V`EO;9zn zO&%T&1B2HFvrK%*3fnsSLuZOfUoqI;a%(ds6(<33d6ibgybt$5ApB5C*_+$j87k4Y zwcOOcMLgjFtSHre)KuR9(Tt8Cs+Y*~2oGF#UUC)S4`*ERU8e7wo4vaLAQ>bZC z?GD=zH{cll8~AF%P&B`va7~udj_>yV6br}Gsd{xgUTN4ozw2gI;v$nBfFGbukyE8# zXG{3r+7|)N%B6iLu6{2Oz2Bwz6#*NY2)~!L;ET*H7nWEB43-w?&}(tXRjLdcKMORj zZ*^pE+Y+o@2qWfwLB2|(_O>wob7Y6;=x7I=1t$~>7$AL$IO169K9Sd{@IiqqsVowm zTU)_oS^>{=N>H7XF1rH7SoGtY%h!x!tSj(In$1_sHKT(z6e4;^I=Z^A z`iv3t%2{3*#*PsVgw^#dXM&7hez!zzDTu)X_%Vrp0i3^Y=c6{}O9^W+cm|eQ6TE_r z`cN%VSPp`>j*3Nu-HTNR)+W!2_RO$00wnyv{QUfR7;8u)jN>e#*!rG169b62&nk3l)^WaB zcL>`0mz9K|Ms^@hF8x~T8fyGqDgb%pD`kXDaGo^=erKZFGrlLEm(Wd?dy(>WT@w3` z9pJAWzdEhltpIiCpZU6l=$`q-Km6ErRf&^ybTlCyfMzF?SRx0n5#$y?|9vdpX7?5wc2 zDzf3qC{jVe4oect|9KWqh~$@S7QH%KttvDevh*N4f!r2Ra6{zril{%bC*G>kaBx%z z=0cmBnlhDCRRM^+G!?PmnUMpgMYbG_ur%a9?}`b!<)RZ9V&mdWaPHHA&D3DZg4qUU zk&ywu6H|>In6urbxS= zI8f~kt%{0lu)O{uD#)L~&8AjH+rsC_b#+)XGo*QdfPLC2A(ku@(V2JGTaR zioYj*296K)r-W%vJrW`fz#@snXFJ&tUaW*g#15MbX9ZzrH7=X?le2AEh zgh%ad=|Ht1W9k_HkYq4hv`i;rwo%Vzl}_#7vXQ>IIi@X^cBWuEP20a@Fm{xz-pgN= z8l3qlu((&K&t0ZY@bn|g>HfRXdf*2A`s&py5gBh5AoS84cNQq|Jsww`~iL?3!A$~`<#a{rlQGx*rKK4xQt)Y~#iyfDv#^ZNaoc-~|!MC`r+WR35CaLMK7k2FHJGW5xpfC%B045%^x(v9`@v_fM@ z@JEC7pYqIO+z)};*r2$)>e>7>mFBmPHn{7Amc#DuTnERRz^w9LxT|J1E8>vXmRlv{2W63^ejX&Sf==cTbpzvWq_qKLL~w0#V#XDOSfFgBHsfw zo8^hG&n1yD@1-sRtv&2Y+`vQ$Q(XP?t0Ga(lP~Jc0U=2Lxk^iqy(*@L&i9nmg{357 zdshD@jJI{{ovo2_(I;B4wV&Df-la5>wyZmyBd=(-ptBOjs6vdP`$%3LnYqkA{3obQ z!s5zggNbY&BJAw;*fy6IW8?Q5T&?nOei{6y?t8ifw+A(w-r>3!v#g~+kgmfeFzOSn ziHtug1$A!tW*If>g1z>vt~!EM0OPSi3N?_Q?7#a8$1OnEP4nIaCd7F_bs4My&pHMy z;2C|kqz8mA0ul5QUtsy^D{;M4Z?WrLsxrp1yuH_&CCi+JRvfn2tv-)LmMjjk2PS^} zU~RwiouLS5GV`lk&rj)7Hki`Kbs&0dy8x0I;JNUUH~d)rZn%xw_Se`0RwRtTKfx@zu^2WsT?;<1wW-o@jYi@Gk)6|LJpdB5{G zY|p>+FV%b9lXqOuocUE+u4QO{a$0L%uhrI?HyQEi4iV*K(IvSF% zoB?GxD16^}K|UWm&;UBCZ;8-##Kgp$ALWi&adF89>R|>&N>~RpMv9#ah+m`8dICf8 z1eZbgaQR1B3laB_K1yz(O7- zWrj$0P*DD;`6v{Do&naDu4C3_BD!;l`F?%3xlt*3Sl`FT=kDC4NOghh_1RzB`nCI8)cNIAqzUur!f z!m)ipeh>`ic3knau%*<5Kth>|ONAeI{D}gVAr-krhVOsMzKF z*+L!DTam940)zFk{La+`xYx5Ab)dt$d_L4OU^plw| ziDo1fq8n3_n3SX>b)v%9dM6{d2vRL4ACtV?1HTj(B>c;$0 z6ZhoaZwBLa+Ja?HD#v6*Qx;BjWhLU?Hq>^lNiV?@l!UD$6w25@UzgHhIZXQnDE z9EZhfIGywc=%<`s^}Zd~s%wsG_E0oHFruah4=l73rYaLKX%JF5XNCYP4XGDBioHDV zB53-b_s%-H4dY9jOhC&ub-F3 zhP~NXU-vsaJ&peS<%>5DqVq9#pi$Is_j8)9&LJmi=>{Xm?w9J1adB!*M(ut|)%~KN zY~rUVZOD z`S2W#$P`^|j7|CLp6oRASD)3#Clk^rp8UM*o^eYDmNZuQJw`Z4fJ#L$jO`%qf} z)kaeMF^;el3JOyIG1)7+@E2Ne=8eN2-I1{f;N~l1zt#eA!xU}sHymtigMdV%;DLfx zC6qS&5@E2_QJ5hPdS~LkPbs4+Ue)P3+qLAMDSwvql+Rot+xa!qu~zL{F`Ud)F0vrM z96Adh$j!|aHiLdR-^*8wFQmE3H*Y-Aug8$^CZeJedk}+7mOcF@V?Jv3& zBe;^-X^K)@?*in!ldW%K)sO+{yBYy8`)+xhswYP-a_4@q!d{qghD_EhZI%|rYHEH` zsXG`Fra|&Wt5Ar`U;PzLTM?$8zQ< zK3#SZO0{VaAq_&wgrD*3d2@+o{c2G?LjDA%L4x7o;Sv)QlRQ`7_-D#e)!$iz{1H76 zn*x4u0-5VG86YBhMF7Vzoz-yg@=kN>?eV2{08w^?Ze(3^fpIu}b0nng^-fRDJ1&XK z^K+j@4kI*>(hJZEvLLS5UMYO;B_ZnnLGyn5cVAvM6SIANZ;enuTmOKc$hi7%;o8`; z;6IW;sjf#q^9VGW1`5+9%oj5j?u0?g5)vJlD1?EBKwLD|%R^wdjUhNdZy?sSx$!Hf z;77hC80|oFEEP~t#W9!HdoB;%+#~a#b*%mE!NfE-C7-36-!BCvLVw+P$|ND^VtsG5 zxBa_ZFbgL*4avtpBRzfJ)W2jg;`6lX&gEXSSTbrdQgA^Pkfy04RI&~`Y&QdN8k{64 zFk+CfKi-PwuS=Vk0#5|QZn;PFF^zb>Ha?BbCL!sl^g%>9uxXXLQU(nJm2q`RiMEqA z#tQo0JsJlMMGB#coOSv??E!!;=L3s=f7Q;qQCiYs{DEymnmX+*`fHrL(b5_|e1=md z@_oMlq*bHHqB%axxA{3)R{CLP&sQb@3r2Dv(!pMD^s3}RaC{yNXML_dU|*DfgG$?d zW7G5d<(JQ&BN3CfOur9G7~58o`lS$&rU3qn_YgYOI_ojhc5nuCqT^8smT09=S@gcA zT1#(jZM~Nah|bQ=_5!%Anl%$W12jqpAv&s&Ld*H6-5;F)+zl~6SD+l;47WASSUBEW z;{ST4?RU5_+ArIeS^ad3kT$&sx8g}bb=;hHa%I__qD6W53*WA5lmo&Rqr=S3>(HT* z;M@^^(Zl0VR3*@VoSPbP-`T|)(U}dI0!FW!_7MO}J@ou)CH=6r*+GYzjv=uDy=PNC z0sZ|>cOyh}B!E!0+|{&)fGw+$T;z9JFFu?3znb5UR*Ths>H{nG21Nmmln86_@hP1g zP{gOl6SY`jmyi-%p!`XuKHGoeE8C8v@5Gh#SZuC(Gyn|^0eE$org6%_G;N$u*a<3G z_CBBwGF${fZIWLoKn6Y1O4m1Q)2r3Pc6%< z`A-f2tR9U(O1p^3#Ks=}R3w)R@h0brNl`l8U-Gs9!4Z7t4-y6geH{%A4M$9{3IK-8 zr^ZQK07M58%osE>Tog+9sIjkbKZn7`8sLdoFWF_bg*^{f&p&IFd7!bGgM72?2tpnw zrvvMkv>G3ne<8#X#stQAc*!Yqt=oSs1Mk_DCZwp%WC%jFux=vpEl9{7$WU_5<`wF< z_8DMel@PLOHhix?bOq-Ax6Ue^{J7OyV;nsH=Fv&IYMY5dqwK;gAs0)oxt>9}h+Bv1 z5ge)nKD+6bfLo_S-35(L{La5DJk2BJvkJLypD2nh+&2JOQ} z*iaqEKg|&bEzO&^ZqCjM3kZoz60cHOT3~+)LfIOnRSD+yK6zPLx6qMyJkh<~3qXwF#p+Cd4FlcWw{B9H7e zqKHEV-FH@lX-}kBZGTZ~cXasZA#*SUO+)J_sjC2~5=VDd)a$Rg?~%(%24V%#BtwSS zjM4GjL{Z^!?^#C_gBJ~3JdXkZRBseCR}9>5{K{m&gpzU0P>B5R*MZ9=FhH_+QIh`t z{W~An)3MeaHlnJiSdwq<{)~w$@B)my2uF`7$k!6DvBt{sM?vi-!CZV;8iUxxw2(w^ zAb?k0lciBose0`D>kBp9+T<)eQS|~|>$vpx@sI{^YE@qV8y??f`TGH|Nx^Xx7zCsV zeh9clns*b(u=nQLe7)s+Q(kxG=d`wl>am__@$vEb;@Od5-sHBL$#T#`g{2{4+MY;) z!otfd%^pr{88)6`d?z4L>IZGYRE7SzvVlQ*Q88)wA+OucBnD{u8h;}+C;Ai}C{nJO zV*g4ExOA};3Hn9xjESy9yDBC7HMO+R&!`FQ`et$#>nJtgc{+S&Y~tq^51`bNVr5NQ-G{E z`<)3s{C(go_9r6U%T{1Zal|?l>9d>y%yM~wGR~+wT`E1Dm6>XeGSxF_#Er))tpXe9 zlmR&Bfwj_}LjjK{n6Gz$*nYr7AD7&vFVH2OF;t{X zm`URQ|4ILBV<0{j7l=3fe!~hwEAZe2YTHqb7~DlL(7z+dV)?`ygWbFn@Uk94pw7y; z2!m3QdGN=n-<;hHNY}&JRk{(ACzuQrEo)oRSF0B|=+1}JR=tw~h!+I<)u%`)>yjJD z;*Tys=pZ3f&!)}twgl-VkM&?SU;J;6O8XB8V+jq3er1+;>uw^d{5Ik@sz9#2jGxGr z4{dMQE&?&Iu<`RJEkhvUXiR}0<8FkexF67Wt#B#QoCJKe_%n@-tS5;Ii0eHHM}e=} zb_5{#ciQItpHo2%F7#;Ow?X@{*5Q-);o%qW-mf&~3g3+^y9LO;ZgJY4ts$0SJgY43 zlmZFcQH-;BQV$t>iP%WIiAPBYMbKm;W?!hW!{}g-)MX`5BqdOh7$Z$*7_R+a`MtVJ z9(-Nz;VrP(`TO{SyBOGqGPKEQC8`hvQGP)L#rF zsQ~z>A4;Z}?@L5L&6mSgyOlQIae&8L!j3AY{bcTzkS{{sEp?si6t7&i>)#zbzBrd2 zF6uB7zUPp%f+7C@3kZ_n0G(~ZS9zvqii$QEE5ftE-Q{W%5B$3`iJ%P8ADg*5so12TH8j3zr>$c4< zzF;@Z2TEpnM3!N&D5e&zo|gfGu){EKWN6bP?X+hEi2J8OMT*&AMJNkCG&Xvn*|l@9 z?DRhk$^XcvU9KAzLaGd_i;E5#2HdbKbUFOdukCFiJ#wNH9w%%=29$pf?U5Tw^&^qj z>))&ybbC!5fTz_Cpa3dLhTMECkaHxNlKrW&88;P$lW3ub;5>N0U@d)V^CCC-MLau~>j(Z#Z9PP)XHg^S>jGIjG=YjD|1gQSVa&#R|lX~TN z)DL*=ruUYCsLZ=mhpbK&yu)8mq`g|&-n=JNH9>F>A!8yqi!6&id(s1)pHrLl z7@dwxnv|fiOkRFAoyC!=M%KR{Zu}lXgbnEJjAfvcL1DLsj~&i^v6`B*%^|aab?|}1 zoZc86*uaZy-tgKQ85yMk&gOSu&!oAimWx#}A!5CWKy^tvs<7UNfR`Ji-rVs3#kjr- z?8+K0WSOAv4dfs!P&5eY$81 z-n%@$CQ2Z0t;Ga^$)7j|GW%6#wyc2yXm*fP+!tK%#et+GHC+Q%-Hgb8Z~o8-1^z7; zh!|~vMV^YHkDDCZ3DB0Z37QDQ`Nlw`Vy{1bk}nfK|1@-#zBs(1;?IFOD=qosrvEMd z4m_m%g-)qHVCFReBTkj?v(@s~ipkvP0h!>0hT>rH`H4=ZtEY6^gN?|4RSgmysd?p} zj0c0;t|dZBk)v5R27XfY8sisMAmb?_1f~oHN;E!=L`BA_lKQ3vzq=c+Xuv!U^_k9f zb*&cyyCO2QxmcgG?)iV4@5NXAfOa9D2GA_P!<`O5bH{HT?)^?onZ#rO7T^21LyVD` zxxPnWvA1^8M43pLDy~myDvHSc8>oYaY|5NqL}?{^FJ(zNK_}TE z%EjeOgXWRqH3S%Gw!a-VYQzd!|2=Oc99hUpJjg6?>O4nXA@y<6ylwqG;l@r9cZV)1 z0mlx0PU*n%F4_vn4Wo5E=@e4FZmGO|01Xa6qmTXcX%3|jK5L^P%Oe+@ zgWsgAqH}5H(G*a5n1^Pl?s=~Jt-dR?i4)+%|pN!_Uo7c-L3AOj9A!G`BRg0?>bglfzMb7L0^vX~FRF##b$%KB)snJMDo zn8HIwpNfxmFBeT_0RoxNWc0WUU!b9DO?Em`_@D!RvkQo_^?hG%l|uDw-~D%Wh*#_0 z-d+nJm@(G~D2a(@w5Ez_C?OA&>Bo2HSDzz|+nUB7NNrK@F7;LEknS0D$rTlXIUm-AV5G3>z8;K1w+7y1!_Mx0a%A+!HOljgE4Wr|=U}A~>O? zM4VcZD8?TH0*Oq_87U)sREU8woY`)CKQ?G-vT416xSvq~wEywOQlDVa6m6@Y*Nx0DUt5Q9Udvq&!_xG7QP!MA3yw%X5WKTz9h(%wS7?BF+4TphJSc+ zc9y9--}Jg<_oW+OsiCp|U8{r5?!PiAp@_079~wLk?9#{YSWuLS{VvwRd`YauWc|vE z%_lTfP+{g)R%vunOmy?{PY5b5HW+am5N4_<034& zJBpX;q{4{Z{4xN-bOTE^0%v(HB5&H+)pb#(5G&Pf!}$ZCJ9;}3pzrxme7r6WO{9N} zf2^hbulS70q2%V}H7_hIP$Q7^CjbYR1|{Ls80gkXZtyTLw#%Y@S#dJfK7@T+grx&o z@5I7p>+o#xXB!_*bE~uJ-NgrS5ok|A*u^PK#e1cY*Ol%QSby0xP%gvSCb!+EDV)Y( z*zx`;VlX#4?H4$#l@KVGhs;~#pDm<7l!Ye-{m4{`$ty4WuK_`v$EGOo{8BK9mY|`b zQNv$g$E6M0!Qr$Yf`L_tMnhwQ!rt>s=5xYZFmBeU-4lDeymA|eIQ!G$&}pqRD3{3N z=H_}|xon}j-qUCBgw{8&32{4eD8^5sVKO8EChs!S(-(2qjh6$e8;OXBUIP_%#bhy* zj{}Y*Xm6HG(D}+(8_B2(M5bsH%(81S|F1|}!8)q!0d0<6qj&xN@e+y9kJYb0;KSyJ_ACMNa3d2oc-f>NzP{2^zGqs_E)|Q99Pzx z0<+1jjH!Gr`E%5K>>ulb%Pfic=K;frqxdC$1BiA@22N>|ub{gB0cwK0pEwW0f6fAF zjD9$iZYpk-Eowm&UyS(-7^RJ3%-6w`W#W7oN@_>C%j6dJ->E18mV&+v4Hr8_Y#0Gj z*H1ol4N(~uK(|=QAkN_Ch6eWUBO_I8RaAymFKEHUNXp*>XbF-IbUUvUle@DWMc3O2 zec~Z9GaL7ckCnsnOV0|O#Hdf>S03)BN}@R=70Njqs2sU>WL^%Z)H=$e&8XZwy;T9b zr63q0wFB(!cd)%2lLtkxo@gX5p!U&UJ$(6KLHpSfT@4SsV}BE*5Xzq&tlYl=d)=kx zNosujgn}GllMLc)1D$6tetcV%x;qYdX6IWCY~&tG|@-8gT*N z_B9MLsA57786S$GU(leVpkT-LWj?bBRmK0W&$i}RxEt7xZ-G;c-A@6Q{q)_=Vx zUX_k+jS8i}j;qa-%t&Js;ovQwyzZ%Dq4tiL$!kaKWh5vG1tJRuyAVs^P1KELDI7~9 z6ol3ofte%%(zXZn}w%Rxgj(og}@O87ut&9ZKK*~FY2*m0L9G9R0{$6tRTACCP z&0Zu|ERC*w_bw>t%8US>)&#m`}>*$>R=Xg7<*jI0-_~=d1Kc_cAJz@ve0L9SJXRk%o}Ch&$3DX)fjE zM#idmcH}8D=eskHVVgeX=4@e_+f#o}PwPqmVYdtpwCQ3fea!pfhL#F7-HAq%Oyda# z?@%Fw@uo^~Qd0lBaiJqK>irn~_iZ>=`SwcJ#w>bCexT6|z=U^H>H?Tr1GK*$sLd9v z*6Nl7h}uv?9;PT3;+~9=C})JihADplIswbhBl7o-(?a^ZWHjnA|F#*2G-Mw~Aqks} zOm`w7={xOBI=pybeqQ)tV3}Cs%bP)l=Y!c=@0(y`K~HNDEv}Tnv=C?hz{yo8hT$}$ zL$9fFYL~%Scrb2{MQ-8Z$1?_$%7= z8^$!XQnJ3+i6AYdW?^yhsdVDQR`r1wjFPa9mNh`rcZ10qcL*f8zLvDMkc?Kh_+M{m z2|O{lSUf*D-bK(7*$xE{;s;MYPI=s6i_BsYOD3n9_{NR&JC7V7Zs-Pl^(lp)n?d%QH( ztL>ythrWb=`w2z`=I8+;w#sSoTf}6Qe>b5@`AbfXZGOjz zLc~+yStK6n#{|f4s&36krz=^_#SQg`acG1jfEH*$R9Y^|2{F#XTu1E-p^mVR)hRzaCKv9^fqv z+(Y{(e-;D;IXNrKV!!In;9z22z1f?sVTNm(1Gv5UV|@HV&9o%JFOyflfBvk+Z+iz6 z^DD3^{HVFXmJ{JPBzd&Gm+j6+3HY}@!UEjixhBUh%W`{CgCE6|G5%F@X+4fhQOOC= zxcwUf$^Fy0hmAZpUZ9l0$9+ZR?g0GqesYjFU;>WfH1a66Kj#O!iR16_%+$eN1E8{q z0vS?s@*gUVga0130SbHz!>=3o37UH5TP3H8jo%Spc2?6!)_&jU`|yF$zZjD+)|&{U z(+Wga5s(*_c4TaPX4@npoa)Vu_Kq_=ekG=pZcX85L*Z8Kx>}LIs^~{`VZ0-~er!fQ*M+7REyz>4B0Hu08nq`TsZa75qvs-zKps^D`dh zcU;^;Y3~NQ19|VaZ&kIY!ld=NX)0Zl;h&#SGQLQpy4M14)eSoQT17iaIub#m2?2~ z|IR_Z$}+GfW#*%N*83t0cBxDmad>|a? zyT*&TBUy6#fq=iyoV`dBkWJRl5}K_7es3D@SgT})0CE~oPX7;`$bWzcYon-swv$j& z;?Uhr&6It3Ix;k5pNlDaR6%4^8+|o!HU6+_NRRQyOvCRrKBC$uxDyqk4aQEr=ygS| z>ax@&I8qW^P%a#lIy_eLnD!v@$yhY5SY1865O8bvJnl$(r9B0ec2iTu?t!JB05OzReT3R>gxRK*1LZG|^CWM( zFeU)l*_>jYQ!ZITchCT0o=MRJ#9M?dT+K8-oapU*Wl36_U%bcHqQ8{SUVclQg%|=v z0S77gTTeD}njHwyu_^6ms2L|+u(gTl2f`sQc<1z?_vy6KQEyMNBG8Zjbbsqk!khlYlz z&i0qC$qu{FOuVFMfw|&|I-&7?e~?y>%cmNrt#86ja!v~zu;n5a{BMI3qD5^yGLSuJ z?y)iRPYv9f0qmaS$VW1nJOTnEG75$<8&Ojg-xgfrucvwU&bovyH;C!DJMt^%}y4@AfDP)nM{F7$MFBR4xHPSQueWc-JNdE-mWUIO)NFK`wEiAFPFAm`QM0Hbi3ew~uw}S4z^Yu%D;1O>NH%YDm2!XQO`m0$9Aq*y|6N7e`Tt9NU`N2C9i`BJCk6q4dnFT z)`E{{g-o1-N%3o8etxYvO{&blrZjsD)}e`KPpbRm!XbV;WxXEj|AQ@oQ-`fAj|H!; zukm>J_q7|L!0n&sY-`?{gkUEf%oyDy!nfHxWhHJJPXr@QB`y?Xy zpB}bsWF;MPUE9&HaAxv-Pu5HGu{ci~kSZ;F56B!0(b~ zz1VUDGnf$XCtH~g@)iA(an0&%Tf|zINo1f}<9|>NNSArD0m8HmAxgNRtO=2i>`%;) zA0llAGu>=rZm6iHKck8fdLVUtt<1YnQnYf?di&|A_xDCJzZFql%k;(^M>RLBuzch( zRNv}HY~e3g{*<9PXb^`I&H-5$LjrvD9fZpG=UInwAjUSBhno&dbf^Y8?uW<6c`(=J zweVj+b;Y0K@QK&%hh?GHRggMmYpgw{%#%APXQ~ixdQ4+sO@#bq;IH$F=9v6!b+-ou z$s8CK6L6la0BUwpM|NQn?H@Fe8VP_by`Ety| z&CL@0+|{P=#w3KEs3`m*!aDimdzLogmLJ0NlWo%7;W4=AEhF|+g6&C_N6ok~Yu=3U zVR^UWoT>*i0}Az{-)vPT>!6i63TTz6x3i;l(-#_kwWORn(z1U>Aj(Yu^zRb&K#ZiZlGUG#{ zQolzpVY)3>WuRZ=velVhkxPSrBYS}Caa~1Ccfa6k5+ypzA$` zg&(nW!ROWnyeL+GMiwj2>S=z9xw?$TA<|}Q5)uw|Tk*KCyt1&W8ZA*!jt*H65+M-i z&TQ{xQW7kGBP(y~ucLqO7*EYf8MjFIF^|bmn;v@jT*lfaKNX~~O4I#a8QuR(hE!CD z5y%q^Nsd?F9gh<=TXLsLcN440W@|%A9Tip9298-~`jqZ)e0tTDR`2>crY)^y*A8Ea z%wfb-6s%VQrc1e>N9^wfjS0x&zU)qwCHr5UY{w0p;8&tYT-!5$b(m#znLqHb3~#1` z3h<^UmAj!5)vY7U~6m3CRWGI)zX-3o^%s@o$N({79#zBqg@MLv5SjEQAlXw z+x82-UuKWR-V|etpg8+i8Z?eOfv?Xoubqe@_=E4?zdOv?NPGmZS3)1Y+TfaprG(CD zqu%;TGHqYf(VeT}L_)ruSvPL!|Fi&WVL9yuLWyTT55ZD1GW&wDw#u>;zWG!w9BFlH z>jG;?x#rXU!NI+XjX2wm1iED=Ow7#5sl6QtgGzIG4mtcjt&S~!_5DJ=lI16i+CGM+ zv$|}oK-h|IsqvU1p1(a(Z+Y&!LVJ&X-=W9QI4jG%0IQKIT!IQyX0+4%4FY`}1OX#j zOO8L^y(5KneP&x=>SH8#+;-T~`LVx8dXzz}=527d-L$d0QeCgM5?4 z!3rDebH1CQKM&8{O@YgLpV%U0J&`Z@iQ-(+S*N<1p607tn6Vo8OxqK*cq;bkYHKiQ ztck0B-gY<20%mH&atkEXv0% zyBH`WZlB^1G?gm_Jzr?w%ai@%Ec@Hme?{Wnz4b`UD(hFK_0y)WeyMYl@8u(M@M-fu z$)||W%?Xk0f@bo%!@nlqyZn{;kKznl+pXB+ChPPZg%BreBN z1~!7Q@LJe8XC=XHg(%Eenxxafm@NF;8gQ9%{b3^r)FexfeRgE$BO;hj%w2?y1z;jG3e<0rwEic@EpHTrl0WyI2CQ@b3|$c#pT5+u9_jbX0_O7q_{s->hC?oy#kJ$&H;M%G%|k?heAnw+4*y zuJ-iM#)a_0ejS`SAAbo>qe%WZq`>;YLtK%>01ocpsRI;m)LHnN+ zk+LPc;khr4<2-}HXn1aRFU4G%F2X-nupzTfsZj+2)>JpregH9vYqc?!mS96KFE8Ej zeKAblTryquLa_WhJRcWvpLJI}_psOeBladwYjo^al&zok>}lB;E$)-hyXh{8bJ;&} zVYe<>8P@Fk$%RpwnnkW~7!FQds15#*1_GgA=H{+hOw}lPt*(H{0AnWExd39k{vB7EM!I!)8a1-~CavXQP5;UETVG+PPayRvjvqCci| z+bpYPBrzX>fU741KXZDTy?#%o(gIxUMyjaN*HnIZsZB)in6hfs@o!z;1F|s4IdALB z!vjY%!x^VnR|_FOoq94JC@JZUc5vlM@fb27{Ty6PNNTgt+b_z<5(@vg4RDPjYa_LL zi&t+m`UB{Z-7Ab5JO{%4#4^%S%t>~(ZnVZsRiqyFO+C0QXk5OuuCTt=Y&*f1_`D0a z-p75{wiXtI;DdrS2fQW}CK1?u7Nk}C_HP=y2gny};(8x=eJpr}!u4up&dO{icko2_ z_x2wT@-04Zf!{l6_>A8rE}5S<_%r%$e#ZNRTUC;#<~wwRRmx*14pc+o0GcQ|*_}Cf z=B40g@L>7Z(bGlFmey8g&}l!(1$hwXAg3bP^jKHFrlTHz8?AcG88M&E&*P2jM*~?< zoX*fz;a92JStJxw-Qf#V(IO+bs|NjzrhHD463LeO@WB_Vbf#OnmClU*B;j z2cqGnhB-Dt3%D$csrv%qgWx5VpZ?n`?f1U``Myclv;%UthaTF>C}EjBJtcGW>9-E> z7G$HQ;0TGFoHj;j2)f0Qcg>=5J;R@gWV>h$R8eV-+F9{L`OUhz!uXaLyU9_Zf3;yp zs)?M4XqueB1*IxzJ1zEWapf6396VY(2EKNy*$tt-!jY_0(ij@YY2m)nQs-=k|>fQy=d~T>An1Uucgh?PIR zqN2b{)|p=-wfv4B!?MAzY*Nn`fSy6)RE%EL)~8L;hpzO z*({Iqu@1A(9<$1n&IkJ4%l%jU4&wpt#N@oSLz#XIzvLYRJYuIuKEJ^g0-r2QtH(Lu z{kw+>z~~M^4ecxDIlPlUJLybzIQs0*klEP%G6qMgl;88emf3PrkzJiI$QBetzH3{X zd|T4ymsYPDchI(0_T)`|qpQH*DkuMCdA(iYlpvEuiRx0|M)G@J&TDisC@+++C$+qQ zz_5GPDQ$uTBRcrz15g$vt4Hl83V&qG&gx=0jDB*}d;(qEpg--cr6%`dxb~HQt#+`H zYP?Bz9o4r zZ{a=$m-Ma@$YdC19Xk$^5AWK;=ns#?3W1w0BkV(2?dN$P+!2qYw8U1EUt=SxPk#@M z5i6_aoj=c-&8zHuS4xCM0R4OK@ha_Fu_;*;MT}zryWlix@Umh|VCr;#rdG%&VuT5q ze_bj`|JI;+tp7Swk6KvCPGed~rsaAqjQXrI-w#MwvEs0vX?rzkx8ZkuA%FOA(jPQ( zMKY3R)k4J%#X!U93C;A<(V6L~CCzd0c;0hGH~-3DaU;gFK~`nsAnZESXXd2r2W`#B zGCxr}6Kh^6{k#~E!wUv$%3i3APM6xI&6~V~{53%w8~p%f)|?CBL!&#AT_~<9f2?ve zkghfUrB4*EswO+*b&BJkPE_wFVyXQ{0|!MvBZ z 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 Note: by "even" bits we mean the bits of weight an even-power of $2$, i.e. of weight -> $2^0, 2^2, \ldots$. Similarly by "odd" bits we mean the bits of weight an odd-power of -> $2$. - -### Ch function -> TODO: can probably be optimized to $4$ or $5$ lookups using an additional table. -> -$Ch$ can be done in $8$ lookups: $4\; \mathtt{spread} * 2$ chunks - -- As mentioned above, after the first round we already have $E$ in spread form $E'$. - Similarly, $F$ and $G$ are equal to the $E$ and $F$ respectively of the previous round, - and therefore in the steady state we already have them in spread form $F'$ and $G'$. In - fact we can also assume we have them in spread form in the first round, either from the - fixed IV or from the use of $\mathtt{spread}$ to reduce the output of the feedforward in - the previous block. -- Calculate $P' = E' + F'$ and $Q' = (evens - E') + G'$, where $evens = \mathtt{spread}(2^{32} - 1)$. - - We can add them as $32$-bit words or in pieces; it's equivalent. - - $evens - E'$ works to compute the spread of $¬E$ even though negation and - $\mathtt{spread}$ do not commute in general. It works because each spread bit in $E'$ - is subtracted from $1$, so there are no borrows. -- Witness $P^{even}_i, P^{odd}_i, Q^{even}_i, Q^{odd}_i$ such that - $P' = \mathtt{spread}(P^{even}_0) + 2 \cdot \mathtt{spread}(P^{odd}_0) + 2^{32} \cdot \mathtt{spread}(P^{even}_1) + 2^{33} \cdot \mathtt{spread}(P^{odd}_1)$, and similarly for $Q'$. -- $\{P^{odd}_i + Q^{odd}_i\}_{i=0..1}$ is the $Ch$ function output. - -### Σ_0 function - -$\Sigma_0(A)$ can be done in $6$ lookups. - -To achieve this we first split $A$ into pieces $(a, b, c, d)$, of lengths $(2, 11, 9, 10)$ -bits respectively counting from the little end. At the same time we obtain the spread -forms of these pieces. This can all be done in two PLONK rows, because the $10$ and -$11$-bit pieces can be handled using $\mathtt{spread}$ lookups, and the $9$-bit piece can -be split into $3 * 3$-bit subpieces. The latter and the remaining $2$-bit piece can be -range-checked by polynomial constraints in parallel with the two lookups, two small pieces -in each row. The spread forms of these small pieces are found by interpolation. - -Note that the splitting into pieces can be combined with the reduction of $A_{new}$, i.e. -no extra lookups are needed for the latter. In the last round we reduce $A_{new}$ after -adding the feedforward (requiring a carry of up to $7$ which is fine). - -$(A ⋙ 2) \oplus (A ⋙ 13) \oplus (A ⋙ 22)$ is equivalent to -$(A ⋙ 2) \oplus (A ⋙ 13) \oplus (A ⋘ 10)$: - -![](./upp_sigma_0.png) - -Then, using $4$ more $\mathtt{spread}$ lookups we obtain the result as the even bits of a -linear combination of the pieces: - -$$ -\begin{array}{rcccccccl} - & (a &||& d &||& c &||& b) & \oplus \\ - & (b &||& a &||& d &||& c) & \oplus \\ - & (c &||& b &||& a &||& d) & \\ -&&&&\Downarrow \\ -R' = & 4^{30} a &+& 4^{20} d &+& 4^{11} c &+& b\;&+ \\ - & 4^{21} b &+& 4^{19} a &+& 4^{ 9} d &+& c\;&+ \\ - & 4^{23} c &+& 4^{12} b &+& 4^{10} a &+& d\;& -\end{array} -$$ - -That is, we witness the compressed even bits $R^{even}_i$ and the compressed odd bits -$R^{odd}_i$, and constrain -$$R' = \mathtt{spread}(R^{even}_0) + 2 \cdot \mathtt{spread}(R^{odd}_0) + 2^{32} \cdot \mathtt{spread}(R^{even}_1) + 2^{33} \cdot \mathtt{spread}(R^{odd}_1)$$ -where $\{R^{even}_i\}_{i=0..1}$ is the $\Sigma_0$ function output. - -### Σ_1 function - -$\Sigma_1(E)$ can be done in $6$ lookups. - -To achieve this we first split $E$ into pieces $(a, b, c, d)$, of lengths $(6, 5, 14, 7)$ -bits respectively counting from the little end. At the same time we obtain the spread -forms of these pieces. This can all be done in two PLONK rows, because the $7$ and -$14$-bit pieces can be handled using $\mathtt{spread}$ lookups, the $5$-bit piece can be -split into $3$ and $2$-bit subpieces, and the $6$-bit piece can be split into $2 * 3$-bit -subpieces. The four small pieces can be range-checked by polynomial constraints in -parallel with the two lookups, two small pieces in each row. The spread forms of these -small pieces are found by interpolation. - -Note that the splitting into pieces can be combined with the reduction of $E_{new}$, i.e. -no extra lookups are needed for the latter. In the last round we reduce $E_{new}$ after -adding the feedforward (requiring a carry of up to $6$ which is fine). - -$(E ⋙ 6) \oplus (E ⋙ 11) \oplus (E ⋙ 25)$ is equivalent to -$(E ⋙ 6) \oplus (E ⋙ 11) \oplus (E ⋘ 7)$. - -![](./upp_sigma_1.png) - -Then, using $4$ more $\mathtt{spread}$ lookups we obtain the result as the even bits of a -linear combination of the pieces, in the same way we did for $\Sigma_0$: - -$$ -\begin{array}{rcccccccl} - & (a &||& d &||& c &||& b) & \oplus \\ - & (b &||& a &||& d &||& c) & \oplus \\ - & (c &||& b &||& a &||& d) & \\ -&&&&\Downarrow \\ -R' = & 4^{26} a &+& 4^{19} d &+& 4^{ 5} c &+& b\;&+ \\ - & 4^{27} b &+& 4^{21} a &+& 4^{14} d &+& c\;&+ \\ - & 4^{18} c &+& 4^{13} b &+& 4^{ 7} a &+& d\;& -\end{array} -$$ - -That is, we witness the compressed even bits $R^{even}_i$ and the compressed odd bits -$R^{odd}_i$, and constrain -$$R' = \mathtt{spread}(R^{even}_0) + 2 \cdot \mathtt{spread}(R^{odd}_0) + 2^{32} \cdot \mathtt{spread}(R^{even}_1) + 2^{33} \cdot \mathtt{spread}(R^{odd}_1)$$ -where $\{R^{even}_i\}_{i=0..1}$ is the $\Sigma_1$ function output. - -## Block decomposition - -For each block $M \in \{0,1\}^{512}$ of the padded message, $64$ words of $32$ bits each -are constructed as follows: -- The first $16$ are obtained by splitting $M$ into $32$-bit blocks $$M = W_0 || W_1 || \cdots || W_{14} || W_{15};$$ -- The remaining $48$ words are constructed using the formula: -$$W_i = \sigma_1(W_{i-2}) \boxplus W_{i-7} \boxplus \sigma_0(W_{i-15}) \boxplus W_{i-16},$$ for $16 \leq i < 64$. - -> Note: $0$-based numbering is used for the $W$ word indices. - -$$ -\begin{array}{ccc} -\sigma_0(X) &=& (X ⋙ 7) \oplus (X ⋙ 18) \oplus (X ≫ 3) \\ -\sigma_1(X) &=& (X ⋙ 17) \oplus (X ⋙ 19) \oplus (X ≫ 10) \\ -\end{array} -$$ - -> Note: $≫$ is a right-**shift**, not a rotation. - -### σ_0 function - -$(X ⋙ 7) \oplus (X ⋙ 18) \oplus (X ≫ 3)$ is equivalent to -$(X ⋙ 7) \oplus (X ⋘ 14) \oplus (X ≫ 3)$. - -![](./low_sigma_0.png) - -As above but with pieces $(a, b, c, d)$ of lengths $(3, 4, 11, 14)$ counting from the -little end. Split $b$ into two $2$-bit subpieces. - -$$ -\begin{array}{rcccccccl} - & (0^{[3]} &||& d &||& c &||& b) & \oplus \\ - & (\;\;\;b &||& a &||& d &||& c) & \oplus \\ - & (\;\;\;c &||& b &||& a &||& d) & \\ -&&&&\Downarrow \\ -R' = & & & 4^{15} d &+& 4^{ 4} c &+& b\;&+ \\ - & 4^{28} b &+& 4^{25} a &+& 4^{11} d &+& c\;&+ \\ - & 4^{21} c &+& 4^{17} b &+& 4^{14} a &+& d\;& -\end{array} -$$ - -### σ_1 function - -$(X ⋙ 17) \oplus (X ⋙ 19) \oplus (X ≫ 10)$ is equivalent to -$(X ⋘ 15) \oplus (X ⋘ 13) \oplus (X ≫ 10)$. - -![](./low_sigma_1.png) - -TODO: this diagram doesn't match the expression on the right. This is just for consistency -with the other diagrams. - -As above but with pieces $(a, b, c, d)$ of lengths $(10, 7, 2, 13)$ counting from the -little end. Split $b$ into $(3, 2, 2)$-bit subpieces. - -$$ -\begin{array}{rcccccccl} - & (0^{[10]}&||& d &||& c &||& b) & \oplus \\ - & (\;\;\;b &||& a &||& d &||& c) & \oplus \\ - & (\;\;\;c &||& b &||& a &||& d) & \\ -&&&&\Downarrow \\ -R' = & & & 4^{ 9} d &+& 4^{ 7} c &+& b\;&+ \\ - & 4^{25} b &+& 4^{15} a &+& 4^{ 2} d &+& c\;&+ \\ - & 4^{30} c &+& 4^{23} b &+& 4^{13} a &+& d\;& -\end{array} -$$ - -### Message scheduling - -We apply $\sigma_0$ to $W_{1..48}$, and $\sigma_1$ to $W_{14..61}$. In order to avoid -redundant applications of $\mathtt{spread}$, we can merge the splitting into pieces for -$\sigma_0$ and $\sigma_1$ in the case of $W_{14..48}$. Merging the piece lengths -$(3, 4, 11, 14)$ and $(10, 7, 2, 13)$ gives pieces of lengths $(3, 4, 3, 7, 1, 1, 13)$. - -![](./bit_reassignment.png) - -If we can do the merged split in $3$ rows (as opposed to a total of $4$ rows when -splitting for $\sigma_0$ and $\sigma_1$ separately), we save $35$ rows. - -> These might even be doable in $2$ rows; not sure. -> —Daira - -We can merge the reduction mod $2^{32}$ of $W_{16..61}$ into their splitting when they are -used to compute subsequent words, similarly to what we did for $A$ and $E$ in the round -function. - -We will still need to reduce $W_{62..63}$ since they are not split. (Technically we could -leave them unreduced since they will be reduced later when they are used to compute -$A_{new}$ and $E_{new}$ -- but that would require handling a carry of up to $10$ rather -than $6$, so it's not worth the complexity.) - -The resulting message schedule cost is: -- $2$ rows to constrain $W_0$ to $32$ bits - - This is technically optional, but let's do it for robustness, since the rest of the - input is constrained for free. -- $13*2$ rows to split $W_{1..13}$ into $(3, 4, 11, 14)$-bit pieces -- $35*3$ rows to split $W_{14..48}$ into $(3, 4, 3, 7, 1, 1, 13)$-bit pieces (merged with - a reduction for $W_{16..48}$) -- $13*2$ rows to split $W_{49..61}$ into $(10, 7, 2, 13)$-bit pieces (merged with a - reduction) -- $4*48$ rows to extract the results of $\sigma_0$ for $W_{1..48}$ -- $4*48$ rows to extract the results of $\sigma_1$ for $W_{14..61}$ -- $2*2$ rows to reduce $W_{62..63}$ -- $= 547$ rows. - -## Overall cost - -For each round: -- $8$ rows for $Ch$ -- $4$ rows for $Maj$ -- $6$ rows for $\Sigma_0$ -- $6$ rows for $\Sigma_1$ -- $reduce_6$ and $reduce_7$ are always free -- $= 24$ per round - -This gives $24*64 = 1792$ rows for all of "step 3", to which we need to add: - -- $547$ rows for message scheduling -- $2*8$ rows for $8$ reductions mod $2^{32}$ in "step 4" - -giving a total of $2099$ rows. - -## Tables - -We only require one table $\mathtt{spread}$, with $2^{16}$ rows and $3$ columns. We need a -tag column to allow selecting $(7, 10, 11, 13, 14)$-bit subsets of the table for -$\Sigma_{0..1}$ and $\sigma_{0..1}$. - -### `spread` table - -| row | tag | table (16b) | spread (32b) | -|--------------|-----|------------------|----------------------------------| -| $0$ | 0 | 0000000000000000 | 00000000000000000000000000000000 | -| $1$ | 0 | 0000000000000001 | 00000000000000000000000000000001 | -| $2$ | 0 | 0000000000000010 | 00000000000000000000000000000100 | -| $3$ | 0 | 0000000000000011 | 00000000000000000000000000000101 | -| ... | 0 | ... | ... | -| $2^{7} - 1$ | 0 | 0000000001111111 | 00000000000000000001010101010101 | -| $2^{7}$ | 1 | 0000000010000000 | 00000000000000000100000000000000 | -| ... | 1 | ... | ... | -| $2^{10} - 1$ | 1 | 0000001111111111 | 00000000000001010101010101010101 | -| ... | 2 | ... | ... | -| $2^{11} - 1$ | 2 | 0000011111111111 | 00000000010101010101010101010101 | -| ... | 3 | ... | ... | -| $2^{13} - 1$ | 3 | 0001111111111111 | 00000001010101010101010101010101 | -| ... | 4 | ... | ... | -| $2^{14} - 1$ | 4 | 0011111111111111 | 00000101010101010101010101010101 | -| ... | 5 | ... | ... | -| $2^{16} - 1$ | 5 | 1111111111111111 | 01010101010101010101010101010101 | - -For example, to do an $11$-bit $\mathtt{spread}$ lookup, we polynomial-constrain the tag -to be in $\{0, 1, 2\}$. For the most common case of a $16$-bit lookup, we don't need to -constrain the tag. Note that we can fill any unused rows beyond $2^{16}$ with a duplicate -entry, e.g. all-zeroes. - -## Gates - -### Choice gate -Input from previous operations: -- $E', F', G',$ 64-bit spread forms of 32-bit words $E, F, G$, assumed to be constrained by previous operations - - in practice, we'll have the spread forms of $E', F', G'$ after they've been decomposed into 16-bit subpieces -- $evens$ is defined as $\mathtt{spread}(2^{32} - 1)$ - - $evens_0 = evens_1 = \mathtt{spread}(2^{16} - 1)$ - -#### E ∧ F -|s_ch| $a_0$ | $a_1$ | $a_2$ | $a_3$ | $a_4$ | -|----|-------------|-------------|-----------------------------|------------------------------------|------------------------------------| -|0 |{0,1,2,3,4,5}|$P_0^{even}$ |$\texttt{spread}(P_0^{even})$| $\mathtt{spread}(E^{lo})$ | $\mathtt{spread}(E^{hi})$ | -|1 |{0,1,2,3,4,5}|$P_0^{odd}$ |$\texttt{spread}(P_0^{odd})$ |$\texttt{spread}(P_1^{odd})$ | | -|0 |{0,1,2,3,4,5}|$P_1^{even}$ |$\texttt{spread}(P_1^{even})$| $\mathtt{spread}(F^{lo})$ | $\mathtt{spread}(F^{hi})$ | -|0 |{0,1,2,3,4,5}|$P_1^{odd}$ |$\texttt{spread}(P_1^{odd})$ | | | - -#### ¬E ∧ G -s_ch_neg| $a_0$ | $a_1$ | $a_2$ | $a_3$ | $a_4$ | $a_5$ | ---------|-------------|-------------|-----------------------------|------------------------------------|------------------------------------|------------------------------------| - 0 |{0,1,2,3,4,5}|$Q_0^{even}$ |$\texttt{spread}(Q_0^{even})$|$\mathtt{spread}(E_{neg}^{lo})$ | $\mathtt{spread}(E_{neg}^{hi})$ | $\mathtt{spread}(E^{lo})$ | - 1 |{0,1,2,3,4,5}|$Q_0^{odd}$ |$\texttt{spread}(Q_0^{odd})$ |$\texttt{spread}(Q_1^{odd})$ | | $\mathtt{spread}(E^{hi})$ | - 0 |{0,1,2,3,4,5}|$Q_1^{even}$ |$\texttt{spread}(Q_1^{even})$|$\mathtt{spread}(G^{lo})$ | $\mathtt{spread}(G^{hi})$ | | - 0 |{0,1,2,3,4,5}|$Q_1^{odd}$ |$\texttt{spread}(Q_1^{odd})$ | | | | - -Constraints: -- `s_ch` (choice): $LHS - RHS = 0$ - - $LHS = a_3 \omega^{-1} + a_3 \omega + 2^{32}(a_4 \omega^{-1} + a_4 \omega)$ - - $RHS = a_2 \omega^{-1} + 2* a_2 + 2^{32}(a_2 \omega + 2* a_3)$ -- `s_ch_neg` (negation): `s_ch` with an extra negation check -- $\mathtt{spread}$ lookup on $(a_0, a_1, a_2)$ -- permutation between $(a_2, a_3)$ - -Output: $Ch(E, F, G) = P^{odd} + Q^{odd} = (P_0^{odd} + Q_0^{odd}) + 2^{16} (P_1^{odd} + Q_1^{odd})$ - -### Majority gate - -Input from previous operations: -- $A', B', C',$ 64-bit spread forms of 32-bit words $A, B, C$, assumed to be constrained by previous operations - - in practice, we'll have the spread forms of $A', B', C'$ after they've been decomposed into $16$-bit subpieces - -s_maj| $a_0$ | $a_1$ | $a_2$ | $a_3$ | $a_4$ | $a_5$ | ------|-------------|------------|-----------------------------|----------------------------|--------------------------|--------------------------| - 0 |{0,1,2,3,4,5}|$M_0^{even}$|$\texttt{spread}(M_0^{even})$| |$\mathtt{spread}(A^{lo})$ |$\mathtt{spread}(A^{hi})$ | - 1 |{0,1,2,3,4,5}|$M_0^{odd}$ |$\texttt{spread}(M_0^{odd})$ |$\texttt{spread}(M_1^{odd})$|$\mathtt{spread}(B^{lo})$ |$\mathtt{spread}(B^{hi})$ | - 0 |{0,1,2,3,4,5}|$M_1^{even}$|$\texttt{spread}(M_1^{even})$| |$\mathtt{spread}(C^{lo})$ |$\mathtt{spread}(C^{hi})$ | - 0 |{0,1,2,3,4,5}|$M_1^{odd}$ |$\texttt{spread}(M_1^{odd})$ | | | | - -Constraints: -- `s_maj` (majority): $LHS - RHS = 0$ - - $LHS = \mathtt{spread}(M^{even}_0) + 2 \cdot \mathtt{spread}(M^{odd}_0) + 2^{32} \cdot \mathtt{spread}(M^{even}_1) + 2^{33} \cdot \mathtt{spread}(M^{odd}_1)$ - - $RHS = A' + B' + C'$ -- $\mathtt{spread}$ lookup on $(a_0, a_1, a_2)$ -- permutation between $(a_2, a_3)$ - -Output: $Maj(A,B,C) = M^{odd} = M_0^{odd} + 2^{16} M_1^{odd}$ - -### Σ_0 gate - -$A$ is a 32-bit word split into $(2,11,9,10)$-bit chunks, starting from the little end. We refer to these chunks as $(a(2), b(11), c(9), d(10))$ respectively, and further split $c(9)$ into three 3-bit chunks $c(9)^{lo}, c(9)^{mid}, c(9)^{hi}$. We witness the spread versions of the small chunks. - -$$ -\begin{array}{ccc} -\Sigma_0(A) &=& (A ⋙ 2) \oplus (A ⋙ 13) \oplus (A ⋙ 22) \\ -&=& (A ⋙ 2) \oplus (A ⋙ 13) \oplus (A ⋘ 10) -\end{array} -$$ - -s_upp_sigma_0| $a_0$ | $a_1$ | $a_2$ | $a_3$ | $a_4$ | $a_5$ | $a_6$ | --------------|-------------|------------|-----------------------------|------------------------------|----------------------------|------------------------|-----------------------------| - 0 |{0,1,2,3,4,5}|$R_0^{even}$|$\texttt{spread}(R_0^{even})$| $c(9)^{lo}$ |$\texttt{spread}(c(9)^{lo})$| $c(9)^{mid}$ |$\texttt{spread}(c(9)^{mid})$| - 1 |{0,1,2,3,4,5}|$R_0^{odd}$ |$\texttt{spread}(R_0^{odd})$ | $\texttt{spread}(R_1^{odd})$ | $\texttt{spread}(d(10))$ |$\texttt{spread}(b(11))$| $c(9)$ | - 0 |{0,1,2,3,4,5}|$R_1^{even}$|$\texttt{spread}(R_1^{even})$| $a(2)$ |$\texttt{spread}(a(2))$ | $c(9)^{hi}$ |$\texttt{spread}(c(9)^{hi})$ | - 0 |{0,1,2,3,4,5}|$R_1^{odd}$ |$\texttt{spread}(R_1^{odd})$ | | | | | - - -Constraints: -- `s_upp_sigma_0` ($\Sigma_0$ constraint): $LHS - RHS + tag + decompose = 0$ - -$$ -\begin{array}{ccc} -tag &=& constrain_1(a_0\omega^{-1}) + constrain_2(a_0\omega) \\ -decompose &=& a(2) + 2^2 b(11) + 2^{13} c(9)^{lo} + 2^{16} c(9)^{mid} + 2^{19} c(9)^{hi} + 2^{22} d(10) - A\\ -LHS &=& \mathtt{spread}(R^{even}_0) + 2 \cdot \mathtt{spread}(R^{odd}_0) + 2^{32} \cdot \mathtt{spread}(R^{even}_1) + 2^{33} \cdot \mathtt{spread}(R^{odd}_1) -\end{array} -$$ -$$ -\begin{array}{rcccccccccl} -RHS = & 4^{30} \texttt{spread}(a(2)) &+& 4^{20} \texttt{spread}(d(10)) &+& 4^{17} \texttt{spread}(c(9)^{hi}) &+& 4^{14} \texttt{spread}(c(9)^{mid}) &+& 4^{11} \texttt{spread}(c(9)^{lo}) &+& \texttt{spread}(b(11))\;&+ \\ - & 4^{21} \texttt{spread}(b(11)) &+& 4^{19} \texttt{spread}(a(2)) &+& 4^{9} \texttt{spread}(d(10)) &+& 4^{6} \texttt{spread}(c(9)^{hi}) &+& 4^{3} \texttt{spread}(c(9)^{mid}) &+& \texttt{spread}(c(9)^{lo}) \;&+ \\ - & 4^{29} \texttt{spread}(c(9)^{hi}) &+& 4^{26} \texttt{spread}(c(9)^{mid}) &+& 4^{23} \texttt{spread}(c(9)^{lo}) &+& 4^{12} \texttt{spread}(b(11)) &+& 4^{10} \texttt{spread}(a(2)) &+& \texttt{spread}(d(10))\;& -\end{array} -$$ - -- $\mathtt{spread}$ lookup on $a_0, a_1, a_2$ -- 2-bit range check and 2-bit spread check on $a(2)$ -- 3-bit range check and 3-bit spread check on $c(9)^{lo}, c(9)^{mid}, c(9)^{hi}$ - -(see section [Helper gates](#helper-gates)) - -Output: $\Sigma_0(A) = R^{even} = R_0^{even} + 2^{16} R_1^{even}$ - -### Σ_1 gate -$E$ is a 32-bit word split into $(6,5,14,7)$-bit chunks, starting from the little end. We refer to these chunks as $(a(6), b(5), c(14), d(7))$ respectively, and further split $a(6)$ into two 3-bit chunks $a(6)^{lo}, a(6)^{hi}$ and $b$ into (2,3)-bit chunks $b(5)^{lo}, b(5)^{hi}$. We witness the spread versions of the small chunks. - -$$ -\begin{array}{ccc} -\Sigma_1(E) &=& (E ⋙ 6) \oplus (E ⋙ 11) \oplus (E ⋙ 25) \\ -&=& (E ⋙ 6) \oplus (E ⋙ 11) \oplus (E ⋘ 7) -\end{array} -$$ - -s_upp_sigma_1| $a_0$ | $a_1$ | $a_2$ | $a_3$ | $a_4$ | $a_5$ | $a_6$ | $a_7$ | --------------|-------------|------------|-----------------------------|------------------------------|----------------------------|------------------------|-----------------------------|------------| - 0 |{0,1,2,3,4,5}|$R_0^{even}$|$\texttt{spread}(R_0^{even})$| $b(5)^{lo}$ |$\texttt{spread}(b(5)^{lo})$| $b(5)^{hi}$ |$\texttt{spread}(b(5)^{hi})$ | $b(5)$ | - 1 |{0,1,2,3,4,5}|$R_0^{odd}$ |$\texttt{spread}(R_0^{odd})$ | $\texttt{spread}(R_1^{odd})$ | $\texttt{spread}(d(7))$ |$\texttt{spread}(c(14))$| | | - 0 |{0,1,2,3,4,5}|$R_1^{even}$|$\texttt{spread}(R_1^{even})$| $a(6)^{lo}$ |$\texttt{spread}(a(6)^{lo})$| $a(6)^{hi}$ |$\texttt{spread}(a(6)^{hi})$ | $a(6)$ | - 0 |{0,1,2,3,4,5}|$R_1^{odd}$ |$\texttt{spread}(R_1^{odd})$ | | | | | | - - -Constraints: -- `s_upp_sigma_1` ($\Sigma_1$ constraint): $LHS - RHS + tag + decompose = 0$ - -$$ -\begin{array}{ccc} -tag &=& a_0\omega^{-1} + constrain_4(a_0\omega) \\ -decompose &=& a(6)^{lo} + 2^3 a(6)^{hi} + 2^6 b(5)^{lo} + 2^8 b(5)^{hi} + 2^{11} c(14) + 2^{25} d(7) - E \\ -LHS &=& \mathtt{spread}(R^{even}_0) + 2 \cdot \mathtt{spread}(R^{odd}_0) + 2^{32} \cdot \mathtt{spread}(R^{even}_1) + 2^{33} \cdot \mathtt{spread}(R^{odd}_1) -\end{array} -$$ -$$ -\begin{array}{rcccccccccl} -RHS = & 4^{29} \texttt{spread}(a(6)^{hi}) &+& 4^{26} \texttt{spread}(a(6)^{lo}) &+& 4^{19} \texttt{spread}(d(7)) &+& 4^{ 5} \texttt{spread}(c(14)) &+& 4^{2} \texttt{spread}(b(5)^{hi}) &+& \texttt{spread}(b(5)^{lo})\;&+ \\ - & 4^{29} \texttt{spread}(b(5)^{hi}) &+& 4^{27} \texttt{spread}(b(5)^{lo}) &+& 4^{24} \texttt{spread}(a(6)^{hi}) &+& 4^{21} \texttt{spread}(a(6)^{lo}) &+& 4^{14} \texttt{spread}(d(7)) &+& \texttt{spread}(c(14))\;&+ \\ - & 4^{18} \texttt{spread}(c(14)) &+& 4^{15} \texttt{spread}(b(5)^{hi}) &+& 4^{13} \texttt{spread}(b(5)^{lo}) &+& 4^{10} \texttt{spread}(a(6)^{hi}) &+& 4^{7} \texttt{spread}(a(6)^{lo}) &+& \texttt{spread}(d(7))\;& -\end{array} -$$ - -- $\mathtt{spread}$ lookup on $a_0, a_1, a_2$ -- 2-bit range check and 2-bit spread check on $b(5)^{lo}$ -- 3-bit range check and 3-bit spread check on $a(6)^{lo}, a(6)^{hi}, b(4)^{hi}$ - -(see section [Helper gates](#helper-gates)) - -Output: $\Sigma_1(E) = R^{even} = R_0^{even} + 2^{16} R_1^{even}$ - -### σ_0 gate -#### v1 -v1 of the $\sigma_0$ gate takes in a word that's split into $(3, 4, 11, 14)$-bit chunks (already constrained by message scheduling). We refer to these chunks respectively as $(a(3), b(4), c(11), d(14)).$ $b(4)$ is further split into two 2-bit chunks $b(4)^{lo},b(4)^{hi}.$ We witness the spread versions of the small chunks. We already have $\texttt{spread}(c(11))$ and $\texttt{spread}(d(14))$ from the message scheduling. - -$(X ⋙ 7) \oplus (X ⋙ 18) \oplus (X ≫ 3)$ is equivalent to -$(X ⋙ 7) \oplus (X ⋘ 14) \oplus (X ≫ 3)$. - -s_low_sigma_0| $a_0$ | $a_1$ | $a_2$ | $a_3$ | $a_4$ | $a_5$ | $a_6$ | --------------|-------------|------------|-----------------------------|-----------------------------|----------------------------|--------------------|----------------------------| - 0 |{0,1,2,3,4,5}|$R_0^{even}$|$\texttt{spread}(R_0^{even})$| $b(4)^{lo}$ |$\texttt{spread}(b(4)^{lo})$| $b(4)^{hi}$ |$\texttt{spread}(b(4)^{hi})$| - 1 |{0,1,2,3,4,5}|$R_0^{odd}$ |$\texttt{spread}(R_0^{odd})$ |$\texttt{spread}(R_1^{odd})$ |$\texttt{spread}(c)$ |$\texttt{spread}(d)$| $b(4)$ | - 0 |{0,1,2,3,4,5}|$R_1^{even}$|$\texttt{spread}(R_1^{even})$| $0$ | $0$ | $a$ | $\texttt{spread}(a)$ | - 0 |{0,1,2,3,4,5}|$R_1^{odd}$ |$\texttt{spread}(R_1^{odd})$ | | | | | - -Constraints: -- `s_low_sigma_0` ($\sigma_0$ v1 constraint): $LHS - RHS = 0$ - -$$ -\begin{array}{ccc} -LHS &=& \mathtt{spread}(R^{even}_0) + 2 \cdot \mathtt{spread}(R^{odd}_0) + 2^{32} \cdot \mathtt{spread}(R^{even}_1) + 2^{33} \cdot \mathtt{spread}(R^{odd}_1) -\end{array} -$$ -$$ -\begin{array}{rccccccccl} -RHS = & & & 4^{15} d(14) &+& 4^{ 4} c(11) &+& 4^2 b(4)^{hi} &+& b(4)^{lo}\;&+ \\ - & 4^{30} b(4)^{hi} &+& 4^{28} b(4)^{lo} &+& 4^{25} a(3) &+& 4^{11} d(14) &+& c(11)\;&+ \\ - & 4^{21} c(11) &+& 4^{19} b(4)^{hi} &+& 4^{17} b(4)^{lo} &+& 4^{14} a(3) &+& d(14)\;& -\end{array} -$$ - -- check that `b` was properly split into subsections for 4-bit pieces. - - $W^{b(4)lo} + 2^2 W^{b(4)hi} - W = 0$ -- 2-bit range check and 2-bit spread check on $b(4)^{lo}, b(4)^{hi}$ -- 3-bit range check and 3-bit spread check on $a(3)$ - - -#### v2 -v2 of the $\sigma_0$ gate takes in a word that's split into $(3, 4, 3, 7, 1, 1, 13)$-bit chunks (already constrained by message scheduling). We refer to these chunks respectively as $(a(3), b(4), c(3), d(7), e(1), f(1), g(13)).$ We already have $\mathtt{spread}(d(7)), \mathtt{spread}(g(13))$ from the message scheduling. The 1-bit $e(1), f(1)$ remain unchanged by the spread operation and can be used directly. We further split $b(4)$ into two 2-bit chunks $b(4)^{lo}, b(4)^{hi}.$ We witness the spread versions of the small chunks. - -$(X ⋙ 7) \oplus (X ⋙ 18) \oplus (X ≫ 3)$ is equivalent to -$(X ⋙ 7) \oplus (X ⋘ 14) \oplus (X ≫ 3)$. - -s_low_sigma_0_v2| $a_0$ | $a_1$ | $a_2$ | $a_3$ | $a_4$ | $a_5$ | $a_6$ | $a_7$ | -----------------|-------------|------------|-----------------------------|-----------------------------|----------------------------|------------------------|----------------------------|------------| - 0 |{0,1,2,3,4,5}|$R_0^{even}$|$\texttt{spread}(R_0^{even})$| $b(4)^{lo}$ |$\texttt{spread}(b(4)^{lo})$| $b(4)^{hi}$ |$\texttt{spread}(b(4)^{hi})$| | - 1 |{0,1,2,3,4,5}|$R_0^{odd}$ |$\texttt{spread}(R_0^{odd})$ | $\texttt{spread}(R_1^{odd})$| $\texttt{spread}(d(7))$ |$\texttt{spread}(g(13))$| $b(4)$ | $e(1)$ | - 0 |{0,1,2,3,4,5}|$R_1^{even}$|$\texttt{spread}(R_1^{even})$| $a(3)$ |$\texttt{spread}(a(3))$ | $c(3)$ |$\texttt{spread}(c(3))$ | $f(1)$ | - 0 |{0,1,2,3,4,5}|$R_1^{odd}$ |$\texttt{spread}(R_1^{odd})$ | | | | | | - -Constraints: -- `s_low_sigma_0_v2` ($\sigma_0$ v2 constraint): $LHS - RHS = 0$ - -$$ -\begin{array}{ccc} -LHS &=& \mathtt{spread}(R^{even}_0) + 2 \cdot \mathtt{spread}(R^{odd}_0) + 2^{32} \cdot \mathtt{spread}(R^{even}_1) + 2^{33} \cdot \mathtt{spread}(R^{odd}_1) -\end{array} -$$ -$$ -\begin{array}{rcccccccccccl} -RHS = & & & 4^{16} g(13) &+& 4^{15} f(1) &+& 4^{ 14} e(1) &+& 4^{ 7} d(7) &+& 4^{ 4} c(3) &+& 4^2 b(4)^{hi} &+& b(4)^{lo}\;&+ \\ - & 4^{30} b(4)^{hi} &+& 4^{28} b(4)^{lo} &+& 4^{25} a(3) &+& 4^{12} g(13) &+& 4^{11} f(1) &+& 4^{10} e(1) &+& 4^{3} d(7) &+& c(3)\;&+ \\ - & 4^{31} e(1) &+& 4^{24} d(7) &+& 4^{21} c(3) &+& 4^{19} b(4)^{hi} &+& 4^{17} b(4)^{lo} &+& 4^{14} a(3) &+& 4^{1} g(13) &+& f(1)\;& -\end{array} -$$ - -- check that `b` was properly split into subsections for 4-bit pieces. - - $W^{b(4)lo} + 2^2 W^{b(4)hi} - W = 0$ -- 2-bit range check and 2-bit spread check on $b(4)^{lo}, b(4)^{hi}$ -- 3-bit range check and 3-bit spread check on $a(3), c(3)$ - -### σ_1 gate -#### v1 -v1 of the $\sigma_1$ gate takes in a word that's split into $(10, 7, 2, 13)$-bit chunks (already constrained by message scheduling). We refer to these chunks respectively as $(a(10), b(7), c(2), d(13)).$ $b(7)$ is further split into $(2, 2, 3)$-bit chunks $b(7)^{lo}, b(7)^{mid}, b(7)^{hi}.$ We witness the spread versions of the small chunks. We already have $\texttt{spread}(a(10))$ and $\texttt{spread}(d(13))$ from the message scheduling. - -$(X ⋙ 17) \oplus (X ⋙ 19) \oplus (X ≫ 10)$ is equivalent to -$(X ⋘ 15) \oplus (X ⋘ 13) \oplus (X ≫ 10)$. - -s_low_sigma_1| $a_0$ | $a_1$ | $a_2$ | $a_3$ | $a_4$ | $a_5$ | $a_6$ | --------------|-------------|------------|-----------------------------|------------------------------|----------------------------|------------------------|-----------------------------| - 0 |{0,1,2,3,4,5}|$R_0^{even}$|$\texttt{spread}(R_0^{even})$| $b(7)^{lo}$ |$\texttt{spread}(b(7)^{lo})$| $b(7)^{mid}$ |$\texttt{spread}(b(7)^{mid})$| - 1 |{0,1,2,3,4,5}|$R_0^{odd}$ |$\texttt{spread}(R_0^{odd})$ | $\texttt{spread}(R_1^{odd})$ | $\texttt{spread}(a(10))$ |$\texttt{spread}(d(13))$| $b(7)$ | - 0 |{0,1,2,3,4,5}|$R_1^{even}$|$\texttt{spread}(R_1^{even})$| $c(2)$ |$\texttt{spread}(c(2))$ | $b(7)^{hi}$ |$\texttt{spread}(b(7)^{hi})$ | - 0 |{0,1,2,3,4,5}|$R_1^{odd}$ |$\texttt{spread}(R_1^{odd})$ | | | | | - -Constraints: -- `s_low_sigma_1` ($\sigma_1$ v1 constraint): $LHS - RHS = 0$ -$$ -\begin{array}{ccc} -LHS &=& \mathtt{spread}(R^{even}_0) + 2 \cdot \mathtt{spread}(R^{odd}_0) + 2^{32} \cdot \mathtt{spread}(R^{even}_1) + 2^{33} \cdot \mathtt{spread}(R^{odd}_1) -\end{array} -$$ -$$ -\begin{array}{rcccccccccl} -RHS = & & & 4^{ 9} d(13) &+& 4^{ 7} c(2) &+& 4^{4} b(7)^{hi} &+& 4^{2} b(7)^{mid} &+& b(7)^{lo}\;&+ \\ - & 4^{29} b(7)^{hi} &+& 4^{27} b(7)^{mid} &+& 4^{25} b(7)^{lo} &+& 4^{15} a(10) &+& 4^{ 2} d(13) &+& c(2)\;&+ \\ - & 4^{30} c(2) &+& 4^{27} b(7)^{hi} &+& 4^{25} b(7)^{mid} &+& 4^{23} b(7)^{lo} &+& 4^{13} a(10) &+& d(13)\;& -\end{array} -$$ - -- check that `b` was properly split into subsections for 7-bit pieces. - - $W^{b(7)lo} + 2^2 W^{b(7)mid} + 2^4 W^{b(7)hi} - W = 0$ -- 2-bit range check and 2-bit spread check on $b(7)^{lo}, b(7)^{mid}, c(2)$ -- 3-bit range check and 3-bit spread check on $b(7)^{hi}$ - - -#### v2 -v2 of the $\sigma_1$ gate takes in a word that's split into $(3, 4, 3, 7, 1, 1, 13)$-bit chunks (already constrained by message scheduling). We refer to these chunks respectively as $(a(3), b(4), c(3), d(7), e(1), f(1), g(13)).$ We already have $\mathtt{spread}(d(7)), \mathtt{spread}(g(13))$ from the message scheduling. The 1-bit $e(1), f(1)$ remain unchanged by the spread operation and can be used directly. We further split $b(4)$ into two 2-bit chunks $b(4)^{lo}, b(4)^{hi}.$ We witness the spread versions of the small chunks. - -$(X ⋙ 17) \oplus (X ⋙ 19) \oplus (X ≫ 10)$ is equivalent to -$(X ⋘ 15) \oplus (X ⋘ 13) \oplus (X ≫ 10)$. - -s_low_sigma_1_v2| $a_0$ | $a_1$ | $a_2$ | $a_3$ | $a_4$ | $a_5$ | $a_6$ | $a_7$ | -----------------|-------------|------------|-----------------------------|-----------------------------|----------------------------|-------------------------|----------------------------|------------| - 0 |{0,1,2,3,4,5}|$R_0^{even}$|$\texttt{spread}(R_0^{even})$| $b(4)^{lo}$ |$\texttt{spread}(b(4)^{lo})$| $b(4)^{hi}$ |$\texttt{spread}(b(4)^{hi})$| | - 1 |{0,1,2,3,4,5}|$R_0^{odd}$ |$\texttt{spread}(R_0^{odd})$ | $\texttt{spread}(R_1^{odd})$| $\texttt{spread}(d(7))$ | $\texttt{spread}(g(13))$| $b(4)$ | $e(1)$ | - 0 |{0,1,2,3,4,5}|$R_1^{even}$|$\texttt{spread}(R_1^{even})$| $a(3)$ |$\texttt{spread}(a(3))$ | $c(3)$ |$\texttt{spread}(c(3))$ | $f(1)$ | - 0 |{0,1,2,3,4,5}|$R_1^{odd}$ |$\texttt{spread}(R_1^{odd})$ | | | | | | - -Constraints: -- `s_low_sigma_1_v2` ($\sigma_1$ v2 constraint): $LHS - RHS = 0$ - -$$ -\begin{array}{ccc} -LHS &=& \mathtt{spread}(R^{even}_0) + 2 \cdot \mathtt{spread}(R^{odd}_0) + 2^{32} \cdot \mathtt{spread}(R^{even}_1) + 2^{33} \cdot \mathtt{spread}(R^{odd}_1) -\end{array} -$$ -$$ -\begin{array}{rccccccccccccl} -RHS = & &&&& & & 4^{ 9} g(13) &+& 4^{ 8} f(1) &+& 4^{ 7} e(1) &+& d(7)\;&+ \\ - & 4^{25} d(7) &+& 4^{22} c(3) &+& 4^{20} b(4)^{hi} &+& 4^{18} b(4)^{lo} &+& 4^{15} a &+& 4^{ 2} g(13) &+& 4^{1}f(1) &+& e(1)\;&+ \\ - & 4^{31} f(1) &+& 4^{30} e(1) &+& 4^{23} d(7) &+& 4^{20} c(3) &+& 4^{18} b(4)^{hi} &+& 4^{16} b(4)^{lo} &+& 4^{13} a &+& g(13)\;& -\end{array} -$$ - -- check that `b` was properly split into subsections for 4-bit pieces. - - $W^{b(4)lo} + 2^2 W^{b(4)hi} - W = 0$ -- 2-bit range check and 2-bit spread check on $b(4)^{lo}, b(4)^{hi}$ -- 3-bit range check and 3-bit spread check on $a(3), c(3)$ - - -### Helper gates - -#### Small range constraints -Let $constrain_n(x) = \prod_{i=0}^n (x-i)$. Constraining this expression to equal zero enforces that $x$ is in $[0..n].$ - -#### 2-bit range check -$(a - 3)(a - 2)(a - 1)(a) = 0$ - -sr2| $a_0$ | ----|-------| - 1 | a | - -#### 2-bit spread -$l_1(a) + 4*l_2(a) + 5*l_3(a) - a' = 0$ - -ss2| $a_0$ | $a_1$ ----|-------|------ - 1 | a | a' - -with interpolation polynomials: -- $l_0(a) = \frac{(a - 3)(a - 2)(a - 1)}{(-3)(-2)(-1)}$ ($\mathtt{spread}(00) = 0000$) -- $l_1(a) = \frac{(a - 3)(a - 2)(a)}{(-2)(-1)(1)}$ ($\mathtt{spread}(01) = 0001$) -- $l_2(a) = \frac{(a - 3)(a - 1)(a)}{(-1)(1)(2)}$ ($\mathtt{spread}(10) = 0100$) -- $l_3(a) = \frac{(a - 2)(a - 1)(a)}{(1)(2)(3)}$ ($\mathtt{spread}(11) = 0101$) - -#### 3-bit range check -$(a - 7)(a - 6)(a - 5)(a - 4)(a - 3)(a - 2)(a - 1)(a) = 0$ - -sr3| $a_0$ | ----|-------| - 1 | a | - -#### 3-bit spread -$l_1(a) + 4*l_2(a) + 5*l_3(a) + 16*l_4(a) + 17*l_5(a) + 20*l_6(a) + 21*l_7(a) - a' = 0$ - -ss3| $a_0$ | $a_1$ ----|-------|------ - 1 | a | a' - -with interpolation polynomials: -- $l_0(a) = \frac{(a - 7)(a - 6)(a - 5)(a - 4)(a - 3)(a - 2)(a - 1)}{(-7)(-6)(-5)(-4)(-3)(-2)(-1)}$ ($\mathtt{spread}(000) = 000000$) -- $l_1(a) = \frac{(a - 7)(a - 6)(a - 5)(a - 4)(a - 3)(a - 2)(a)}{(-6)(-5)(-4)(-3)(-2)(-1)(1)}$ ($\mathtt{spread}(001) = 000001$) -- $l_2(a) = \frac{(a - 7)(a - 6)(a - 5)(a - 4)(a - 3)(a - 1)(a)}{(-5)(-4)(-3)(-2)(-1)(1)(2)}$ ($\mathtt{spread}(010) = 000100$) -- $l_3(a) = \frac{(a - 7)(a - 6)(a - 5)(a - 4)(a - 2)(a - 1)(a)}{(-4)(-3)(-2)(-1)(1)(2)(3)}$ ($\mathtt{spread}(011) = 000101$) -- $l_4(a) = \frac{(a - 7)(a - 6)(a - 5)(a - 3)(a - 2)(a - 1)(a)}{(-3)(-2)(-1)(1)(2)(3)(4)}$ ($\mathtt{spread}(100) = 010000$) -- $l_5(a) = \frac{(a - 7)(a - 6)(a - 4)(a - 3)(a - 2)(a - 1)(a)}{(-2)(-1)(1)(2)(3)(4)(5)}$ ($\mathtt{spread}(101) = 010001$) -- $l_6(a) = \frac{(a - 7)(a - 5)(a - 4)(a - 3)(a - 2)(a - 1)(a)}{(-1)(1)(2)(3)(4)(5)(6)}$ ($\mathtt{spread}(110) = 010100$) -- $l_7(a) = \frac{(a - 6)(a - 5)(a - 4)(a - 3)(a - 2)(a - 1)(a)}{(1)(2)(3)(4)(5)(6)(7)}$ ($\mathtt{spread}(111) = 010101$) - -#### reduce_6 gate -Addition $\pmod{2^{32}}$ of 6 elements - -Input: -- $E$ -- $\{e_i^{lo}, e_i^{hi}\}_{i=0}^5$ -- $carry$ - -Check: $E = e_0 + e_1 + e_2 + e_3 + e_4 + e_5 \pmod{32}$ - -Assume inputs are constrained to 16 bits. -- Addition gate (sa): - - $a_0 + a_1 + a_2 + a_3 + a_4 + a_5 + a_6 - a_7 = 0$ -- Carry gate (sc): - - $2^{16} a_6 \omega^{-1} + a_6 + [(a_6 - 5)(a_6 - 4)(a_6 -3)(a_6 - 2)(a_6 - 1)(a_6)] = 0$ - -sa|sc| $a_0$ | $a_1$ |$a_2$ |$a_3$ |$a_4$ |$a_5$ |$a_6$ |$a_7$ | ---|--|----------|----------|----------|----------|----------|----------|---------------|--------| -1 |0 |$e_0^{lo}$|$e_1^{lo}$|$e_2^{lo}$|$e_3^{lo}$|$e_4^{lo}$|$e_5^{lo}$|$-carry*2^{16}$|$E^{lo}$| -1 |1 |$e_0^{hi}$|$e_1^{hi}$|$e_2^{hi}$|$e_3^{hi}$|$e_4^{hi}$|$e_5^{hi}$|$carry$ |$E^{hi}$| - -Assume inputs are constrained to 16 bits. -- Addition gate (sa): - - $a_0 \omega^{-1} + a_1 \omega^{-1} + a_2 \omega^{-1} + a_0 + a_1 + a_2 + a_3 \omega^{-1} - a_3 = 0$ -- Carry gate (sc): - - $2^{16} a_3 \omega + a_3 \omega^{-1} = 0$ - - -sa|sc| $a_0$ | $a_1$ |$a_2$ |$a_3$ | ---|--|----------|----------|----------|---------------| -0 |0 |$e_0^{lo}$|$e_1^{lo}$|$e_2^{lo}$|$-carry*2^{16}$| -1 |1 |$e_3^{lo}$|$e_4^{lo}$|$e_5^{lo}$|$E^{lo}$ | -0 |0 |$e_0^{hi}$|$e_1^{hi}$|$e_2^{hi}$|$carry$ | -1 |0 |$e_3^{hi}$|$e_4^{hi}$|$e_5^{hi}$|$E^{hi}$ | - -#### reduce_7 gate -Addition $\pmod{2^{32}}$ of 7 elements - -Input: -- $E$ -- $\{e_i^{lo}, e_i^{hi}\}_{i=0}^6$ -- $carry$ - -Check: $E = e_0 + e_1 + e_2 + e_3 + e_4 + e_5 + e_6 \pmod{32}$ - -Assume inputs are constrained to 16 bits. -- Addition gate (sa): - - $a_0 + a_1 + a_2 + a_3 + a_4 + a_5 + a_6 + a_7 - a_8 = 0$ -- Carry gate (sc): - - $2^{16} a_7 \omega^{-1} + a_7 + [(a_7 - 6)(a_7 - 5)(a_7 - 4)(a_7 -3)(a_7 - 2)(a_7 - 1)(a_7)] = 0$ - -sa|sc| $a_0$ | $a_1$ |$a_2$ |$a_3$ |$a_4$ |$a_5$ |$a_6$ |$a_7$ |$a_8$ | ---|--|----------|----------|----------|----------|----------|----------|----------|---------------|--------| -1 |0 |$e_0^{lo}$|$e_1^{lo}$|$e_2^{lo}$|$e_3^{lo}$|$e_4^{lo}$|$e_5^{lo}$|$e_6^{lo}$|$-carry*2^{16}$|$E^{lo}$| -1 |1 |$e_0^{hi}$|$e_1^{hi}$|$e_2^{hi}$|$e_3^{hi}$|$e_4^{hi}$|$e_5^{hi}$|$e_6^{hi}$|$carry$ |$E^{hi}$| - - -### Message scheduling region -For each block $M \in \{0,1\}^{512}$ of the padded message, $64$ words of $32$ bits each are constructed as follows: -- the first $16$ are obtained by splitting $M$ into $32$-bit blocks $$M = W_0 || W_1 || \cdots || W_{14} || W_{15};$$ -- the remaining $48$ words are constructed using the formula: -$$W_i = \sigma_1(W_{i-2}) \boxplus W_{i-7} \boxplus \sigma_0(W_{i-15}) \boxplus W_{i-16},$$ for $16 \leq i < 64$. - -sw|sd0|sd1|sd2|sd3|ss0|ss0_v2|ss1|ss1_v2| $a_0$ | $a_1$ | $a_2$ | $a_3$ | $a_4$ | $a_5$ | $a_6$ | $a_7$ | $a_8$ | $a_9$ | ---|---|---|---|---|---|------|---|------|---------------|------------------|-----------------------------------|------------------------------|----------------------------------|---------------------------------|--------------------------------- |------------------------|----------------|--------------| -0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $W_{0}^{lo}$ | $\texttt{spread}(W_{0}^{lo})$ | $W_{0}^{lo}$ | $W_{0}^{hi}$ | $W_{0}$ |$\sigma_0(W_1)^{lo}$ |$\sigma_1(W_{14})^{lo}$ | $W_{9}^{lo}$ | | -1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $W_{0}^{hi}$ | $\texttt{spread}(W_{0}^{hi})$ | | | $W_{16}$ |$\sigma_0(W_1)^{hi}$ |$\sigma_1(W_{14})^{hi}$ | $W_{9}^{hi}$ | $carry_{16}$ | -0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4} | $W_{1}^{d(14)}$ | $\texttt{spread}(W_{1}^{d(14)})$ | $W_{1}^{lo}$ | $W_{1}^{hi}$ | $W_{1}$ |$\sigma_0(W_2)^{lo}$ |$\sigma_1(W_{15})^{lo}$ | $W_{10}^{lo}$ | | -1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2} | $W_{1}^{c(11)}$ | $\texttt{spread}(W_{1}^{c(11)})$ | $W_{1}^{a(3)}$ | $W_{1}^{b(4)}$ | $W_{17}$ |$\sigma_0(W_2)^{hi}$ |$\sigma_1(W_{15})^{hi}$ | $W_{10}^{hi}$ | $carry_{17}$ | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $R_0^{even}$ | $\texttt{spread}(R_0^{even})$ | $W_{1}^{b(4)lo}$ |$\texttt{spread}(W_{1}^{b(4)lo})$ | $W_{1}^{b(4)hi}$ |$\texttt{spread}(W_{1}^{b(4)hi})$ | | | | -0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | {0,1,2,3,4,5} | $R_1^{odd}$ | $\texttt{spread}(R_0^{odd})$ | $\texttt{spread}(R_1^{odd})$ |$\texttt{spread}(W_{1}^{c(11)})$ |$\texttt{spread}(W_{1}^{d(14)})$ | $W_{1}^{b(4)}$ | | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $R_0^{odd}$ | $\texttt{spread}(R_1^{even})$ | $0$ | $0$ | $W_{1}^{a(3)}$ |$\texttt{spread}(W_{1}^{a(3)})$ | | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $R_1^{even}$ | $\texttt{spread}(R_1^{odd})$ | $\sigma_0 v1 R_0$ | $\sigma_0 v1 R_1$ | $\sigma_0 v1 R_0^{even}$ | $\sigma_0 v1 R_0^{odd}$ | | | | -..|...|...|...|...|...|... |...|... | ... | ... | ... | ... | ... | ... | ... | ... | ... | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3} | $W_{14}^{g(13)}$ | $\texttt{spread}(W_{14}^{g(13)})$ | $W_{14}^{a(3)}$ | $W_{14}^{c(3)}$ | | | | | | -0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | $W_{14}^{d(7)}$ | $\texttt{spread}(W_{14}^{d(7)})$ | $W_{14}^{lo}$ | $W_{14}^{hi}$ | $W_{14}$ |$\sigma_0(W_{15})^{lo}$ |$\sigma_1(W_{28})^{lo}$ | $W_{23}^{lo}$ | | -1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | $W_{14}^{b(4)}$ | $\texttt{spread}(W_{14}^{b(4)})$ | $W_{14}^{e(1)}$ | $W_{14}^{f(1)}$ | $W_{30}$ |$\sigma_0(W_{15})^{hi}$ |$\sigma_1(W_{28})^{hi}$ | $W_{23}^{hi}$ | $carry_{30}$ | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $R_0^{even}$ | $\texttt{spread}(R_0^{even})$ | $W_{14}^{b(4)lo}$ |$\texttt{spread}(W_{14}^{b(4)lo})$| $W_{14}^{b(4) hi}$ |$\texttt{spread}(W_{14}^{b(4)hi})$ | | | | -0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | {0,1,2,3,4,5} | $R_0^{odd}$ | $\texttt{spread}(R_0^{odd})$ | $\texttt{spread}(R_1^{odd})$ |$\texttt{spread}(W_{14}^{d(7)})$ |$\texttt{spread}(W_{14}^{g(13)})$| $W_{1}^{b(14)}$ | $W_{14}^{e(1)}$ | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $R_1^{even}$ | $\texttt{spread}(R_1^{even})$ | $W_{14}^{a(3)}$ |$\texttt{spread}(W_{14}^{a(3)})$ | $W_{14}^{c(3)}$ |$\texttt{spread}(W_{14}^{c(3)})$ | $W_{14}^{f(1)}$ | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $R_1^{odd}$ | $\texttt{spread}(R_1^{odd})$ | $\sigma_0 v2 R_0$ | $\sigma_0 v2 R_1$ |$\sigma_0 v2 R_0^{even}$ |$\sigma_0 v2 R_0^{odd}$ | | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $R_0^{even}$ | $\texttt{spread}(R_0^{even})$ | $W_{14}^{b(4)lo}$ |$\texttt{spread}(W_{14}^{b(4)lo})$| $W_{14}^{b(4) hi}$ |$\texttt{spread}(W_{14}^{b(4)hi})$ | | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | {0,1,2,3,4,5} | $R_0^{odd}$ | $\texttt{spread}(R_0^{odd})$ | $\texttt{spread}(R_1^{odd})$ | $\texttt{spread}(d)$ | $\texttt{spread}(g)$ | | $W_{14}^{e(1)}$ | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $R_1^{even}$ | $\texttt{spread}(R_1^{even})$ | $W_{14}^{a(3)}$ |$\texttt{spread}(W_{14}^{a(3)})$ | $W_{14}^{c(3)}$ |$\texttt{spread}(W_{14}^{c(3)})$ | $W_{14}^{f(1)}$ | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $R_1^{odd}$ | $\texttt{spread}(R_1^{odd})$ | $\sigma_1 v2 R_0$ | $\sigma_1 v2 R_1$ |$\sigma_1 v2 R_0^{even}$ |$\sigma_1 v2 R_0^{odd}$ | | | | -..|...|...|...|...|...|... |...|... | ... | ... | ... | ... | ... | ... | ... | ... | ... | | -0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | {0,1,2,3} | $W_{49}^{d(13)}$ | $\texttt{spread}(W_{49}^{d(13)})$ | $W_{49}^{lo}$ | $W_{49}^{hi}$ | $W_{49}$ | | | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1} | $W_{49}^{a(10)}$ | $\texttt{spread}(W_{49}^{a(10)})$ | $W_{49}^{c(2)}$ | $W_{49}^{b(7)}$ | | | | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5} | $R_0^{even}$ | $\texttt{spread}(R_0^{even})$ | $W_{49}^{b(7)lo}$ |$\texttt{spread}(W_{49}^{b(7)lo})$| $W_{49}^{b(7)mid}$ |$\texttt{spread}(W_{49}^{b(7)mid})$| | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |{0,1,2,3,4,5} | $R_0^{odd}$ | $\texttt{spread}(R_0^{odd})$ | $\texttt{spread}(R_1^{odd})$ | $\texttt{spread}(a)$ | $\texttt{spread}(d)$ | $W_{1}^{b(49)}$ | | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5} | $R_1^{even}$ | $\texttt{spread}(R_1^{even})$ | $W_{49}^{c(2)}$ |$\texttt{spread}(W_{49}^{c(2)})$ | $W_{49}^{b(7)hi}$ |$\texttt{spread}(W_{49}^{b(7)hi})$ | | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5} | $R_1^{odd}$ | $\texttt{spread}(R_1^{odd})$ | $\sigma_1 v1 R_0$ | $\sigma_1 v1 R_1$ |$\sigma_1 v1 R_0^{even}$ |$\sigma_1 v1 R_0^{odd}$ | | | | -..|...|...|...|...|...|... |...|... | ... | ... | ... | ... | ... | ... | ... | ... | ... | | -0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $W_{62}^{lo}$ | $\texttt{spread}(W_{62}^{lo})$ | $W_{62}^{lo}$ | $W_{62}^{hi}$ | $W_{62}$ | | | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $W_{62}^{hi}$ | $\texttt{spread}(W_{62}^{hi})$ | | | | | | | | -0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $W_{63}^{lo}$ | $\texttt{spread}(W_{63}^{lo})$ | $W_{63}^{lo}$ | $W_{63}^{hi}$ | $W_{63}$ | | | | | -0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2,3,4,5} | $W_{63}^{hi}$ | $\texttt{spread}(W_{63}^{hi})$ | | | | | | | | - -Constraints: -- `sw`: construct word using $reduce_4$ -- `sd0`: decomposition gate for $W_0, W_{62}, W_{63}$ - - $W^{lo} + 2^{16} W^{hi} - W = 0$ -- `sd1`: decomposition gate for $W_{1..13}$ (split into $(3,4,11,14)$-bit pieces) - - $W^{a(3)} + 2^3 W^{b(4) lo} + 2^5 W^{b(4) hi} + 2^7 W^{c(11)} + 2^{18} W^{d(14)} - W = 0$ -- `sd2`: decomposition gate for $W_{14..48}$ (split into $(3,4,3,7,1,1,13)$-bit pieces) - - $W^{a(3)} + 2^3 W^{b(4) lo} + 2^5 W^{b(4) hi} + 2^7 W^{c(11)} + 2^{10} W^{d(14)} + 2^{17} W^{e(1)} + 2^{18} W^{f(1)} + 2^{19} W^{g(13)} - W = 0$ -- `sd3`: decomposition gate for $W_{49..61}$ (split into $(10,7,2,13)$-bit pieces) - - $W^{a(10)} + 2^{10} W^{b(7) lo} + 2^{12} W^{b(7) mid} + 2^{15} W^{b(7) hi} + 2^{17} W^{c(2)} + 2^{19} W^{d(13)} - W = 0$ - -### Compression region - -```plaintext -+----------------------------------------------------------+ -| | -| decompose E, | -| Σ_1(E) | -| | -| +---------------------------------------+ -| | | -| | reduce_5() to get H' | -| | | -+----------------------------------------------------------+ -| decompose F, decompose G | -| | -| Ch(E,F,G) | -| | -+----------------------------------------------------------+ -| | -| decompose A, | -| Σ_0(A) | -| | -| | -| +---------------------------------------+ -| | | -| | reduce_7() to get A_new, | -| | using H' | -| | | -+------------------+---------------------------------------+ -| decompose B, decompose C | -| | -| Maj(A,B,C) | -| | -| +---------------------------------------+ -| | reduce_6() to get E_new, | -| | using H' | -+------------------+---------------------------------------+ - -``` - -#### Initial round: - -s_digest|sd_abcd|sd_efgh|ss0|ss1|s_maj|s_ch_neg|s_ch|s_a_new |s_e_new |s_h_prime| $a_0$ | $a_1$ | $a_2$ | $a_3$ | $a_4$ | $a_5$ | $a_6$ | $a_7$ | $a_8$ | $a_9$ | ---------|-------|-------|---|---|-----|--------|----|--------|--------|---------|-------------|------------|-----------------------------|-------------------------------------|-------------------------------------|----------------------------------------|------------------------------------|------------------------------------|------------------------------------|------------------------------------| - 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2} |$F_0 d(7)$ |$\texttt{spread}(E_0 d(7)) $ | $E_0 b(5)^{lo}$ | $\texttt{spread}(E_0 b(5)^{lo})$ | $E_0 b(5)^{hi}$ | $\texttt{spread}(E_0 b(5)^{hi}) $ | $E_0^{lo}$ | $\mathtt{spread}(E_0^{lo})$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1} |$E_0 c(14)$ |$\texttt{spread}(E_0 c(14))$ | $E_0 a(6)^{lo}$ | $\texttt{spread}(E_0 a(6)^{lo})$ | $E_0 a(6)^{hi}$ | $\texttt{spread}(E_0 a(6)^{hi}) $ | $E_0^{hi}$ | $\mathtt{spread}(E_0^{hi})$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_0^{even}$|$\texttt{spread}(R_0^{even})$| $\texttt{spread}(E_0 b(5)^{lo})$ | $\texttt{spread}(E_0 b(5)^{hi})$ | | | | | | - 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_0^{odd}$ |$\texttt{spread}(R_0^{odd})$ | $\texttt{spread}(R_1^{odd})$ | $\texttt{spread}(E_0 d(7))$ | $\texttt{spread}(E_0 c(14))$ | | | | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_1^{even}$|$\texttt{spread}(R_1^{even})$| $\texttt{spread}(E_0 a(6)^{lo})$ | $\texttt{spread}(E_0 a(6)^{hi})$ | | | | | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_1^{odd}$ |$\texttt{spread}(R_1^{odd})$ | | | | | | | | - 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2} |$F_0 d(7)$ |$\texttt{spread}(F_0 d(7)) $ | $F_0 b(5)^{lo}$ | $\texttt{spread}(F_0 b(5)^{lo})$ | $F_0 b(5)^{hi}$ | $\texttt{spread}(F_0 b(5)^{hi}) $ | $F_0^{lo}$ | $\mathtt{spread}(F_0^{lo})$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1} |$F_0 c(14)$ |$\texttt{spread}(F_0 c(14))$ | $F_0 a(6)^{lo}$ | $\texttt{spread}(F_0 a(6)^{lo})$ | $F_0 a(6)^{hi}$ | $\texttt{spread}(F_0 a(6)^{hi}) $ | $F_0^{hi}$ | $\mathtt{spread}(F_0^{hi})$ | | - 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2} |$G_0 d(7)$ |$\texttt{spread}(G_0 d(7)) $ | $G_0 b(5)^{lo}$ | $\texttt{spread}(G_0 b(5)^{lo})$ | $G_0 b(5)^{hi}$ | $\texttt{spread}(G_0 b(5)^{hi}) $ | $G_0^{lo}$ | $\mathtt{spread}(G_0^{lo})$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1} |$G_0 c(14)$ |$\texttt{spread}(G_0 c(14))$ | $G_0 a(6)^{lo}$ | $\texttt{spread}(G_0 a(6)^{lo})$ | $G_0 a(6)^{hi}$ | $\texttt{spread}(G_0 a(6)^{hi}) $ | $G_0^{hi}$ | $\mathtt{spread}(G_0^{hi})$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$P_0^{even}$|$\texttt{spread}(P_0^{even})$| $\mathtt{spread}(E^{lo})$ | $\mathtt{spread}(E^{hi})$ | $Q_0^{odd}$ | $K_0^{lo}$ | $H_0^{lo}$ | $W_0^{lo}$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |{0,1,2,3,4,5}|$P_0^{odd}$ |$\texttt{spread}(P_0^{odd})$ | $\texttt{spread}(P_1^{odd})$ | $\Sigma_1(E_0)^{lo}$ | $\Sigma_1(E_0)^{hi}$ | $K_0^{hi}$ | $H_0^{hi}$ | $W_0^{hi}$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$P_1^{even}$|$\texttt{spread}(P_1^{even})$| $\mathtt{spread}(F^{lo})$ | $\mathtt{spread}(F^{hi})$ | $Q_1^{odd}$ | $P_1^{odd}$ | $Hprime_0^{lo}$ | $Hprime_0^{hi}$ | $Hprime_0 carry$ | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$P_1^{odd}$ |$\texttt{spread}(P_1^{odd})$ | | | | | $D_0^{lo}$ | $E_1^{lo}$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |{0,1,2,3,4,5}|$Q_0^{even}$|$\texttt{spread}(Q_0^{even})$| $\mathtt{spread}(E_{neg}^{lo})$ | $\mathtt{spread}(E_{neg}^{hi})$ | $\mathtt{spread}(E^{lo})$ | | $D_0^{hi}$ | $E_1^{hi}$ | $E_1 carry$ | - 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$Q_0^{odd}$ |$\texttt{spread}(Q_0^{odd})$ | $\texttt{spread}(Q_1^{odd})$ | | $\mathtt{spread}(E^{hi})$ | | | | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$Q_1^{even}$|$\texttt{spread}(Q_1^{even})$| $\mathtt{spread}(G^{lo})$ | $\mathtt{spread}(G^{hi})$ | | | | | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$Q_1^{odd}$ |$\texttt{spread}(Q_1^{odd})$ | | | | | | | | - 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2} |$A_0 b(11)$ |$\texttt{spread}(A_0 b(11))$ | $A_0 c(9)^{lo}$ | $\texttt{spread}(A_0 c(9)^{lo})$ | $A_0 c(9)^{mid}$ | $\texttt{spread}(A_0 c(9)^{mid})$ | $A_0^{lo}$ | $\mathtt{spread}(A_0^{lo})$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1} |$A_0 d(10)$ |$\texttt{spread}(A_0 d(10))$ | $A_0 a(2)$ | $\texttt{spread}(A_0 a(2))$ | $A_0 c(9)^{hi}$ | $\texttt{spread}(A_0 c(9)^{hi})$ | $A_0^{hi}$ | $\mathtt{spread}(A_0^{hi})$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_0^{even}$|$\texttt{spread}(R_0^{even})$| $\texttt{spread}(c(9)^{lo})$ | $\texttt{spread}(c(9)^{mid})$ | | | | | | - 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_0^{odd}$ |$\texttt{spread}(R_0^{odd})$ | $\texttt{spread}(R_1^{odd})$ | $\texttt{spread}(d(10))$ | $\texttt{spread}(b(11))$ | | | | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_1^{even}$|$\texttt{spread}(R_1^{even})$| $\texttt{spread}(a(2))$ | $\texttt{spread}(c(9)^{hi})$ | | | | | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_1^{odd}$ |$\texttt{spread}(R_1^{odd})$ | | | | | | | | - 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2} |$B_0 b(11)$ |$\texttt{spread}(B_0 b(11))$ | $B_0 c(9)^{lo}$ | $\texttt{spread}(B_0 c(9)^{lo})$ | $B_0 c(9)^{mid}$ | $\texttt{spread}(B_0 c(9)^{mid})$ | $B_0^{lo}$ | $\mathtt{spread}(B_0^{lo})$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1} |$B_0 d(10)$ |$\texttt{spread}(B_0 d(10))$ | $B_0 a(2)$ | $\texttt{spread}(B_0 a(2))$ | $B_0 c(9)^{hi}$ | $\texttt{spread}(B_0 c(9)^{hi})$ | $B_0^{hi}$ | $\mathtt{spread}(B_0^{hi})$ | | - 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2} |$C_0 b(11)$ |$\texttt{spread}(C_0 b(11))$ | $C_0 c(9)^{lo}$ | $\texttt{spread}(C_0 c(9)^{lo})$ | $C_0 c(9)^{mid}$ | $\texttt{spread}(C_0 c(9)^{mid})$ | $C_0^{lo}$ | $\mathtt{spread}(C_0^{lo})$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1} |$C_0 d(10)$ |$\texttt{spread}(C_0 d(10))$ | $C_0 a(2)$ | $\texttt{spread}(C_0 a(2))$ | $C_0 c(9)^{hi}$ | $\texttt{spread}(C_0 c(9)^{hi})$ | $C_0^{hi}$ | $\mathtt{spread}(C_0^{hi})$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$M_0^{even}$|$\texttt{spread}(M_0^{even})$| $M_1^{odd}$ | $\mathtt{spread}(A_0^{lo})$ | $\mathtt{spread}(A_0^{hi})$ | | $Hprime_0^{lo}$ | $Hprime_0^{hi}$ | | - 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |{0,1,2,3,4,5}|$M_0^{odd}$ |$\texttt{spread}(M_0^{odd})$ | $\texttt{spread}(M_1^{odd})$ | $\mathtt{spread}(B_0^{lo})$ | $\mathtt{spread}(B_0^{hi})$ | $\Sigma_0(A_0)^{lo}$ | | $A_1^{lo}$ | $A_1 carry$ | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$M_1^{even}$|$\texttt{spread}(M_1^{even})$| | $\mathtt{spread}(C_0^{lo})$ | $\mathtt{spread}(C_0^{hi})$ | $\Sigma_0(A_0)^{hi}$ | | $A_1^{hi}$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$M_1^{odd}$ |$\texttt{spread}(M_1^{odd})$ | | | | | | | | - -#### Steady-state: - -s_digest|sd_abcd|sd_efgh|ss0|ss1|s_maj|s_ch_neg|s_ch|s_a_new |s_e_new |s_h_prime| $a_0$ | $a_1$ | $a_2$ | $a_3$ | $a_4$ | $a_5$ | $a_6$ | $a_7$ | $a_8$ | $a_9$ | ---------|-------|-------|---|---|-----|--------|----|--------|--------|---------|-------------|------------|-----------------------------|-------------------------------------|-------------------------------------|----------------------------------------|------------------------------------|------------------------------------|------------------------------------|------------------------------------| - 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2} |$F_0 d(7)$ |$\texttt{spread}(E_0 d(7)) $ | $E_0 b(5)^{lo}$ | $\texttt{spread}(E_0 b(5)^{lo})$ | $E_0 b(5)^{hi}$ | $\texttt{spread}(E_0 b(5)^{hi}) $ | $E_0^{lo}$ | $\mathtt{spread}(E_0^{lo})$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1} |$E_0 c(14)$ |$\texttt{spread}(E_0 c(14))$ | $E_0 a(6)^{lo}$ | $\texttt{spread}(E_0 a(6)^{lo})$ | $E_0 a(6)^{hi}$ | $\texttt{spread}(E_0 a(6)^{hi}) $ | $E_0^{hi}$ | $\mathtt{spread}(E_0^{hi})$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_0^{even}$|$\texttt{spread}(R_0^{even})$| $\texttt{spread}(E_0 b(5)^{lo})$ | $\texttt{spread}(E_0 b(5)^{hi})$ | | | | | | - 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_0^{odd}$ |$\texttt{spread}(R_0^{odd})$ | $\texttt{spread}(R_1^{odd})$ | $\texttt{spread}(E_0 d(7))$ | $\texttt{spread}(E_0 c(14))$ | | | | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_1^{even}$|$\texttt{spread}(R_1^{even})$| $\texttt{spread}(E_0 a(6)^{lo})$ | $\texttt{spread}(E_0 a(6)^{hi})$ | | | | | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_1^{odd}$ |$\texttt{spread}(R_1^{odd})$ | | | | | | | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$P_0^{even}$|$\texttt{spread}(P_0^{even})$| $\mathtt{spread}(E^{lo})$ | $\mathtt{spread}(E^{hi})$ | $Q_0^{odd}$ | $K_0^{lo}$ | $H_0^{lo}$ | $W_0^{lo}$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |{0,1,2,3,4,5}|$P_0^{odd}$ |$\texttt{spread}(P_0^{odd})$ | $\texttt{spread}(P_1^{odd})$ | $\Sigma_1(E_0)^{lo}$ | $\Sigma_1(E_0)^{hi}$ | $K_0^{hi}$ | $H_0^{hi}$ | $W_0^{hi}$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$P_1^{even}$|$\texttt{spread}(P_1^{even})$| $\mathtt{spread}(F^{lo})$ | $\mathtt{spread}(F^{hi})$ | $Q_1^{odd}$ | $P_1^{odd}$ | $Hprime_0^{lo}$ | $Hprime_0^{hi}$ | $Hprime_0 carry$ | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$P_1^{odd}$ |$\texttt{spread}(P_1^{odd})$ | | | | | $D_0^{lo}$ | $E_1^{lo}$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |{0,1,2,3,4,5}|$Q_0^{even}$|$\texttt{spread}(Q_0^{even})$| $\mathtt{spread}(E_{neg}^{lo})$ | $\mathtt{spread}(E_{neg}^{hi})$ | $\mathtt{spread}(E^{lo})$ | | $D_0^{hi}$ | $E_1^{hi}$ | $E_1 carry$ | - 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$Q_0^{odd}$ |$\texttt{spread}(Q_0^{odd})$ | $\texttt{spread}(Q_1^{odd})$ | | $\mathtt{spread}(E^{hi})$ | | | | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$Q_1^{even}$|$\texttt{spread}(Q_1^{even})$| $\mathtt{spread}(G^{lo})$ | $\mathtt{spread}(G^{hi})$ | | | | | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$Q_1^{odd}$ |$\texttt{spread}(Q_1^{odd})$ | | | | | | | | - 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1,2} |$A_0 b(11)$ |$\texttt{spread}(A_0 b(11))$ | $A_0 c(9)^{lo}$ | $\texttt{spread}(A_0 c(9)^{lo})$ | $A_0 c(9)^{mid}$ | $\texttt{spread}(A_0 c(9)^{mid})$ | $A_0^{lo}$ | $\mathtt{spread}(A_0^{lo})$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | {0,1} |$A_0 d(10)$ |$\texttt{spread}(A_0 d(10))$ | $A_0 a(2)$ | $\texttt{spread}(A_0 a(2))$ | $A_0 c(9)^{hi}$ | $\texttt{spread}(A_0 c(9)^{hi})$ | $A_0^{hi}$ | $\mathtt{spread}(A_0^{hi})$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_0^{even}$|$\texttt{spread}(R_0^{even})$| $\texttt{spread}(c(9)^{lo})$ | $\texttt{spread}(c(9)^{mid})$ | | | | | | - 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_0^{odd}$ |$\texttt{spread}(R_0^{odd})$ | $\texttt{spread}(R_1^{odd})$ | $\texttt{spread}(d(10))$ | $\texttt{spread}(b(11))$ | | | | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_1^{even}$|$\texttt{spread}(R_1^{even})$| $\texttt{spread}(a(2))$ | $\texttt{spread}(c(9)^{hi})$ | | | | | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$R_1^{odd}$ |$\texttt{spread}(R_1^{odd})$ | | | | | | | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$M_0^{even}$|$\texttt{spread}(M_0^{even})$| $M_1^{odd}$ | $\mathtt{spread}(A_0^{lo})$ | $\mathtt{spread}(A_0^{hi})$ | | $Hprime_0^{lo}$ | $Hprime_0^{hi}$ | | - 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |{0,1,2,3,4,5}|$M_0^{odd}$ |$\texttt{spread}(M_0^{odd})$ | $\texttt{spread}(M_1^{odd})$ | $\mathtt{spread}(B_0^{lo})$ | $\mathtt{spread}(B_0^{hi})$ | $\Sigma_0(A_0)^{lo}$ | | $A_1^{lo}$ | $A_1 carry$ | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$M_1^{even}$|$\texttt{spread}(M_1^{even})$| | $\mathtt{spread}(C_0^{lo})$ | $\mathtt{spread}(C_0^{hi})$ | $\Sigma_0(A_0)^{hi}$ | | $A_1^{hi}$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |{0,1,2,3,4,5}|$M_1^{odd}$ |$\texttt{spread}(M_1^{odd})$ | | | | | | | | - -#### Final digest: -s_digest|sd_abcd|sd_efgh|ss0|ss1|s_maj|s_ch_neg|s_ch|s_a_new |s_e_new |s_h_prime| $a_0$ | $a_1$ | $a_2$ | $a_3$ | $a_4$ | $a_5$ | $a_6$ | $a_7$ | $a_8$ | $a_9$ | ---------|-------|-------|---|---|-----|--------|----|--------|--------|---------|-------------|-------------|------------------------------|-------------------------------------|-------------------------------------|----------------------------------------|------------------------------------|------------------------------------|------------------------------------|------------------------------------| - 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | $A_{63}^{lo}$ | $A_{63}^{hi}$ | $A_{63}$ | $B_{63}^{lo}$ | $B_{63}^{hi}$ | $B_{63}$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | $C_{63}^{lo}$ | $C_{63}^{hi}$ | $C_{63}$ | $C_{63}^{lo}$ | $C_{63}^{hi}$ | $C_{63}$ | | - 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | $E_{63}^{lo}$ | $E_{63}^{hi}$ | $E_{63}$ | $G_{63}^{lo}$ | $G_{63}^{hi}$ | $G_{63}$ | | - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | $F_{63}^{lo}$ | $F_{63}^{hi}$ | $F_{63}$ | $H_{63}^{lo}$ | $H_{63}^{hi}$ | $H_{63}$ | | diff --git a/book/src/design/gadgets/sha256/upp_sigma_0.png b/book/src/design/gadgets/sha256/upp_sigma_0.png deleted file mode 100644 index f509e5d58d86ae3e0c382f4a93816cac9a46a3c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46020 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 diff --git a/book/src/design/gadgets/sha256/upp_sigma_1.png b/book/src/design/gadgets/sha256/upp_sigma_1.png deleted file mode 100644 index 6f80dc710c469ff2e4c0cfd2119950acb23fa54d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42804 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 Mz#cU%F+LgjcOu=$fdBvi diff --git a/book/src/design/gadgets/sinsemilla.md b/book/src/design/gadgets/sinsemilla.md deleted file mode 100644 index b5fee9fc46..0000000000 --- a/book/src/design/gadgets/sinsemilla.md +++ /dev/null @@ -1,262 +0,0 @@ -# Sinsemilla - -## Overview -Sinsemilla is a collision-resistant hash function and commitment scheme designed to be efficient in algebraic circuit models that support [lookups](https://zcash.github.io/halo2/design/proving-system/lookup.html), such as PLONK or Halo 2. - -The security properties of Sinsemilla are similar to Pedersen hashes; it is **not** designed to be used where a random oracle, PRF, or preimage-resistant hash is required. **The only claimed security property of the hash function is collision-resistance for fixed-length inputs.** - -Sinsemilla is roughly 4 times less efficient than the algebraic hashes Rescue and Poseidon inside a circuit, but around 19 times more efficient than Rescue outside a circuit. Unlike either of these hashes, the collision resistance property of Sinsemilla can be proven based on cryptographic assumptions that have been well-established for at least 20 years. Sinsemilla can also be used as a computationally binding and perfectly hiding commitment scheme. - -The general approach is to split the message into $k$-bit pieces, and for each piece, select from a table of $2^k$ bases in our cryptographic group. We combine the selected bases using a double-and-add algorithm. This ends up being provably as secure as a vector Pedersen hash, and makes advantageous use of the lookup facility supported by Halo 2. - -## Description - -This section is an outline of how Sinsemilla works: for the normative specification, refer to [§5.4.1.9 Sinsemilla Hash Function](https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash) in the protocol spec. The incomplete point addition operator, ⸭, that we use below is also defined there. - -Let $\mathbb{G}$ be a cryptographic group of prime order $q$. We write $\mathbb{G}$ additively, with identity $\mathcal{O}$, and using $[m] P$ for scalar multiplication of $P$ by $m$. - -Let $k \geq 1$ be an integer chosen based on efficiency considerations (the table size will be $2^k$). Let $n$ be an integer, fixed for each instantiation, such that messages are $kn$ bits, where $2^n \leq \frac{q-1}{2}$. We use zero-padding to the next multiple of $k$ bits if necessary. - -$\textsf{Setup}$: Choose $Q$ and $P[0..2^k - 1]$ as $2^k + 1$ independent, verifiably random generators of $\mathbb{G}$, using a suitable hash into $\mathbb{G}$, such that none of $Q$ or $P[0..2^k - 1]$ are $\mathcal{O}$. - -> In Orchard, we define $Q$ to be dependent on a domain separator $D$. The protocol specification uses $\mathcal{Q}(D)$ in place of $Q$ and $\mathcal{S}(m)$ in place of $P[m]$. - -$\textsf{Hash}(M)$: -- Split $M$ into $n$ groups of $k$ bits. Interpret each group as a $k$-bit little-endian integer $m_i$. -- let $\mathsf{Acc}_0 := Q$ -- for $i$ from $0$ up to $n-1$: - - let $\mathsf{Acc}_{i+1} := (\mathsf{Acc}_i \;⸭\; P[m_{i+1}]) \;⸭\; \mathsf{Acc}_i$ -- return $\mathsf{Acc}_n$ - -Let $\textsf{ShortHash}(M)$ be the $x$-coordinate of $\textsf{Hash}(M)$. (This assumes that $\mathbb{G}$ is a prime-order elliptic curve in short Weierstrass form, as is the case for Pallas and Vesta.) - -> It is slightly more efficient to express a double-and-add $[2] A + R$ as $(A + R) + A$. We also use incomplete additions: it is shown in the [Sinsemilla security argument](https://zips.z.cash/protocol/protocol.pdf#sinsemillasecurity) that in the case where $\mathbb{G}$ is a prime-order short Weierstrass elliptic curve, an exceptional case for addition would lead to finding a discrete logarithm, which can be assumed to occur with negligible probability even for adversarial input. - -### Use as a commitment scheme -Choose another generator $H$ independently of $Q$ and $P[0..2^k - 1]$. - -The randomness $r$ for a commitment is chosen uniformly on $[0, q)$. - -Let $\textsf{Commit}_r(M) = \textsf{Hash}(M) \;⸭\; [r] H$. - -Let $\textsf{ShortCommit}_r(M)$ be the $x\text{-coordinate}$ of $\textsf{Commit}_r(M)$. (This again assumes that $\mathbb{G}$ is a prime-order elliptic curve in short Weierstrass form.) - -Note that unlike a simple Pedersen commitment, this commitment scheme ($\textsf{Commit}$ or $\textsf{ShortCommit}$) is not additively homomorphic. - -## Efficient implementation -The aim of the design is to optimize the number of bits that can be processed for each step of the algorithm (which requires a doubling and addition in $\mathbb{G}$) for a given table size. Using a single table of size $2^k$ group elements, we can process $k$ bits at a time. - -### Incomplete addition - -In each step of Sinsemilla we want to compute $A_{i+1} := (A_i \;⸭\; P_i) \;⸭\; A_i$. Let -$R_i := A_i \;⸭\; P_i$ be the intermediate result such that $A_{i+1} := A_i \;⸭\; R_i$. -Recalling the [incomplete addition formulae](ecc/addition.md#incomplete-addition): - -$$ -\begin{aligned} -x_3 &= \left(\frac{y_1 - y_2}{x_1 - x_2}\right)^2 - x_1 - x_2 \\ -y_3 &= \frac{y_1 - y_2}{x_1 - x_2} \cdot (x_1 - x_3) - y_1 \\ -\end{aligned} -$$ - -Let $\lambda = \frac{y_1 - y_2}{x_1 - x_2}$. Substituting the coordinates for each of the -incomplete additions in turn, and rearranging, we get - -$$ -\begin{aligned} -\lambda_{1,i} &= \frac{y_{A,i} - y_{P,i}}{x_{A,i} - x_{P,i}} \\ -&\implies y_{A,i} - y_{P,i} = \lambda_{1,i} \cdot (x_{A,i} - x_{P,i}) \\ -&\implies y_{P,i} = y_{A,i} - \lambda_{1,i} \cdot (x_{A,i} - x_{P,i}) \\ -x_{R,i} &= \lambda_{1,i}^2 - x_{A,i} - x_{P,i} \\ -y_{R,i} &= \lambda_{1,i} \cdot (x_{A,i} - x_{R,i}) - y_{A,i} \\ -\end{aligned} -$$ -and -$$ -\begin{aligned} -\lambda_{2,i} &= \frac{y_{A,i} - y_{R,i}}{x_{A,i} - x_{R,i}} \\ -&\implies y_{A,i} - y_{R,i} = \lambda_{2,i} \cdot (x_{A,i} - x_{R,i}) \\ -&\implies y_{A,i} - \left( \lambda_{1,i} \cdot (x_{A,i} - x_{R,i}) - y_{A,i} \right) = \lambda_{2,i} \cdot (x_{A,i} - x_{R,i}) \\ -&\implies 2 \cdot y_{A,i} = (\lambda_{1,i} + \lambda_{2,i}) \cdot (x_{A,i} - x_{R,i}) \\ -x_{A,i+1} &= \lambda_{2,i}^2 - x_{A,i} - x_{R,i} \\ -y_{A,i+1} &= \lambda_{2,i} \cdot (x_{A,i} - x_{A,i+1}) - y_{A,i}. \\ -\end{aligned} -$$ - -### Constraint program -Let $\mathcal{P} = \left\{(j,\, x_{P[j]},\, y_{P[j]}) \text{ for } j \in \{0..2^k - 1\}\right\}$. - -Input: $m_{1..=n}$. (The message words are 1-indexed here, as in the [protocol spec](https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash), but we start the loop from $i = 0$ so that $(x_{A,i}, y_{A,i})$ corresponds to $\mathsf{Acc}_i$ in the protocol spec.) - -Output: $(x_{A,n},\, y_{A,n})$. - -- $(x_{A,0},\, y_{A,0}) = Q$ -- for $i$ from $0$ up to $n-1$: - - $y_{P,i} = y_{A,i} - \lambda_{1,i} \cdot (x_{A,i} - x_{P,i})$ - - $x_{R,i} = \lambda_{1,i}^2 - x_{A,i} - x_{P,i}$ - - $2 \cdot y_{A,i} = (\lambda_{1,i} + \lambda_{2,i}) \cdot (x_{A,i} - x_{R,i})$ - - $(m_{i+1},\, x_{P,i},\, y_{P,i}) \in \mathcal{P}$ - - $\lambda_{2,i}^2 = x_{A,i+1} + x_{R,i} + x_{A,i}$ - - $\lambda_{2,i} \cdot (x_{A,i} - x_{A,i+1}) = y_{A,i} + y_{A,i+1}$ - - -## PLONK / Halo 2 constraints - -### Message decomposition -We have an $n$-bit message $m = m_1 + 2^k m_2 + ... + 2^{k\cdot (n-1)} m_n$. (Note that the message words are 1-indexed as in the [protocol spec](https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash).) - -Initialise the running sum $z_0 = \alpha$ and define $z_{i + 1} := \frac{z_{i} - m_{i+1}}{2^K}$. We will end up with $z_n = 0.$ - -Rearranging gives us an expression for each word of the original message -$m_{i+1} = z_{i} - 2^k \cdot z_{i + 1}$, which we can look up in the table. We position -$z_{i}$ and $z_{i + 1}$ in adjacent rows of the same column, so we can sequentially apply -the constraint across the entire message. - -In other words, $z_{n-i} = \sum\limits_{h=0}^{i-1} 2^{kh} \cdot m_{h+1}$. - -> For a little-endian decomposition as used here, the running sum is initialized to the scalar and ends at 0. For a big-endian decomposition as used in [variable-base scalar multiplication](https://hackmd.io/o9EzZBwxSWSi08kQ_fMIOw), the running sum would start at 0 and end with recovering the original scalar. - -### Efficient packing - -The running sum only applies to message words within a single field element. That means if -$n \geq \mathtt{PrimeField::NUM\_BITS}$ then we will need several disjoint running sums. A -longer message can be constructed by splitting the message words across several field -elements, and then running several instances of the constraints below. - -The expression for $m_{i+1}$ above requires $n + 1$ rows in the $z_{i}$ column, leaving a -one-row gap in adjacent columns and making $\mathsf{Acc}_i$ tricker to accumulate. In -order to support chaining multiple field elements without a gap, we use a slightly more -complicated expression for $m_{i+1}$ that includes a selector: - -$$m_{i+1} = z_{i} - 2^k \cdot q_{run,i} \cdot z_{i+1}$$ - -This effectively forces $\mathbf{z}_n$ to zero for the last step of each element, which -allows the cell that would have been $\mathbf{z}_n$ to be used to reinitialize the running -sum for the next element. - -With this sorted out, the incomplete addition accumulator can eliminate $y_{A,i}$ almost -entirely, by substituting for $x$ and $\lambda$ values in the current and next rows. The -two exceptions are at the start of Sinsemilla (where we need to constrain the accumulator -to have initial value $Q$), and the end (where we need to witness $y_{A,n}$ for use -outside of Sinsemilla). - -### Selectors - -We need a total of four logical selectors to: - -- Control the Sinsemilla gate and lookup. -- Distinguish between the last message word in a running sum and its earlier words. -- Mark the start of Sinsemilla. -- Mark the end of Sinsemilla. - -We use regular selector columns for the Sinsemilla gate selector $q_{S1}$ and Sinsemilla -start selector $q_{S4}.$ The other two selectors are synthesized from a single fixed -column $q_{S2}$ as follows: - -$$ -\begin{aligned} -q_{S3} &= q_{S2} \cdot (q_{S2} - 1) \\ -q_{run} &= q_{S2} - q_{S3} \\ -\end{aligned} -$$ - -$$ -\begin{array}{|c|c|c|} -\hline -q_{S2} & q_{S3} & q_{run} \\\hline - 0 & 0 & 0 \\\hline - 1 & 0 & 1 \\\hline - 2 & 2 & 0 \\\hline -\end{array} -$$ - -We set $q_{S2}$ to $1$ on most Sinsemilla rows, and $0$ for the last step of each element, -except for the last element where it is set to $2$. We can then use $q_{S3}$ to toggle -between constraining the substituted $y_{A,i+1}$ on adjacent rows, and the witnessed -$y_{A,n}$ at the end of Sinsemilla: - -$$ -\lambda_{2,i} \cdot (x_{A,i} - x_{A,i+1}) = y_{A,i} + \frac{2 - q_{S3}}{2} \cdot y_{A,i+1} + \frac{q_{S3}}{2} \cdot y_{A,n} -$$ - -### Generator lookup table -The Sinsemilla circuit makes use of $2^{10}$ pre-computed random generators. These are loaded into a lookup table: -$$ -\begin{array}{|c|c|c|} -\hline - table_{idx} & table_x & table_y \\\hline - 0 & x_{P[0]} & y_{P[0]} \\\hline - 1 & x_{P[1]} & y_{P[1]} \\\hline - 2 & x_{P[2]} & y_{P[2]} \\\hline - \vdots & \vdots & \vdots \\\hline - 2^{10} - 1 & x_{P[2^{10}-1]} & y_{P[2^{10}-1]} \\\hline -\end{array} -$$ - -### Layout -$$ -\begin{array}{|c|c|c|c|c|c|c|c|c|c|} -\hline -\text{Step} & x_A & x_P & bits & \lambda_1 & \lambda_2 & q_{S1} & q_{S2} & q_{S4} & \textsf{fixed\_y\_Q}\\\hline - 0 & x_Q & x_{P[m_1]} & z_0 & \lambda_{1,0} & \lambda_{2,0} & 1 & 1 & 1 & y_Q \\\hline - 1 & x_{A,1} & x_{P[m_2]} & z_1 & \lambda_{1,1} & \lambda_{2,1} & 1 & 1 & 0 & 0 \\\hline - 2 & x_{A,2} & x_{P[m_3]} & z_2 & \lambda_{1,2} & \lambda_{2,2} & 1 & 1 & 0 & 0 \\\hline - \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & 1 & 1 & 0 & 0 \\\hline - n-1 & x_{A,n-1} & x_{P[m_n]} & z_{n-1} & \lambda_{1,n-1} & \lambda_{2,n-1} & 1 & 0 & 0 & 0 \\\hline - 0' & x'_{A,0} & x_{P[m'_1]} & z'_0 & \lambda'_{1,0} & \lambda'_{2,0} & 1 & 1 & 0 & 0 \\\hline - 1' & x'_{A,1} & x_{P[m'_2]} & z'_1 & \lambda'_{1,1} & \lambda'_{2,1} & 1 & 1 & 0 & 0 \\\hline - 2' & x'_{A,2} & x_{P[m'_3]} & z'_2 & \lambda'_{1,2} & \lambda'_{2,2} & 1 & 1 & 0 & 0 \\\hline - \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & 1 & 1 & 0 & 0 \\\hline - n-1' & x'_{A,n-1} & x_{P[m'_n]} & z'_{n-1} & \lambda'_{1,n-1} & \lambda'_{2,n-1} & 1 & 2 & 0 & 0 \\\hline - n' & x'_{A,n} & & & y_{A,n} & & 0 & 0 & 0 & 0 \\\hline -\end{array} -$$ - -$x_Q$, $z_0$, $z'_0$, etc. are copied in using equality constraints. - -### Optimized Sinsemilla gate -$$ -\begin{array}{lrcl} -\text{For } i \in [0, n), \text{ let} &x_{R,i} &=& \lambda_{1,i}^2 - x_{A,i} - x_{P,i} \\ - &Y_{A,i} &=& (\lambda_{1,i} + \lambda_{2,i}) \cdot (x_{A,i} - x_{R,i}) \\ - &y_{P,i} &=& Y_{A,i}/2 - \lambda_{1,i} \cdot (x_{A,i} - x_{P,i}) \\ - &m_{i+1} &=& z_{i} - q_{run,i} \cdot z_{i+1} \cdot 2^k \\ - &q_{run} &=& q_{S2} - q_{S3} \\ - &q_{S3} &=& q_{S2} \cdot (q_{S2} - 1) -\end{array} -$$ - -The Halo 2 circuit API can automatically substitute $y_{P,i}$, $x_{R,i}$, $Y_{A,i}$, and -$Y_{A,i+1}$, so we don't need to do that manually. - -- $x_{A,0} = x_Q$ -- $2 \cdot y_Q = Y_{A,0}$ -- for $i$ from $0$ up to $n-1$: - - $(m_{i+1},\, x_{P,i},\, y_{P,i}) \in \mathcal{P}$ - - $\lambda_{2,i}^2 = x_{A,i+1} + x_{R,i} + x_{A,i}$ - - $4 \cdot \lambda_{2,i} \cdot (x_{A,i} - x_{A,i+1}) = 2 \cdot Y_{A,i} + (2 - q_{S3}) \cdot Y_{A,i+1} + 2 q_{S3} \cdot y_{A,n}$ - -Note that each term of the last constraint is multiplied by $4$ relative to the constraint program given earlier. This is a small optimization that avoids divisions by $2$. - -By gating the lookup expression on $q_{S1}$, we avoid the need to fill in unused cells with dummy values to pass the lookup argument. The optimized lookup value (using a default index of $0$) is: - -$$ -\begin{array}{ll} -(&q_{S1} \cdot m_{i+1}, \\ - &q_{S1} \cdot x_{P,i} + (1 - q_{S1}) \cdot x_{P,0}, \\ - &q_{S1} \cdot y_{P,i} + (1 - q_{S1}) \cdot y_{P,0} \;\;\;) -\end{array} -$$ - -This increases the degree of the lookup argument to $6$. - -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline -4 & q_{S4} \cdot (2 \cdot y_Q - Y_{A,0}) = 0 \\\hline -6 & q_{S1,i} \Rightarrow (m_{i+1},\, x_{P,i},\, y_{P,i}) \in \mathcal{P} \\\hline -3 & q_{S1,i} \cdot \big(\lambda_{2,i}^2 - (x_{A,i+1} + x_{R,i} + x_{A,i})\big) \\\hline -5 & q_{S1,i} \cdot \left(4 \cdot \lambda_{2,i} \cdot (x_{A,i} - x_{A,i+1}) - (2 \cdot Y_{A,i} + (2 - q_{S3,i}) \cdot Y_{A,i+1} + 2 \cdot q_{S3,i} \cdot y_{A,n})\right) = 0 \\\hline -\end{array} -$$ diff --git a/book/src/design/gadgets/sinsemilla/merkle-crh.md b/book/src/design/gadgets/sinsemilla/merkle-crh.md deleted file mode 100644 index 774c7eea4d..0000000000 --- a/book/src/design/gadgets/sinsemilla/merkle-crh.md +++ /dev/null @@ -1,144 +0,0 @@ -# MerkleCRH - -## Message decomposition -$\mathsf{SinsemillaHash}$ is used in the [$\mathsf{MerkleCRH^{Orchard}}$ hash function](https://zips.z.cash/protocol/protocol.pdf#orchardmerklecrh). The input to $\mathsf{SinsemillaHash}$ is: - -$${l\star} \,||\, {\textsf{left}\star} \,||\, {\textsf{right}\star},$$ - -where: -- ${l\star} = \textsf{I2LEBSP}_{10}(l) = \textsf{I2LEBSP}_{10}(\textsf{MerkleDepth}^\textsf{Orchard} - 1 - \textsf{layer})$, -- ${\textsf{left}\star} = \textsf{I2LEBSP}_{\ell_{\textsf{Merkle}}^{\textsf{Orchard}}}(\textsf{left})$, -- ${\textsf{right}\star} = \textsf{I2LEBSP}_{\ell_{\textsf{Merkle}}^{\textsf{Orchard}}}(\textsf{right})$, - -with $\ell_{\textsf{Merkle}}^{\textsf{Orchard}} = 255.$ $\textsf{left}\star$ and -$\textsf{right}\star$ are allowed to be non-canonical $255$-bit encodings of -$\textsf{left}$ and $\textsf{right}$. - -Sinsemilla operates on multiples of 10 bits, so we start by decomposing the message into -chunks: - -$$ -\begin{aligned} -l\star &= a_0 \\ -\textsf{left}\star &= a_1 \bconcat b_0 \bconcat b_1 \\ - &= (\text{bits } 0..=239 \text{ of } \textsf{ left }) \bconcat - (\text{bits } 240..=249 \text{ of } \textsf{left}) \bconcat - (\text{bits } 250..=254 \text{ of } \textsf{left}) \\ -\textsf{right}\star &= b_2 \bconcat c \\ - &= (\text{bits } 0..=4 \text{ of } \textsf{right}) \bconcat - (\text{bits } 5..=254 \text{ of } \textsf{right}) -\end{aligned} -$$ - -Then we recompose the chunks into `MessagePiece`s: - -$$ -\begin{array}{|c|l|} -\hline -\text{Length (bits)} & \text{Piece} \\\hline -250 & a = a_0 \bconcat a_1 \\ -20 & b = b_0 \bconcat b_1 \bconcat b_2 \\ -250 & c \\\hline -\end{array} -$$ - -Each message piece is constrained by $\SinsemillaHash$ to its stated length. Additionally, -$\textsf{left}$ and $\textsf{right}$ are witnessed as field elements, so we know that they -are canonical. However, we need additional constraints to enforce that the chunks are the -correct bit lengths (or else they could overlap in the decompositions and allow the prover -to witness an arbitrary $\SinsemillaHash$ message). - -Some of these constraints can be implemented with reusable circuit gadgets. We define a -custom gate controlled by the selector $q_\mathsf{decompose}$ to hold the remaining -constraints. - -## Bit length constraints - -Chunk $c$ is directly constrained by Sinsemilla. We constrain the remaining chunks with -the following constraints: - -### $a_0, a_1$ - -$z_{1,a}$, the index-1 running sum output of $\textsf{SinsemillaHash}(a)$, is copied into -the gate. $z_{1,a}$ has been constrained by $\textsf{SinsemillaHash}$ to be $240$ bits, -and is precisely $a_1$. We recover chunk $a_0$ using $a, z_{1,a}:$ -$$ -\begin{aligned} -z_{1,a} &= \frac{a - a_0}{2^{10}}\\ - &= a_1 \\ - \implies a_0 &= a - z_{1,a} \cdot 2^{10}. -\end{aligned} -$$ - -### $b_0, b_1, b_2$ - -$z_{1,b}$, the index-1 running sum output of $\textsf{SinsemillaHash}(b)$, is copied into -the gate. $z_{1,b}$ has been constrained by $\textsf{SinsemillaHash}$ to be $10$ bits. We -witness the subpieces $b_1, b_2$ outside this gate, and constrain them each to be $5$ -bits. Inside the gate, we check that $$b_1 + 2^5 \cdot b_2 = z_{1,b}.$$ -We also recover the subpiece $b_0$ using $(b, z_{1,b})$: -$$ -\begin{aligned} -z_{1,b} &= \frac{b - b_{0..=10}}{2^{10}}\\ - \implies b_0 &= b - (z_{1,b} \cdot 2^{10}). -\end{aligned} -$$ - -### Constraints - -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline - & \ShortLookupRangeCheck{b_1, 5} \\\hline - & \ShortLookupRangeCheck{b_2, 5} \\\hline -2 & q_\mathsf{decompose} \cdot (z_{1,b} - (b_1 + b_2 \cdot 2^5)) = 0 \\\hline -\end{array} -$$ - -where $\ShortLookupRangeCheck{}$ is a -[short lookup range check](../decomposition.md#short-range-check). - -## Decomposition constraints - -We have now derived or witnessed every subpiece, and range-constrained every subpiece: -- $a_0$ ($10$ bits), derived as $a_0 = a - 2^{10} \cdot z_{1,a}$; -- $a_1$ ($240$ bits), equal to $z_{1,a}$; -- $b_0$ ($10$ bits), derived as $b_0 = b - 2^{10} \cdot z_{1,b}$; -- $b_1$ ($5$ bits) is witnessed and constrained outside the gate; -- $b_2$ ($5$ bits) is witnessed and constrained outside the gate; -- $c$ ($250$ bits) is witnessed and constrained outside the gate. -- $b_1 + 2^5 \cdot b_2$ is constrained to equal $z_{1, b}$. - -We can now use them to reconstruct the original field element inputs: - -$$ -\begin{align} -l &= a_0 \\ -\mathsf{left} &= a_1 + 2^{240} \cdot b_0 + 2^{250} \cdot b_1 \\ -\mathsf{right} &= b_2 + 2^5 \cdot c -\end{align} -$$ - -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline -2 & q_\mathsf{decompose} \cdot (a_0 - l) = 0 \\\hline -2 & q_\mathsf{decompose} \cdot (a_1 + (b_0 + b_1 \cdot 2^{10}) \cdot 2^{240} - \mathsf{left}) = 0 \\\hline -2 & q_\mathsf{decompose} \cdot (b_2 + c \cdot 2^5 - \mathsf{right}) = 0 \\\hline -\end{array} -$$ - -## Region layout - -$$ -\begin{array}{|c|c|c|c|c|c} - & & & & & q_\mathsf{decompose} \\\hline - a & b & c & \mathsf{left} & \mathsf{right} & 1 \\\hline -z_{1,a} &z_{1,b} & b_1 & b_2 & l & 0 \\\hline -\end{array} -$$ - -## Circuit components -The Orchard circuit spans $10$ advice columns while the $\textsf{Sinsemilla}$ chip only uses $5$ advice columns. We distribute the path hashing evenly across two $\textsf{Sinsemilla}$ chips to make better use of the available circuit area. Since the output from the previous layer hash is copied into the next layer hash, we maintain continuity even when moving from one chip to the other. diff --git a/book/src/design/implementation.md b/book/src/design/implementation.md deleted file mode 100644 index d2557ff700..0000000000 --- a/book/src/design/implementation.md +++ /dev/null @@ -1 +0,0 @@ -# Implementation diff --git a/book/src/design/implementation/fields.md b/book/src/design/implementation/fields.md deleted file mode 100644 index 81226e5430..0000000000 --- a/book/src/design/implementation/fields.md +++ /dev/null @@ -1,97 +0,0 @@ -# Fields - -The [Pasta curves](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/) -that we use in `halo2` are designed to be highly 2-adic, meaning that a large $2^S$ -[multiplicative subgroup](../../background/fields.md#multiplicative-subgroups) exists in -each field. That is, we can write $p - 1 \equiv 2^S \cdot T$ with $T$ odd. For both Pallas -and Vesta, $S = 32$; this helps to simplify the field implementations. - -## Sarkar square-root algorithm (table-based variant) - -We use a technique from [Sarkar2020](https://eprint.iacr.org/2020/1407.pdf) to compute -[square roots](../../background/fields.md#square-roots) in `halo2`. The intuition behind -the algorithm is that we can split the task into computing square roots in each -multiplicative subgroup. - -Suppose we want to find the square root of $u$ modulo one of the Pasta primes $p$, where -$u$ is a non-zero square in $\mathbb{Z}_p^\times$. We define a $2^S$ -[root of unity](../../background/fields.md#roots-of-unity) $g = z^T$ where $z$ is a -non-square in $\mathbb{Z}_p^\times$, and precompute the following tables: - -$$ -gtab = \begin{bmatrix} -g^0 & g^1 & ... & g^{2^8 - 1} \\ -(g^{2^8})^0 & (g^{2^8})^1 & ... & (g^{2^8})^{2^8 - 1} \\ -(g^{2^{16}})^0 & (g^{2^{16}})^1 & ... & (g^{2^{16}})^{2^8 - 1} \\ -(g^{2^{24}})^0 & (g^{2^{24}})^1 & ... & (g^{2^{24}})^{2^8 - 1} -\end{bmatrix} -$$ - -$$ -invtab = \begin{bmatrix} -(g^{-2^{24}})^0 & (g^{-2^{24}})^1 & ... & (g^{-2^{24}})^{2^8 - 1} -\end{bmatrix} -$$ - -Let $v = u^{(T-1)/2}$. We can then define $x = uv \cdot v = u^T$ as an element of the -$2^S$ multiplicative subgroup. - -Let $x_3 = x, x_2 = x_3^{2^8}, x_1 = x_2^{2^8}, x_0 = x_1^{2^8}.$ - -### i = 0, 1 -Using $invtab$, we lookup $t_0$ such that -$$ -x_0 = (g^{-2^{24}})^{t_0} \implies x_0 \cdot g^{t_0 \cdot 2^{24}} = 1. -$$ - -Define $\alpha_1 = x_1 \cdot (g^{2^{16}})^{t_0}.$ - -### i = 2 -Lookup $t_1$ s.t. -$$ -\begin{array}{ll} -\alpha_1 = (g^{-2^{24}})^{t_1} &\implies x_1 \cdot (g^{2^{16}})^{t_0} = (g^{-2^{24}})^{t_1} \\ -&\implies -x_1 \cdot g^{(t_0 + 2^8 \cdot t_1) \cdot 2^{16}} = 1. -\end{array} -$$ - -Define $\alpha_2 = x_2 \cdot (g^{2^8})^{t_0 + 2^8 \cdot t_1}.$ - -### i = 3 -Lookup $t_2$ s.t. - -$$ -\begin{array}{ll} -\alpha_2 = (g^{-2^{24}})^{t_2} &\implies x_2 \cdot (g^{2^8})^{t_0 + 2^8\cdot {t_1}} = (g^{-2^{24}})^{t_2} \\ -&\implies x_2 \cdot g^{(t_0 + 2^8 \cdot t_1 + 2^{16} \cdot t_2) \cdot 2^8} = 1. -\end{array} -$$ - -Define $\alpha_3 = x_3 \cdot g^{t_0 + 2^8 \cdot t_1 + 2^{16} \cdot t_2}.$ - -### Final result -Lookup $t_3$ such that - -$$ -\begin{array}{ll} -\alpha_3 = (g^{-2^{24}})^{t_3} &\implies x_3 \cdot g^{t_0 + 2^8\cdot {t_1} + 2^{16} \cdot t_2} = (g^{-2^{24}})^{t_3} \\ -&\implies x_3 \cdot g^{t_0 + 2^8 \cdot t_1 + 2^{16} \cdot t_2 + 2^{24} \cdot t_3} = 1. -\end{array} -$$ - -Let $t = t_0 + 2^8 \cdot t_1 + 2^{16} \cdot t_2 + 2^{24} \cdot t_3$. - -We can now write -$$ -\begin{array}{lclcl} -x_3 \cdot g^{t} = 1 &\implies& x_3 &=& g^{-t} \\ -&\implies& uv^2 &=& g^{-t} \\ -&\implies& uv &=& v^{-1} \cdot g^{-t} \\ -&\implies& uv \cdot g^{t / 2} &=& v^{-1} \cdot g^{-t / 2}. -\end{array} -$$ - -Squaring the RHS, we observe that $(v^{-1} g^{-t / 2})^2 = v^{-2}g^{-t} = u.$ Therefore, -the square root of $u$ is $uv \cdot g^{t / 2}$; the first part we computed earlier, and -the second part can be computed with three multiplications using lookups in $gtab$. diff --git a/book/src/design/implementation/proofs.md b/book/src/design/implementation/proofs.md deleted file mode 100644 index 4b1339fb73..0000000000 --- a/book/src/design/implementation/proofs.md +++ /dev/null @@ -1,89 +0,0 @@ -# Halo 2 proofs - -## Proofs as opaque byte streams - -In proving system implementations like `bellman`, there is a concrete `Proof` struct that -encapsulates the proof data, is returned by a prover, and can be passed to a verifier. - -`halo2` does not contain any proof-like structures, for several reasons: - -- The Proof structures would contain vectors of (vectors of) curve points and scalars. - This complicates serialization/deserialization of proofs because the lengths of these - vectors depend on the configuration of the circuit. However, we didn't want to encode - the lengths of vectors inside of proofs, because at runtime the circuit is fixed, and - thus so are the proof sizes. -- It's easy to accidentally put stuff into a Proof structure that isn't also placed in the - transcript, which is a hazard when developing and implementing a proving system. -- We needed to be able to create multiple PLONK proofs at the same time; these proofs - share many different substructures when they are for the same circuit. - -Instead, `halo2` treats proof objects as opaque byte streams. Creation and consumption of -these byte streams happens via the transcript: - -- The `TranscriptWrite` trait represents something that we can write proof components to - (at proving time). -- The `TranscriptRead` trait represents something that we can read proof components from - (at verifying time). - -Crucially, implementations of `TranscriptWrite` are responsible for simultaneously writing -to some `std::io::Write` buffer at the same time that they hash things into the transcript, -and similarly for `TranscriptRead`/`std::io::Read`. - -As a bonus, treating proofs as opaque byte streams ensures that verification accounts for -the cost of deserialization, which isn't negligible due to point compression. - -## Proof encoding - -A Halo 2 proof, constructed over a curve $E(\mathbb{F}_p)$, is encoded as a stream of: - -- Points $P \in E(\mathbb{F}_p)$ (for commitments to polynomials), and -- Scalars $s \in \mathbb{F}_q$ (for evaluations of polynomials, and blinding values). - -For the Pallas and Vesta curves, both points and scalars have 32-byte encodings, meaning -that proofs are always a multiple of 32 bytes. - -The `halo2` crate supports proving multiple instances of a circuit simultaneously, in -order to share common proof components and protocol logic. - -In the encoding description below, we will use the following circuit-specific constants: - -- $k$ - the size parameter of the circuit (which has $2^k$ rows). -- $A$ - the number of advice columns. -- $F$ - the number of fixed columns. -- $I$ - the number of instance columns. -- $L$ - the number of lookup arguments. -- $P$ - the number of permutation arguments. -- $\textsf{Col}_P$ - the number of columns involved in permutation argument $P$. -- $D$ - the maximum degree for the quotient polynomial. -- $Q_A$ - the number of advice column queries. -- $Q_F$ - the number of fixed column queries. -- $Q_I$ - the number of instance column queries. -- $M$ - the number of instances of the circuit that are being proven simultaneously. - -As the proof encoding directly follows the transcript, we can break the encoding into -sections matching the Halo 2 protocol: - -- PLONK commitments: - - $A$ points (repeated $M$ times). - - $2L$ points (repeated $M$ times). - - $P$ points (repeated $M$ times). - - $L$ points (repeated $M$ times). - -- Vanishing argument: - - $D - 1$ points. - - $Q_I$ scalars (repeated $M$ times). - - $Q_A$ scalars (repeated $M$ times). - - $Q_F$ scalars. - - $D - 1$ scalars. - -- PLONK evaluations: - - $(2 + \textsf{Col}_P) \times P$ scalars (repeated $M$ times). - - $5L$ scalars (repeated $M$ times). - -- Multiopening argument: - - 1 point. - - 1 scalar per set of points in the multiopening argument. - -- Polynomial commitment scheme: - - $1 + 2k$ points. - - $2$ scalars. diff --git a/book/src/design/implementation/selector-combining.md b/book/src/design/implementation/selector-combining.md deleted file mode 100644 index a6e98a9529..0000000000 --- a/book/src/design/implementation/selector-combining.md +++ /dev/null @@ -1,116 +0,0 @@ -# Selector combining - -Heavy use of custom gates can lead to a circuit defining many binary selectors, which -would increase proof size and verification time. - -This section describes an optimization, applied automatically by halo2, that combines -binary selector columns into fewer fixed columns. - -The basic idea is that if we have $\ell$ binary selectors labelled $1, \ldots, \ell$ that -are enabled on disjoint sets of rows, then under some additional conditions we can combine -them into a single fixed column, say $q$, such that: -$$ -q = \begin{cases} - k, &\text{if the selector labelled } k \text{ is } 1 \\ - 0, &\text{if all these selectors are } 0. -\end{cases} -$$ - -However, the devil is in the detail. - -The halo2 API allows defining some selectors to be "simple selectors", subject to the -following condition: - -> Every polynomial constraint involving a simple selector $s$ must be of the form -> $s \cdot t = 0,$ where $t$ is a polynomial involving *no* simple selectors. - -Suppose that $s$ has label $k$ in some set of $\ell$ simple selectors that are combined -into $q$ as above. Then this condition ensures that replacing $s$ by -$q \cdot \prod_{1 \leq h \leq \ell,\,h \neq k}\; (h - q)$ will not change the meaning of -any constraints. - -> It would be possible to relax this condition by ensuring that every use of a binary -> selector is substituted by a precise interpolation of its value from the corresponding -> combined selector. However, -> -> * the restriction simplifies the implementation, developer tooling, and human -> understanding and debugging of the resulting constraint system; -> * the scope to apply the optimization is not impeded very much by this restriction for -> typical circuits. - -Note that replacing $s$ by $q \cdot \prod_{1 \leq h \leq \ell,\,h \neq k}\; (h - q)$ will -increase the degree of constraints selected by $s$ by $\ell-1$, and so we must choose the -selectors that are combined in such a way that the maximum degree bound is not exceeded. - -## Identifying selectors that can be combined - -We need a partition of the overall set of selectors $s_0, \ldots, s_{m-1}$ into subsets -(called "combinations"), such that no two selectors in a combination are enabled on the -same row. - -Labels must be unique within a combination, but they are not unique across combinations. -Do not confuse a selector's index with its label. - -Suppose that we are given $\mathsf{max\_degree}$, the degree bound of the circuit. - -We use the following algorithm: - -1. Leave nonsimple selectors unoptimized, i.e. map each of them to a separate fixed - column. -2. Check (or ensure by construction) that all polynomial constraints involving each simple - selector $s_i$ are of the form $s_i \cdot t_{i,j} = 0$ where $t_{i,j}$ do not involve - any simple selectors. For each $i$, record the maximum degree of any $t_{i,j}$ as - $d^\mathsf{max}_i$. -3. Compute a binary "exclusion matrix" $X$ such that $X_{j,i}$ is $1$ whenever $i \neq j$ - and $s_i$ and $s_j$ are enabled on the same row; and $0$ otherwise. - > Since $X$ is symmetric and is zero on the diagonal, we can represent it by either its - > upper or lower triangular entries. The rest of the algorithm is guaranteed only to - > access only the entries $X_{j,i}$ where $j > i$. -4. Initialize a boolean array $\mathsf{added}_{0..{k-1}}$ to all $\mathsf{false}$. - > $\mathsf{added}_i$ will record whether $s_i$ has been included in any combination. -6. Iterate over the $s_i$ that have not yet been added to any combination: - * a. Add $s_i$ to a fresh combination $c$, and set $\mathsf{added}_i = \mathsf{true}$. - * b. Let mut $d := d^\mathsf{max}_i - 1$. - > $d$ is used to keep track of the largest degree, *excluding* the selector - > expression, of any gate involved in the combination $c$ so far. - * c. Iterate over all the selectors $s_j$ for $j > i$ that can potentially join $c$, - i.e. for which $\mathsf{added}_j$ is false: - * i. (Optimization) If $d + \mathsf{len}(c) = \mathsf{max\_degree}$, break to the - outer loop, since no more selectors can be added to $c$. - * ii. Let $d^\mathsf{new} = \mathsf{max}(d, d^\mathsf{max}_j-1)$. - * iii. If $X_{j,i'}$ is $\mathsf{true}$ for any $i'$ in $c$, or if - $d^\mathsf{new} + (\mathsf{len}(c) + 1) > \mathsf{max\_degree}$, break to the outer - loop. - > $d^\mathsf{new} + (\mathsf{len}(c) + 1)$ is the maximum degree, *including* the - > selector expression, of any constraint that would result from adding $s_j$ to the - > combination $c$. - * iv. Set $d := d^\mathsf{new}$. - * v. Add $s_j$ to $c$ and set $\mathsf{added}_j := \mathsf{true}$. - * d. Allocate a fixed column $q_c$, initialized to all-zeroes. - * e. For each selector $s' \in c$: - * i. Label $s'$ with a distinct index $k$ where $1 \leq k \leq \mathsf{len}(c)$. - * ii. Record that $s'$ should be substituted with - $q_c \cdot \prod_{1 \leq h \leq \mathsf{len}(c),\,h \neq k} (h-q_c)$ in all gate - constraints. - * iii. For each row $r$ such that $s'$ is enabled at $r$, assign the value $k$ to - $q_c$ at row $r$. - -The above algorithm is implemented in -[halo2_proofs/src/plonk/circuit/compress_selectors.rs](https://github.com/zcash/halo2/blob/main/halo2_proofs/src/plonk/circuit/compress_selectors.rs). -This is used by the `compress_selectors` function of -[halo2_proofs/src/plonk/circuit.rs](https://github.com/zcash/halo2/blob/main/halo2_proofs/src/plonk/circuit.rs) -which does the actual substitutions. - -## Writing circuits to take best advantage of selector combining - -For this optimization it is beneficial for a circuit to use simple selectors as far as -possible, rather than fixed columns. It is usually not beneficial to do manual combining -of selectors, because the resulting fixed columns cannot take part in the automatic -combining. That means that to get comparable results you would need to do a global -optimization manually, which would interfere with writing composable gadgets. - -Whether two selectors are enabled on the same row (and so are inhibited from being -combined) depends on how regions are laid out by the floor planner. The currently -implemented floor planners do not attempt to take this into account. We suggest not -worrying about it too much — the gains that can be obtained by cajoling a floor planner to -shuffle around regions in order to improve combining are likely to be relatively small. diff --git a/book/src/design/protocol.md b/book/src/design/protocol.md deleted file mode 100644 index cd15d68bf4..0000000000 --- a/book/src/design/protocol.md +++ /dev/null @@ -1,828 +0,0 @@ -# Protocol Description - -## Preliminaries - -We take $\sec$ as our security parameter, and unless explicitly noted all -algorithms and adversaries are probabilistic (interactive) Turing machines that -run in polynomial time in this security parameter. We use $\negl$ to denote a -function that is negligible in $\sec$. - -### Cryptographic Groups - -We let $\group$ denote a cyclic group of prime order $p$. The identity of a -group is written as $\zero$. We refer to the scalars of elements in $\group$ as -elements in a scalar field $\field$ of size $p$. Group elements are written in -capital letters while scalars are written in lowercase or Greek letters. Vectors -of scalars or group elements are written in boldface, i.e. $\mathbf{a} \in -\field^n$ and $\mathbf{G} \in \group^n$. Group operations are written additively -and the multiplication of a group element $G$ by a scalar $a$ is written $[a] -G$. - -We will often use the notation $\langle \mathbf{a}, \mathbf{b} \rangle$ to -describe the inner product of two like-length vectors of scalars $\mathbf{a}, -\mathbf{b} \in \field^n$. We also use this notation to represent the linear -combination of group elements such as $\langle \mathbf{a}, \mathbf{G} \rangle$ -with $\mathbf{a} \in \field^n, \mathbf{G} \in \group^n$, computed in practice by -a multiscalar multiplication. - -We use $\mathbf{0}^n$ to describe a vector of length $n$ that contains only -zeroes in $\field$. - -> **Discrete Log Relation Problem.** The advantage metric -$$ -\adv^\dlrel_{\group,n}(\a, \sec) = \textnormal{Pr} \left[ \mathsf{G}^\dlrel_{\group,n}(\a, \sec) \right] -$$ -> is defined with respect the following game. -$$ -\begin{array}{ll} -&\underline{\bold{Game} \, \mathsf{G}^\dlrel_{\group,n}(\a, \sec):} \\ -&\mathbf{G} \gets \group^n_\sec \\ -&\mathbf{a} \gets \a(\mathbf{G}) \\ -&\textnormal{Return} \, \left( \langle \mathbf{a}, \mathbf{G} \rangle = \zero \land \mathbf{a} \neq \mathbf{0}^n \right) -\end{array} -$$ - -Given an $n$-length vector $\mathbf{G} \in \group^n$ of group elements, the -_discrete log relation problem_ asks for $\mathbf{g} \in \field^n$ such that -$\mathbf{g} \neq \mathbf{0}^n$ and yet $\innerprod{\mathbf{g}}{\mathbf{G}} = -\zero$, which we refer to as a _non-trivial_ discrete log relation. The hardness -of this problem is tightly implied by the hardness of the discrete log problem -in the group as shown in Lemma 3 of [[JT20]](https://eprint.iacr.org/2020/1213). -Formally, we use the game $\dlgame$ defined above to capture this problem. - -### Interactive Proofs - -_Interactive proofs_ are a triple of algorithms $\ip = (\setup, \prover, -\verifier)$. The algorithm $\setup(1^\sec)$ produces as its output some _public -parameters_ commonly referred to by $\pp$. The prover $\prover$ and verifier -$\verifier$ are interactive machines (with access to $\pp$) and we denote by -$\langle \prover(x), \verifier(y) \rangle$ an algorithm that executes a -two-party protocol between them on inputs $x, y$. The output of this protocol, a -_transcript_ of their interaction, contains all of the messages sent between -$\prover$ and $\verifier$. At the end of the protocol, the verifier outputs a -decision bit. - -### Zero knowledge Arguments of Knowledge - -Proofs of knowledge are interactive proofs where the prover aims to convince the -verifier that they know a witness $w$ such that $(x, w) \in \relation$ for a -statement $x$ and polynomial-time decidable relation $\relation$. We will work -with _arguments_ of knowledge which assume computationally-bounded provers. - -We will analyze arguments of knowledge through the lens of four security -notions. - -* **Completeness:** If the prover possesses a valid witness, can they _always_ - convince the verifier? It is useful to understand this property as it can have - implications for the other security notions. -* **Soundness:** Can a cheating prover falsely convince the verifier of the - correctness of a statement that is not actually correct? We refer to the - probability that a cheating prover can falsely convince the verifier as the - _soundness error_. -* **Knowledge soundness:** When the verifier is convinced the statement is - correct, does the prover actually possess ("know") a valid witness? We refer - to the probability that a cheating prover falsely convinces the verifier of - this knowledge as the _knowledge error_. -* **Zero knowledge:** Does the verifier learn anything besides that which can be - inferred from the correctness of the statement and the prover's knowledge of a - valid witness? - -First, we will visit the simple definition of completeness. - -> **Perfect Completeness.** An interactive argument $(\setup, \prover, \verifier)$ -> has _perfect completeness_ if for all polynomial-time decidable -> relations $\relation$ and for all non-uniform polynomial-time adversaries $\a$ -$$ -Pr \left[ - (x, w) \notin \relation \lor - \langle \prover(\pp, x, w), \verifier(\pp, x) \rangle \, \textnormal{accepts} - \, \middle| \, - \begin{array}{ll} - &\pp \gets \setup(1^\sec) \\ - &(x, w) \gets \a(\pp) - \end{array} -\right] = 1 -$$ - -#### Soundness - -Complicating our analysis is that although our protocol is described as an -interactive argument, it is realized in practice as a _non-interactive argument_ -through the use of the Fiat-Shamir transformation. - -> **Public coin.** We say that an interactive argument is _public coin_ when all -> of the messages sent by the verifier are each sampled with fresh randomness. - -> **Fiat-Shamir transformation.** In this transformation an interactive, public -> coin argument can be made _non-interactive_ in the _random oracle model_ by -> replacing the verifier algorithm with a cryptographically strong hash function -> that produces sufficiently random looking output. - -This transformation means that in the concrete protocol a cheating prover can -easily "rewind" the verifier by forking the transcript and sending new messages -to the verifier. Studying the concrete security of our construction _after_ -applying this transformation is important. Fortunately, we are able to follow a -framework of analysis by Ghoshal and Tessaro -([[GT20](https://eprint.iacr.org/2020/1351)]) that has been applied to -constructions similar to ours. - -We will study our protocol through the notion of _state-restoration soundness_. -In this model the (cheating) prover is allowed to rewind the verifier to any -previous state it was in. The prover wins if they are able to produce an -accepting transcript. - -> **State-Restoration Soundness.** Let $\ip$ be an interactive argument with $r -> = r(\sec)$ verifier challenges and let the $i$th challenge be sampled from -> $\ch_i$. The advantage metric -$$ -\adv^\srs_\ip(\prover, \sec) = \textnormal{Pr} \left[ \srs^\ip_\prover(\sec) \right] -$$ -> of a state restoration prover $\prover$ is defined with respect to the -> following game. -$$ -\begin{array}{ll} -\begin{array}{ll} -&\underline{\bold{Game} \, \srs_\ip^\prover(\sec):} \\ -&\textnormal{win} \gets \tt{false}; \\ -&\tr \gets \epsilon \\ -&\pp \gets \ip.\setup(1^\sec) \\ -&(x, \textsf{st}_\prover) \gets \prover_\sec(\pp) \\ -&\textnormal{Run} \, \prover^{\oracle_\srs}_\sec(\textsf{st}_\prover) \\ -&\textnormal{Return win} -\end{array} & -\begin{array}{ll} -&\underline{\bold{Oracle} \, \oracle_\srs(\tau = (a_1, c_1, ..., a_{i - 1}, c_{i - 1}), a_i):} \\ -& \textnormal{If} \, \tau \in \tr \, \textnormal{then} \\ -& \, \, \textnormal{If} \, i \leq r \, \textnormal{then} \\ -& \, \, \, \, c_i \gets \ch_i; \tr \gets \tr || (\tau, a_i, c_i); \textnormal{Return} \, c_i \\ -& \, \, \textnormal{Else if} \, i = r + 1 \, \textnormal{then} \\ -& \, \, \, \, d \gets \ip.\verifier (\pp, x, (\tau, a_i)); \tr \gets (\tau, a_i) \\ -& \, \, \, \, \textnormal{If} \, d = 1 \, \textnormal{then win} \gets \tt{true} \\ -& \, \, \, \, \textnormal{Return} \, d \\ -&\textnormal{Return} \bottom -\end{array} -\end{array} -$$ - -As shown in [[GT20](https://eprint.iacr.org/2020/1351)] (Theorem 1) state -restoration soundness is tightly related to soundness after applying the -Fiat-Shamir transformation. - -#### Knowledge Soundness - -We will show that our protocol satisfies a strengthened notion of knowledge -soundness known as _witness extended emulation_. Informally, this notion states -that for any successful prover algorithm there exists an efficient _emulator_ -that can extract a witness from it by rewinding it and supplying it with fresh -randomness. - -However, we must slightly adjust our definition of witness extended emulation to -account for the fact that our provers are state restoration provers and can -rewind the verifier. Further, to avoid the need for rewinding the state -restoration prover during witness extraction we study our protocol in the -algebraic group model. - -> **Algebraic Group Model (AGM).** An adversary $\alg{\prover}$ is said to be -> _algebraic_ if whenever it outputs a group element $X$ it also outputs a -> _representation_ $\mathbf{x} \in \field^n$ such that $\langle \mathbf{x}, \mathbf{G} \rangle = X$ where $\mathbf{G} \in \group^n$ is the vector of group -> elements that $\alg{\prover}$ has seen so far. -> Notationally, we write $\rep{X}$ to describe a group element $X$ enhanced -> with this representation. We also write $\repv{X}{G}{i}$ to identify the -> component of the representation of $X$ that corresponds with $\mathbf{G}_i$. In -> other words, -$$ -X = \sum\limits_{i=0}^{n - 1} \left[ \repv{X}{G}{i} \right] \mathbf{G}_i -$$ - -The algebraic group model allows us to perform so-called "online" extraction for -some protocols: the extractor can obtain the witness from the representations -themselves for a single (accepting) transcript. - -> **State Restoration Witness Extended Emulation** Let $\ip$ be an interactive -> argument for relation $\relation$ with $r = r(\sec)$ challenges. We define for -> all non-uniform algebraic provers $\alg{\prover}$, extractors $\extractor$, -> and computationally unbounded distinguishers $\distinguisher$ the advantage -> metric -$$ -\adv^\srwee_{\ip, \relation}(\alg{\prover}, \distinguisher, \extractor, \sec) = \textnormal{Pr} \left[ \weereal^{\prover,\distinguisher}_{\ip,\relation}(\sec) \right] - \textnormal{Pr} \left[ \weeideal^{\extractor,\prover,\distinguisher}_{\ip,\relation}(\sec) \right] -$$ -> is defined with the respect to the following games. -$$ -\begin{array}{ll} -\begin{array}{ll} -&\underline{\bold{Game} \, \weereal_{\ip,\relation}^{\alg{\prover},\distinguisher}(\sec):} \\ -&\tr \gets \epsilon \\ -&\pp \gets \ip.\setup(1^\sec) \\ -&(x, \state{\prover}) \gets \alg{\prover}(\pp) \\ -&\textnormal{Run} \, \alg{\prover}^{\oracle_\real}(\state{\prover}) \\ -&b \gets \distinguisher(\tr) \\ -&\textnormal{Return} \, b = 1 \\ -&\underline{\bold{Game} \, \weeideal_{\ip,\relation}^{\extractor,\alg{\prover},\distinguisher}(\sec):} \\ -&\tr \gets \epsilon \\ -&\pp \gets \ip.\setup(1^\sec) \\ -&(x, \state{\prover}) \gets \alg{\prover}(\pp) \\ -&\state{\extractor} \gets (1^\sec, \pp, x) \\ -&\textnormal{Run} \, \alg{\prover}^{\oracle_\ideal}(\state{\prover}) \\ -&w \gets \extractor(\state{\extractor}, \bottom) \\ -&b \gets \distinguisher(\tr) \\ -&\textnormal{Return} \, (b = 1) \\ -&\, \, \land (\textnormal{Acc}(\tr) \implies (x, w) \in \relation) \\ -\end{array} & -\begin{array}{ll} -&\underline{\bold{Oracle} \, \oracle_\real(\tau = (a_1, c_1, ..., a_{i - 1}, c_{i - 1}), a_i):} \\ -& \textnormal{If} \, \tau \in \tr \, \textnormal{then} \\ -& \, \, \textnormal{If} \, i \leq r \, \textnormal{then} \\ -& \, \, \, \, c_i \gets \ch_i; \tr \gets \tr || (\tau, a_i, c_i); \textnormal{Return} \, c_i \\ -& \, \, \textnormal{Else if} \, i = r + 1 \, \textnormal{then} \\ -& \, \, \, \, d \gets \ip.\verifier (\pp, x, (\tau, a_i)); \tr \gets (\tau, a_i) \\ -& \, \, \, \, \textnormal{If} \, d = 1 \, \textnormal{then win} \gets \tt{true} \\ -& \, \, \, \, \textnormal{Return} \, d \\ -& \, \, \textnormal{Return} \, \bottom \\ -\\ -\\ -&\underline{\bold{Oracle} \, \oracle_\ideal(\tau, a):} \\ -& \textnormal{If} \, \tau \in \tr \, \textnormal{then} \\ -& \, \, (r, \state{\extractor}) \gets \extractor(\state{\extractor}, \left[(\tau, a)\right]) \\ -& \, \, \tr \gets \tr || (\tau, a, r) \\ -& \, \, \textnormal{Return} \, r \\ -&\textnormal{Return} \, \bottom -\end{array} -\end{array} -$$ - -#### Zero Knowledge - -We say that an argument of knowledge is _zero knowledge_ if the verifier also -does not learn anything from their interaction besides that which can be learned -from the existence of a valid $w$. More formally, - -> **Perfect Special Honest-Verifier Zero Knowledge.** A public coin interactive -> argument $(\setup, \prover, \verifier)$ has _perfect special honest-verifier -> zero knowledge_ (PSHVZK) if for all polynomial-time decidable relations -> $\relation$ and for all $(x, w) \in \relation$ and for all non-uniform -> polynomial-time adversaries $\a_1, \a_2$ there exists a probabilistic -> polynomial-time simulator $\sim$ such that -$$ -\begin{array}{rl} -&Pr \left[ \a_1(\sigma, x, \tr) = 1 \, \middle| \, -\begin{array}{ll} -&\pp \gets \setup(1^\lambda); \\ -&(x, w, \rho) \gets \a_2(\pp); \\ -&tr \gets \langle \prover(\pp, x, w), \verifier(\pp, x, \rho) \rangle -\end{array} - \right] \\ - \\ -=&Pr \left[ \a_1(\sigma, x, \tr) = 1 \, \middle| \, -\begin{array}{ll} -&\pp \gets \setup(1^\lambda); \\ -&(x, w, \rho) \gets \a_2(\pp); \\ -&tr \gets \sim(\pp, x, \rho) -\end{array} -\right] -\end{array} -$$ -> where $\rho$ is the internal randomness of the verifier. - -In this (common) definition of zero-knowledge the verifier is expected to act -"honestly" and send challenges that correspond only with their internal -randomness; they cannot adaptively respond to the prover based on the prover's -messages. We use a strengthened form of this definition that forces the simulator -to output a transcript with the same (adversarially provided) challenges that -the verifier algorithm sends to the prover. - -## Protocol - -Let $\omega \in \field$ be a $n = 2^k$ primitive root of unity forming the -domain $D = (\omega^0, \omega^1, ..., \omega^{n - 1})$ with $t(X) = X^n - 1$ the -vanishing polynomial over this domain. Let $n_g, n_a, n_e$ be positive integers with $n_a, n_e \lt n$ and $n_g \geq 4$. -We present an interactive argument $\halo = (\setup, \prover, \verifier)$ for -the relation -$$ -\relation = \left\{ -\begin{array}{ll} -&\left( -\begin{array}{ll} -\left( -g(X, C_0, ..., C_{n_a - 1}, a_0(X), ..., a_{n_a - 1}\left(X, C_0, ..., C_{n_a - 1}, a_0(X), ..., a_{n_a - 2}(X) \right)) -\right); \\ -\left( -a_0(X), a_1(X, C_0, a_0(X)), ..., a_{n_a - 1}\left(X, C_0, ..., C_{n_a - 1}, a_0(X), ..., a_{n_a - 2}(X) \right) -\right) -\end{array} -\right) : \\ -\\ -& g(\omega^i, \cdots) = 0 \, \, \, \, \forall i \in [0, 2^k) -\end{array} -\right\} -$$ -where $a_0, a_1, ..., a_{n_a - 1}$ are (multivariate) polynomials with degree $n - 1$ in $X$ and $g$ has degree $n_g(n - 1)$ at most in any indeterminates $X, C_0, C_1, ...$. - -$\setup(\sec)$ returns $\pp = (\group, \field, \mathbf{G} \in \group^n, U, W \in \group)$. - -For all $i \in [0, n_a)$: -* Let $\mathbf{p_i}$ be the exhaustive set of integers $j$ (modulo $n$) such that $a_i(\omega^j X, \cdots)$ appears as a term in $g(X, \cdots)$. -* Let $\mathbf{q}$ be a list of distinct sets of integers containing $\mathbf{p_i}$ and the set $\mathbf{q_0} = \{0\}$. -* Let $\sigma(i) = \mathbf{q}_j$ when $\mathbf{q}_j = \mathbf{p_i}$. - -Let $n_q \leq n_a$ denote the size of $\mathbf{q}$, and let $n_e$ denote the size of every $\mathbf{p_i}$ without loss of generality. - -In the following protocol, we take it for granted that each polynomial $a_i(X, \cdots)$ is defined such that $n_e + 1$ blinding factors are freshly sampled by the prover and are each present as an evaluation of $a_i(X, \cdots)$ over the domain $D$. In all of the following, the verifier's challenges cannot be zero or an element in $D$, and some additional limitations are placed on specific challenges as well. - -1. $\prover$ and $\verifier$ proceed in the following $n_a$ rounds of interaction, where in round $j$ (starting at $0$) - * $\prover$ sets $a'_j(X) = a_j(X, c_0, c_1, ..., c_{j - 1}, a_0(X, \cdots), ..., a_{j - 1}(X, \cdots, c_{j - 1}))$ - * $\prover$ sends a hiding commitment $A_j = \innerprod{\mathbf{a'}}{\mathbf{G}} + [\cdot] W$ where $\mathbf{a'}$ are the coefficients of the univariate polynomial $a'_j(X)$ and $\cdot$ is some random, independently sampled blinding factor elided for exposition. (This elision notation is used throughout this protocol description to simplify exposition.) - * $\verifier$ responds with a challenge $c_j$. -2. $\prover$ sets $g'(X) = g(X, c_0, c_1, ..., c_{n_a - 1}, \cdots)$. -3. $\prover$ sends a commitment $R = \innerprod{\mathbf{r}}{\mathbf{G}} + [\cdot] W$ where $\mathbf{r} \in \field^n$ are the coefficients of a randomly sampled univariate polynomial $r(X)$ of degree $n - 1$. -4. $\prover$ computes univariate polynomial $h(X) = \frac{g'(X)}{t(X)}$ of degree $n_g(n - 1) - n$. -5. $\prover$ computes at most $n - 1$ degree polynomials $h_0(X), h_1(X), ..., h_{n_g - 2}(X)$ such that $h(X) = \sum\limits_{i=0}^{n_g - 2} X^{ni} h_i(X)$. -6. $\prover$ sends commitments $H_i = \innerprod{\mathbf{h_i}}{\mathbf{G}} + [\cdot] W$ for all $i$ where $\mathbf{h_i}$ denotes the vector of coefficients for $h_i(X)$. -7. $\verifier$ responds with challenge $x$ and computes $H' = \sum\limits_{i=0}^{n_g - 2} [x^{ni}] H_i$. -8. $\prover$ sets $h'(X) = \sum\limits_{i=0}^{n_g - 2} x^{ni} h_i(X)$. -9. $\prover$ sends $r = r(x)$ and for all $i \in [0, n_a)$ sends $\mathbf{a_i}$ such that $(\mathbf{a_i})_j = a'_i(\omega^{(\mathbf{p_i})_j} x)$ for all $j \in [0, n_e - 1]$. -10. For all $i \in [0, n_a)$ $\prover$ and $\verifier$ set $s_i(X)$ to be the lowest degree univariate polynomial defined such that $s_i(\omega^{(\mathbf{p_i})_j} x) = (\mathbf{a_i})_j$ for all $j \in [0, n_e - 1)$. -11. $\verifier$ responds with challenges $x_1, x_2$ and initializes $Q_0, Q_1, ..., Q_{n_q - 1} = \zero$. - * Starting at $i=0$ and ending at $n_a - 1$ $\verifier$ sets $Q_{\sigma(i)} := [x_1] Q_{\sigma(i)} + A_i$. - * $\verifier$ finally sets $Q_0 := [x_1^2] Q_0 + [x_1] H' + R$. -12. $\prover$ initializes $q_0(X), q_1(X), ..., q_{n_q - 1}(X) = 0$. - * Starting at $i=0$ and ending at $n_a - 1$ $\prover$ sets $q_{\sigma(i)} := x_1 q_{\sigma(i)} + a'(X)$. - * $\prover$ finally sets $q_0(X) := x_1^2 q_0(X) + x_1 h'(X) + r(X)$. -13. $\prover$ and $\verifier$ initialize $r_0(X), r_1(X), ..., r_{n_q - 1}(X) = 0$. - * Starting at $i = 0$ and ending at $n_a - 1$ $\prover$ and $\verifier$ set $r_{\sigma(i)}(X) := x_1 r_{\sigma(i)}(X) + s_i(X)$. - * Finally $\prover$ and $\verifier$ set $r_0 := x_1^2 r_0 + x_1 h + r$ and where $h$ is computed by $\verifier$ as $\frac{g'(x)}{t(x)}$ using the values $r, \mathbf{a}$ provided by $\prover$. -14. $\prover$ sends $Q' = \innerprod{\mathbf{q'}}{\mathbf{G}} + [\cdot] W$ where $\mathbf{q'}$ defines the coefficients of the polynomial -$$q'(X) = \sum\limits_{i=0}^{n_q - 1} - -x_2^i - \left( - \frac - {q_i(X) - r_i(X)} - {\prod\limits_{j=0}^{n_e - 1} - \left( - X - \omega^{\left( - \mathbf{q_i} - \right)_j} x - \right) - } - \right) -$$ -15. $\verifier$ responds with challenge $x_3$. -16. $\prover$ sends $\mathbf{u} \in \field^{n_q}$ such that $\mathbf{u}_i = q_i(x_3)$ for all $i \in [0, n_q)$. -17. $\verifier$ responds with challenge $x_4$. -18. $\verifier$ sets $P = Q' + x_4 \sum\limits_{i=0}^{n_q - 1} [x_4^i] Q_i$ and $v = $ -$$ -\sum\limits_{i=0}^{n_q - 1} -\left( -x_2^i - \left( - \frac - { \mathbf{u}_i - r_i(x_3) } - {\prod\limits_{j=0}^{n_e - 1} - \left( - x_3 - \omega^{\left( - \mathbf{q_i} - \right)_j} x - \right) - } - \right) -\right) -+ -x_4 \sum\limits_{i=0}^{n_q - 1} x_4 \mathbf{u}_i -$$ -19. $\prover$ sets $p(X) = q'(X) + [x_4] \sum\limits_{i=0}^{n_q - 1} x_4^i q_i(X)$. -20. $\prover$ samples a random polynomial $s(X)$ of degree $n - 1$ with a root at $x_3$ and sends a commitment $S = \innerprod{\mathbf{s}}{\mathbf{G}} + [\cdot] W$ where $\mathbf{s}$ defines the coefficients of $s(X)$. -21. $\verifier$ responds with challenges $\xi, z$. -22. $\verifier$ sets $P' = P - [v] \mathbf{G}_0 + [\xi] S$. -23. $\prover$ sets $p'(X) = p(X) - p(x_3) + \xi s(X)$ (where $p(x_3)$ should correspond with the verifier's computed value $v$). -24. Initialize $\mathbf{p'}$ as the coefficients of $p'(X)$ and $\mathbf{G'} = \mathbf{G}$ and $\mathbf{b} = (x_3^0, x_3^1, ..., x_3^{n - 1})$. $\prover$ and $\verifier$ will interact in the following $k$ rounds, where in the $j$th round starting in round $j=0$ and ending in round $j=k-1$: - * $\prover$ sends $L_j = \innerprod{\mathbf{p'}_\hi}{\mathbf{G'}_\lo} + [z \innerprod{\mathbf{p'}_\hi}{\mathbf{b}_\lo}] U + [\cdot] W$ and $R_j = \innerprod{\mathbf{p'}_\lo}{\mathbf{G'}_\hi} + [z \innerprod{\mathbf{p'}_\lo}{\mathbf{b}_\hi}] U + [\cdot] W$. - * $\verifier$ responds with challenge $u_j$ chosen such that $1 + u_{k-1-j} x_3^{2^j}$ is nonzero. - * $\prover$ and $\verifier$ set $\mathbf{G'} := \mathbf{G'}_\lo + u_j \mathbf{G'}_\hi$ and $\mathbf{b} := \mathbf{b}_\lo + u_j \mathbf{b}_\hi$. - * $\prover$ sets $\mathbf{p'} := \mathbf{p'}_\lo + u_j^{-1} \mathbf{p'}_\hi$. -25. $\prover$ sends $c = \mathbf{p'}_0$ and synthetic blinding factor $f$ computed from the elided blinding factors. -26. $\verifier$ accepts only if $\sum_{j=0}^{k - 1} [u_j^{-1}] L_j + P' + \sum_{j=0}^{k - 1} [u_j] R_j = [c] \mathbf{G'}_0 + [c \mathbf{b}_0 z] U + [f] W$. - -### Zero-knowledge and Completeness - -We claim that this protocol is _perfectly complete_. This can be verified by -inspection of the protocol; given a valid witness $a_i(X, \cdots) \forall i$ the -prover succeeds in convincing the verifier with probability $1$. - -We claim that this protocol is _perfect special honest-verifier zero knowledge_. -We do this by showing that a simulator $\sim$ exists which can produce an -accepting transcript that is equally distributed with a valid prover's -interaction with a verifier with the same public coins. The simulator will act -as an honest prover would, with the following exceptions: - -1. In step $1$ of the protocol $\sim$ chooses random degree $n - 1$ polynomials (in $X$) $a_i(X, \cdots) \forall i$. -2. In step $5$ of the protocol $\sim$ chooses a random $n - 1$ degree polynomials $h_0(X), h_1(X), ..., h_{n_g - 2}(X)$. -3. In step $14$ of the protocol $\sim$ chooses a random $n - 1$ degree polynomial $q'(X)$. -4. In step $20$ of the protocol $\sim$ uses its foreknowledge of the verifier's choice of $\xi$ to produce a degree $n - 1$ polynomial $s(X)$ conditioned only such that $p(X) - v + \xi s(X)$ has a root at $x_3$. - -First, let us consider why this simulator always succeeds in producing an -_accepting_ transcript. $\sim$ lacks a valid witness and simply commits to -random polynomials whenever knowledge of a valid witness would be required by -the honest prover. The verifier places no conditions on the scalar values in the -transcript. $\sim$ must only guarantee that the check in step $26$ of the -protocol succeeds. It does so by using its knowledge of the challenge $\xi$ to -produce a polynomial which interferes with $p'(X)$ to ensure it has a root at -$x_3$. The transcript will thus always be accepting due to perfect completeness. - -In order to see why $\sim$ produces transcripts distributed identically to the -honest prover, we will look at each piece of the transcript and compare the -distributions. First, note that $\sim$ (just as the honest prover) uses a -freshly random blinding factor for every group element in the transcript, and so -we need only consider the _scalars_ in the transcript. $\sim$ acts just as the -prover does except in the mentioned cases so we will analyze each case: - -1. $\sim$ and an honest prover reveal $n_e$ openings of each polynomial $a_i(X, \cdots)$, and at most one additional opening of each $a_i(X, \cdots)$ in step $16$. However, the honest prover blinds their polynomials $a_i(X, \cdots)$ (in $X$) with $n_e + 1$ random evaluations over the domain $D$. Thus, the openings of $a_i(X, \cdots)$ at the challenge $x$ (which is prohibited from being $0$ or in the domain $D$ by the protocol) are distributed identically between $\sim$ and an honest prover. -2. Neither $\sim$ nor the honest prover reveal $h(x)$ as it is computed by the verifier. However, the honest prover may reveal $h'(x_3)$ --- which has a non-trivial relationship with $h(X)$ --- were it not for the fact that the honest prover also commits to a random degree $n - 1$ polynomial $r(X)$ in step $3$, producing a commitment $R$ and ensuring that in step $12$ when the prover sets $q_0(X) := x_1^2 q_0(X) + x_1 h'(X) + r(X)$ the distribution of $q_0(x_3)$ is uniformly random. Thus, $h'(x_3)$ is never revealed by the honest prover nor by $\sim$. -3. The expected value of $q'(x_3)$ is computed by the verifier (in step $18$) and so the simulator's actual choice of $q'(X)$ is irrelevant. -4. $p(X) - v + \xi s(X)$ is conditioned on having a root at $x_3$, but otherwise no conditions are placed on $s(X)$ and so the distribution of the degree $n - 1$ polynomial $p(X) - v + \xi s(X)$ is uniformly random whether or not $s(X)$ has a root at $x_3$. Thus, the distribution of $c$ produced in step $25$ is identical between $\sim$ and an honest prover. The synthetic blinding factor $f$ also revealed in step $25$ is a trivial function of the prover's other blinding factors and so is distributed identically between $\sim$ and an honest prover. - -Notes: - -1. In an earlier version of our protocol, the prover would open each individual commitment $H_0, H_1, ...$ at $x$ as part of the multipoint opening argument, and the verifier would confirm that a linear combination of these openings (with powers of $x^n$) agreed to the expected value of $h(x)$. This was done because it's more efficient in recursive proofs. However, it was unclear to us what the expected distribution of the openings of these commitments $H_0, H_1, ...$ was and so proving that the argument was zero-knowledge is difficult. Instead, we changed the argument so that the _verifier_ computes a linear combination of the commitments and that linear combination is opened at $x$. This avoided leaking $h_i(x)$. -2. As mentioned, in step $3$ the prover commits to a random polynomial as a way of ensuring that $h'(x_3)$ is not revealed in the multiopen argument. This is done because it's unclear what the distribution of $h'(x_3)$ would be. -3. Technically it's also possible for us to prove zero-knowledge with a simulator that uses its foreknowledge of the challenge $x$ to commit to an $h(X)$ which agrees at $x$ to the value it will be expected to. This would obviate the need for the random polynomial $s(X)$ in the protocol. This may make the analysis of zero-knowledge for the remainder of the protocol a little bit tricky though, so we didn't go this route. -4. Group element blinding factors are _technically_ not necessary after step $23$ in which the polynomial is completely randomized. However, it's simpler in practice for us to ensure that every group element in the protocol is randomly blinded to make edge cases involving the point at infinity harder. -5. It is crucial that the verifier cannot challenge the prover to open polynomials at points in $D$ as otherwise the transcript of an honest prover will be forced to contain what could be portions of the prover's witness. We therefore restrict the space of challenges to include all elements of the field except $D$ and, for simplicity, we also prohibit the challenge of $0$. - -## Witness-extended Emulation - -Let $\protocol = \protocol[\group]$ be the interactive argument described above for relation $\relation$ and some group $\group$ with scalar field $\field$. We can always construct an extractor $\extractor$ such that for any non-uniform algebraic prover $\alg{\prover}$ making at most $q$ queries to its oracle, there exists a non-uniform adversary $\dlreladv$ with the property that for any computationally unbounded distinguisher $\distinguisher$ - -$$ -\adv^\srwee_{\protocol, \relation}(\alg{\prover}, \distinguisher, \extractor, \sec) \leq q\epsilon + \adv^\dlrel_{\group,n+2}(\dlreladv, \sec) -$$ - -where $\epsilon \leq \frac{n_g \cdot (n - 1)}{|\ch|}$. - -_Proof._ We will prove this by invoking Theorem 1 of [[GT20]](https://eprint.iacr.org/2020/1351). First, we note that the challenge space for all rounds is the same, i.e. $\forall i \ \ch = \ch_i$. Theorem 1 requires us to define: - -- $\badch(\tr') \in \ch$ for all partial transcripts $\tr' = (\pp, x, [a_0], c_0, \ldots, [a_i])$ such that $|\badch(\tr')| / |\ch| \leq \epsilon$. -- an extractor function $e$ that takes as input an accepting extended transcript $\tr$ and either returns a valid witness or fails. -- a function $\pfail(\protocol, \alg{\prover}, e, \relation)$ returning a probability. - -We say that an accepting extended transcript $\tr$ contains "bad challenges" if and only if there exists a partial extended transcript $\tr'$, a challenge $c_i \in \badch(\tr')$, and some sequence of prover messages and challenges $([a_{i+1}], c_{i+1}, \ldots [a_j])$ such that $\tr = \tr' \,||\, (c_i, [a_{i+1}], c_{i+1}, \ldots [a_j])$. - -Theorem 1 requires that $e$, when given an accepting extended transcript $\tr$ that does not contain "bad challenges", returns a valid witness for that transcript except with probability bounded above by $\pfail(\protocol, \alg{\prover}, e, \relation)$. - -Our strategy is as follows: we will define $e$, establish an upper bound on $\pfail$ with respect to an adversary $\dlreladv$ that plays the $\dlrel_{\group,n+2}$ game, substitute these into Theorem 1, and then walk through the protocol to determine the upper bound of the size of $\badch(\tr')$. The adversary $\dlreladv$ plays the $\dlrel_{\group,n+2}$ game as follows: given the inputs $U, W \in \mathbb{G}, \mathbf{G} \in \mathbb{G}^n$, the adversary $\dlreladv$ simulates the game $\srwee_{\protocol, \relation}$ to $\alg{\prover}$ using the inputs from the $\dlrel_{\group,n+2}$ game as public parameters. If $\alg{\prover}$ manages to produce an accepting extended transcript $\tr$, $\dlreladv$ invokes a function $h$ on $\tr$ and returns its output. We shall define $h$ in such a way that for an accepting extended transcript $\tr$ that does not contain "bad challenges", $e(\tr)$ _always_ returns a valid witness whenever $h(\tr)$ does _not_ return a non-trivial discrete log relation. This means that the probability $\pfail(\protocol, \alg{\prover}, e, \relation)$ is no greater than $\adv^\dlrel_{\group,n+2}(\dlreladv, \sec)$, establishing our claim. - -#### Helpful substitutions - -We will perform some substitutions to aid in exposition. First, let us define the polynomial - -$$ -\kappa(X) = \prod_{j=0}^{k - 1} (1 + u_{k - 1 - j} X^{2^j}) -$$ - -so that we can write $\mathbf{b}_0 = \kappa(x_3)$. The coefficient vector $\mathbf{s}$ of $\kappa(X)$ is defined such that - -$$\mathbf{s}_i = \prod\limits_{j=0}^{k-1} u_{k - 1 - j}^{f(i, j)}$$ - -where $f(i, j)$ returns $1$ when the $j$th bit of $i$ is set, and $0$ otherwise. We can also write $\mathbf{G'}_0 = \innerprod{\mathbf{s}}{\mathbf{G}}$. - -### Description of function $h$ - -Recall that an accepting transcript $\tr$ is such that - -$$ -\sum_{i=0}^{k - 1} [u_j^{-1}] \rep{L_j} + \rep{P'} + \sum_{i=0}^{k - 1} [u_j] \rep{R_j} = [c] \mathbf{G'}_0 + [c z \mathbf{b}_0] U + [f] W -$$ - -By inspection of the representations of group elements with respect to $\mathbf{G}, U, W$ (recall that $\alg{\prover}$ is algebraic and so $\dlreladv$ has them), we obtain the $n$ equalities - -$$ -\sum_{i=0}^{k - 1} u_j^{-1} \repv{L_j}{G}{i} + \repv{P'}{G}{i} + \sum_{i=0}^{k - 1} u_j \repv{R_j}{G}{i} = c \mathbf{s}_i \forall i \in [0, n) -$$ - -and the equalities - -$$ -\sum_{i=0}^{k - 1} u_j^{-1} \repr{L_j}{U} + \repr{P'}{U} + \sum_{i=0}^{k - 1} u_j \repr{R_j}{U} = c z \kappa(x_3) -$$ - -$$ -\sum_{i=0}^{k - 1} u_j^{-1} \repr{L_j}{W} + \repr{P'}{W} + \sum_{i=0}^{k - 1} u_j \repr{R_j}{W} = f -$$ - -We define the linear-time function $h$ that returns the representation of - -$$ -\begin{array}{rll} -\sum\limits_{i=0}^{n - 1} &\left[ \sum_{i=0}^{k - 1} u_j^{-1} \repv{L_j}{G}{i} + \repv{P'}{G}{i} + \sum_{i=0}^{k - 1} u_j \repv{R_j}{G}{i} - c \mathbf{s}_i \right] & \mathbf{G}_i \\[1ex] -+ &\left[ \sum_{i=0}^{k - 1} u_j^{-1} \repr{L_j}{U} + \repr{P'}{U} + \sum_{i=0}^{k - 1} u_j \repr{R_j}{U} - c z \kappa(x_3) \right] & U \\[1ex] -+ &\left[ \sum_{i=0}^{k - 1} u_j^{-1} \repr{L_j}{W} + \repr{P'}{W} + \sum_{i=0}^{k - 1} u_j \repr{R_j}{W} - f \right] & W -\end{array} -$$ - -which is always a discrete log relation. If any of the equalities above are not satisfied, then this discrete log relation is non-trivial. This is the function invoked by $\dlreladv$. - -#### The extractor function $e$ - -The extractor function $e$ simply returns $a_i(X)$ from the representation $\rep{A_i}$ for $i \in [0, n_a)$. Due to the restrictions we will place on the space of bad challenges in each round, we are guaranteed to obtain polynomials such that $g(X, C_0, C_1, \cdots, a_0(X), a_1(X), \cdots)$ vanishes over $D$ whenever the discrete log relation returned by the adversary's function $h$ is trivial. This trivially gives us that the extractor function $e$ succeeds with probability bounded above by $\pfail$ as required. - -#### Defining $\badch(\tr')$ - -Recall from before that the following $n$ equalities hold: - -$$ -\sum_{i=0}^{k - 1} u_j^{-1} \repv{L_j}{G}{i} + \repv{P'}{G}{i} + \sum_{i=0}^{k - 1} u_j \repv{R_j}{G}{i} = c \mathbf{s}_i \forall i \in [0, n) -$$ - -as well as the equality - -$$ -\sum_{i=0}^{k - 1} u_j^{-1} \repr{L_j}{U} + \repr{P'}{U} + \sum_{i=0}^{k - 1} u_j \repr{R_j}{U} = c z \kappa(x_3) -$$ - -For convenience let us introduce the following notation - -$$ -\begin{array}{ll} -\mv{G}{i}{m} &= \sum_{i=0}^{m - 1} u_j^{-1} \repv{L_j}{G}{i} + \repv{P'}{G}{i} + \sum_{i=0}^{m - 1} u_j \repv{R_j}{G}{i} \\[1ex] -\m{U}{m} &= \sum_{i=0}^{m - 1} u_j^{-1} \repr{L_j}{U} + \repr{P'}{U} + \sum_{i=0}^{m - 1} u_j \repr{R_j}{U} -\end{array} -$$ - -so that we can rewrite the above (after expanding for $\kappa(x_3)$) as - -$$ -\mv{G}{i}{k} = c \mathbf{s}_i \forall i \in [0, n) -$$ - -$$ -\m{U}{k} = c z \prod_{j=0}^{k - 1} (1 + u_{k - 1 - j} x_3^{2^j}) -$$ - -We can combine these equations by multiplying both sides of each instance of the first equation by $\mathbf{s}_i^{-1}$ (because $\mathbf{s}_i$ is never zero) and substituting for $c$ in the second equation, yielding the following $n$ equalities: - -$$ -\m{U}{k} = \mv{G}{i}{k} \cdot \mathbf{s}_i^{-1} z \prod_{j=0}^{k - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \forall i \in [0, n) -$$ - -> **Lemma 1.** If $\m{U}{k} = \mv{G}{i}{k} \cdot \mathbf{s}_i^{-1} z \prod_{j=0}^{k - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \forall i \in [0, n)$ then it follows that $\repr{P'}{U} = z \sum\limits_{i=0}^{2^k - 1} x_3^i \repv{P'}{G}{i}$ for all transcripts that do not contain bad challenges. -> -> _Proof._ It will be useful to introduce yet another abstraction defined starting with -> $$ -\z{k}{m}{i} = \mv{G}{i}{m} -$$ -> and then recursively defined for all integers $r$ such that $0 \lt r \leq k$ -> $$ -\z{k - r}{m}{i} = \z{k - r + 1}{m}{i} + x_3^{2^{k - r}} \z{k - r + 1}{m}{i + 2^{k - r}} -$$ -> This allows us to rewrite our above equalities as -> $$ -\m{U}{k} = \z{k}{k}{i} \cdot \mathbf{s}_i^{-1} z \prod_{j=0}^{k - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \forall i \in [0, n) -$$ -> -> We will now show that for all integers $r$ such that $0 \lt r \leq k$ that whenever the following holds for $r$ -> $$ -\m{U}{r} = \z{r}{r}{i} \cdot \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \forall i \in [0, 2^r) -$$ -> that the same _also_ holds for -> $$ -\m{U}{r - 1} = \z{r - 1}{r - 1}{i} \cdot \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 2 - j} x_3^{2^j}) \forall i \in [0, 2^{r-1}) -$$ -> -> For all integers $r$ such that $0 \lt r \leq k$ we have that $\mathbf{s}_{i + 2^{r - 1}} = u_{r - 1} \mathbf{s}_i \forall i \in [0, 2^{r - 1})$ by the definition of $\mathbf{s}$. This gives us $\mathbf{s}_{i+2^{r - 1}}^{-1} = \mathbf{s}_i^{-1} u_{r - 1}^{-1} \forall i \in [0, 2^{r - 1})$ as no value in $\mathbf{s}$ nor any challenge $u_r$ are zeroes. We can use this to relate one half of the equalities with the other half as so: -> $$ -\begin{array}{rl} -\m{U}{r} &= \z{r}{r}{i} \cdot \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \\ -&= \z{r}{r}{i + 2^{r - 1}} \cdot \mathbf{s}_i^{-1} u_{r - 1}^{-1} z \prod_{j=0}^{r - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \\ -&\forall i \in [0, 2^{r - 1}) -\end{array} -$$ -> -> Notice that $\z{r}{r}{i}$ can be rewritten as $u_{r - 1}^{-1} \repv{L_{r - 1}}{G}{i} + \z{r}{r - 1}{i} + u_{r - 1} \repv{R_{r - 1}}{G}{i}$ for all $i \in [0, 2^{r})$. Thus we can rewrite the above as -> -> $$ -\begin{array}{rl} -\m{U}{r} &= \left( u_{r - 1}^{-1} \repv{L_{r - 1}}{G}{i} + \z{r}{r - 1}{i} + u_{r - 1} \repv{R_{r - 1}}{G}{i} \right) \\ -&\cdot \; \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \\ -&= \left( u_{r - 1}^{-1} \repv{L_{r - 1}}{G}{i + 2^{r - 1}} + \z{r}{r - 1}{i + 2^{r - 1}} + u_{r - 1} \repv{R_{r - 1}}{G}{i + 2^{r - 1}} \right) \\ -&\cdot \; \mathbf{s}_i^{-1} u_{r - 1}^{-1} z \prod_{j=0}^{r - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \\ -&\forall i \in [0, 2^{r - 1}) -\end{array} -$$ -> -> Now let us rewrite these equalities substituting $u_{r - 1}$ with formal indeterminate $X$. -> -> $$ -\begin{array}{rl} -& X^{-1} \repr{L_{r - 1}}{U} + \m{U}{r - 1} + X \repr{R_{r - 1}}{U} \\ -&= \left( X^{-1} \repv{L_{r - 1}}{G}{i} + \z{r}{r - 1}{i} + X \repv{R_{r - 1}}{G}{i} \right) \\ -&\cdot \; \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) (1 + x_3^{2^{r - 1}} X) \\ -&= \left( X^{-1} \repv{L_{r - 1}}{G}{i + 2^{r - 1}} + \z{r}{r - 1}{i + 2^{r - 1}} + X \repv{R_{r - 1}}{G}{i + 2^{r - 1}} \right) \\ -&\cdot \; \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) (X^{-1} + x_3^{2^{r - 1}}) \\ -&\forall i \in [0, 2^{r - 1}) -\end{array} -$$ -> -> Now let us rescale everything by $X^2$ to remove negative exponents. -> -> $$ -\begin{array}{rl} -& X \repr{L_{r - 1}}{U} + X^2 \m{U}{r - 1} + X^3 \repr{R_{r - 1}}{U} \\ -&= \left( X^{-1} \repv{L_{r - 1}}{G}{i} + \z{r}{r - 1}{i} + X \repv{R_{r - 1}}{G}{i} \right) \\ -&\cdot \; \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) (X^2 + x_3^{2^{r - 1}} X^3) \\ -&= \left( X^{-1} \repv{L_{r - 1}}{G}{i + 2^{r - 1}} + \z{r}{r - 1}{i + 2^{r - 1}} + X \repv{R_{r - 1}}{G}{i + 2^{r - 1}} \right) \\ -&\cdot \; \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) (X + x_3^{2^{r - 1}} X^2) \\ -&\forall i \in [0, 2^{r - 1}) -\end{array} -$$ -> -> This gives us $2^{r - 1}$ triples of maximal degree-$4$ polynomials in $X$ that agree at $u_{r - 1}$ despite having coefficients determined prior to the choice of $u_{r - 1}$. The probability that two of these polynomials would agree at $u_{r - 1}$ and yet be distinct would be $\frac{4}{|\ch|}$ by the Schwartz-Zippel lemma and so by the union bound the probability that the three of these polynomials agree and yet any of them is distinct from another is $\frac{8}{|\ch|}$. By the union bound again the probability that any of the $2^{r - 1}$ triples have multiple distinct polynomials is $\frac{2^{r - 1}\cdot8}{|\ch|}$. By restricting the challenge space for $u_{r - 1}$ accordingly we obtain $|\badch(\trprefix{\tr'}{u_r})|/|\ch| \leq \frac{2^{r - 1}\cdot8}{|\ch|}$ for integers $0 \lt r \leq k$ and thus $|\badch(\trprefix{\tr'}{u_k})|/|\ch| \leq \frac{4n}{|\ch|} \leq \epsilon$. -> -> We can now conclude an equality of polynomials, and thus of coefficients. Consider the coefficients of the constant terms first, which gives us the $2^{r - 1}$ equalities -> $$ -0 = 0 = \mathbf{s}_i^{-1} z \left( \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) \right) \cdot \repv{L_{r - 1}}{G}{i + 2^{r - 1}} \forall i \in [0, 2^{r - 1}) -$$ -> -> No value of $\mathbf{s}$ is zero, $z$ is never chosen to be $0$ and each $u_j$ is chosen so that $1 + u_{k - 1 - j} x_3^{2^j}$ is nonzero, so we can then conclude -> $$ -0 = \repv{L_{r - 1}}{G}{i + 2^{r - 1}} \forall i \in [0, 2^{r - 1}) -$$ -> -> An identical process can be followed with respect to the coefficients of the $X^4$ term in the equalities to establish $0 = \repv{R_{r - 1}}{G}{i} \forall i \in [0, 2^{r - 1})$ contingent on $x_3$ being nonzero, which it always is. Substituting these in our equalities yields us something simpler -> -> $$ -\begin{array}{rl} -& X \repr{L_{r - 1}}{U} + X^2 \m{U}{r - 1} + X^3 \repr{R_{r - 1}}{U} \\ -&= \left( X^{-1} \repv{L_{r - 1}}{G}{i} + \z{r}{r - 1}{i} \right) \\ -&\cdot \; \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) (X^2 + x_3^{2^{r - 1}} X^3) \\ -&= \left( \z{r}{r - 1}{i + 2^{r - 1}} + X \repv{R_{r - 1}}{G}{i + 2^{r - 1}} \right) \\ -&\cdot \; \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) (X + x_3^{2^{r - 1}} X^2) \\ -&\forall i \in [0, 2^{r - 1}) -\end{array} -$$ -> -> Now we will consider the coefficients in $X$, which yield the equalities -> -> $$ -\begin{array}{rl} -\repr{L_{r - 1}}{U} &= \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) \cdot \repv{L_{r - 1}}{G}{i} \\ -&= \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) \cdot \z{r}{r - 1}{i + 2^{r - 1}} \\ -&\forall i \in [0, 2^{r - 1}) -\end{array} -$$ -> -> which for similar reasoning as before yields the equalities -> $$ -\repv{L_{r - 1}}{G}{i} = \z{r}{r - 1}{i + 2^{r - 1}} \forall i \in [0, 2^{r - 1}) -$$ -> -> Finally we will consider the coefficients in $X^2$ which yield the equalities -> -> $$ -\begin{array}{rl} -\m{U}{r - 1} &= \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) \cdot \left( \z{r}{r - 1}{i} + \repv{L_{r - 1}}{G}{i} x_3^{2^{r - 1}} \right) \\ -&\forall i \in [0, 2^{r - 1}) -\end{array} -$$ -> -> which by substitution gives us $\forall i \in [0, 2^{r - 1})$ -> $$ -\m{U}{r - 1} = \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) \cdot \left( \z{r}{r - 1}{i} + \z{r}{r - 1}{i + 2^{r - 1}} x_3^{2^{r - 1}} \right) -$$ -> -> Notice that by the definition of $\z{r - 1}{m}{i}$ we can rewrite this as -> -> $$ -\m{U}{r - 1} = \z{r - 1}{r - 1}{i} \cdot \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) \forall i \in [0, 2^{r - 1}) -$$ -> -> which is precisely in the form we set out to demonstrate. -> -> We now proceed by induction from the case $r = k$ (which we know holds) to reach $r = 0$, which gives us -$$ -\m{U}{0} = \z{0}{0}{0} \cdot \mathbf{s}_0^{-1} z -$$ -> -> and because $\m{U}{0} = \repr{P'}{U}$ and $\z{0}{0}{0} = \sum_{i=0}^{2^k - 1} x_3^i \repv{P'}{G}{i}$, we obtain $\repr{P'}{U} = z \sum_{i=0}^{2^k - 1} x_3^i \repv{P'}{G}{i}$, which completes the proof. - -Having established that $\repr{P'}{U} = z \sum_{i=0}^{2^k - 1} x_3^i \repv{P'}{G}{i}$, and given that $x_3$ and $\repv{P'}{G}{i}$ are fixed in advance of the choice of $z$, we have that at most one value of $z \in \ch$ (which is nonzero) exists such that $\repr{P'}{U} = z \sum_{i=0}^{2^k - 1} x_3^i \repv{P'}{G}{i}$ and yet $\repr{P'}{U} \neq 0$. By restricting $|\badch(\trprefix{\tr'}{z})|/|\ch| \leq \frac{1}{|\ch|} \leq \epsilon$ accordingly we obtain $\repr{P'}{U} = 0$ and therefore that the polynomial defined by $\repr{P'}{\mathbf{G}}$ has a root at $x_3$. - -By construction $P' = P - [v] \mathbf{G}_0 + [\xi] S$, giving us that the polynomial defined by $\repr{P + [\xi] S}{\mathbf{G}}$ evaluates to $v$ at the point $x_3$. We have that $v, P, S$ are fixed prior to the choice of $\xi$, and so either the polynomial defined by $\repr{S}{\mathbf{G}}$ has a root at $x_3$ (which implies the polynomial defined by $\repr{P}{\mathbf{G}}$ evaluates to $v$ at the point $x_3$) or else $\xi$ is the single solution in $\ch$ for which $\repr{P + [\xi] S}{\mathbf{G}}$ evaluates to $v$ at the point $x_3$ while $\repr{P}{\mathbf{G}}$ itself does not. We avoid the latter case by restricting $|\badch(\trprefix{\tr'}{\xi})|/|\ch| \leq \frac{1}{|\ch|} \leq \epsilon$ accordingly and can thus conclude that the polynomial defined by $\repr{P}{\mathbf{G}}$ evaluates to $v$ at $x_3$. - -The remaining work deals strictly with the representations of group elements sent previously by the prover and their relationship with $P$ as well as the challenges chosen in each round of the protocol. We will simplify things first by using $p(X)$ to represent the polynomial defined by $\repr{P}{\mathbf{G}}$, as it is the case that this $p(X)$ corresponds exactly with the like-named polynomial in the protocol itself. We will make similar substitutions for the other group elements (and their corresponding polynomials) to aid in exposition, as the remainder of this proof is mainly tedious application of the Schwartz-Zippel lemma to upper bound the bad challenge space size for each of the remaining challenges in the protocol. - -Recall that $P = Q' + x_4 \sum\limits_{i=0}^{n_q - 1} [x_4^i] Q_i$, and so by substitution we have $p(X) = q'(X) + x_4 \sum\limits_{i=0}^{n_q - 1} x_4^i q_i(X)$. Recall also that - -$$ -v = \sum\limits_{i=0}^{n_q - 1} -\left( -x_2^i - \left( - \frac - { \mathbf{u}_i - r_i(x_3) } - {\prod\limits_{j=0}^{n_e - 1} - \left( - x_3 - \omega^{\left( - \mathbf{q_i} - \right)_j} x - \right) - } - \right) -\right) -+ -x_4 \sum\limits_{i=0}^{n_q - 1} x_4 \mathbf{u}_i -$$ - -We have already established that $p(x_3) = v$. Notice that the coefficients in the above expressions for $v$ and $P$ are fixed prior to the choice of $x_4 \in \ch$. By the Schwartz-Zippel lemma we have that only at most $n_q + 1$ possible choices of $x_4$ exist such that these expressions are satisfied and yet $q_i(x_3) \neq \mathbf{u}_i$ for any $i$ or - -$$ -q'(x_3) \neq \sum\limits_{i=0}^{n_q - 1} -\left( -x_2^i - \left( - \frac - { \mathbf{u}_i - r_i(x_3) } - {\prod\limits_{j=0}^{n_e - 1} - \left( - x_3 - \omega^{\left( - \mathbf{q_i} - \right)_j} x - \right) - } - \right) -\right) -$$ - -By restricting $|\badch(\trprefix{\tr'}{x_4})|/|\ch| \leq \frac{n_q + 1}{|\ch|} \leq \epsilon$ we can conclude that all of the aforementioned inequalities are untrue. Now we can substitute $\mathbf{u}_i$ with $q_i(x_3)$ for all $i$ to obtain - -$$ -q'(x_3) = \sum\limits_{i=0}^{n_q - 1} -\left( -x_2^i - \left( - \frac - { q_i(x_3) - r_i(x_3) } - {\prod\limits_{j=0}^{n_e - 1} - \left( - x_3 - \omega^{\left( - \mathbf{q_i} - \right)_j} x - \right) - } - \right) -\right) -$$ - -Suppose that $q'(X)$ (which is the polynomial defined by $\repr{Q'}{\mathbf{G}}$, and is of degree at most $n - 1$) does _not_ take the form - -$$\sum\limits_{i=0}^{n_q - 1} - -x_2^i - \left( - \frac - {q_i(X) - r_i(X)} - {\prod\limits_{j=0}^{n_e - 1} - \left( - X - \omega^{\left( - \mathbf{q_i} - \right)_j} x - \right) - } - \right) -$$ - -and yet $q'(X)$ agrees with this expression at $x_3$ as we've established above. By the Schwartz-Zippel lemma this can only happen for at most $n - 1$ choices of $x_3 \in \ch$ and so by restricting $|\badch(\trprefix{\tr'}{x_3})|/|\ch| \leq \frac{n - 1}{|\ch|} \leq \epsilon$ we obtain that - -$$q'(X) = \sum\limits_{i=0}^{n_q - 1} - -x_2^i - \left( - \frac - {q_i(X) - r_i(X)} - {\prod\limits_{j=0}^{n_e - 1} - \left( - X - \omega^{\left( - \mathbf{q_i} - \right)_j} x - \right) - } - \right) -$$ - -Next we will extract the coefficients of this polynomial in $x_2$ (which are themselves polynomials in formal indeterminate $X$) by again applying the Schwartz-Zippel lemma with respect to $x_2$; again, this leads to the restriction $|\badch(\trprefix{\tr'}{x_2})|/|\ch| \leq \frac{n_q}{|\ch|} \leq \epsilon$ and we obtain the following polynomials of degree at most $n - 1$ for all $i \in [0, n_q - 1)$ - -$$ -\frac - {q_i(X) - r_i(X)} - {\prod\limits_{j=0}^{n_e - 1} - \left( - X - \omega^{\left( - \mathbf{q_i} - \right)_j} x - \right) - } -$$ - -Having established that these are each non-rational polynomials of degree at most $n - 1$ we can then say (by the factor theorem) that for each $i \in [0, n_q - 1]$ and $j \in [0, n_e - 1]$ we have that $q_i(X) - r_i(X)$ has a root at $\omega^{\left(\mathbf{q_i}\right)_j} x$. Note that we can interpret each $q_i(X)$ as the restriction of a _bivariate_ polynomial at the point $x_1$ whose degree with respect to $x_1$ is at most $n_a + 1$ and whose coefficients consist of various polynomials $a'_i(X)$ (from the representation $\repr{A'_i}{\mathbf{G}}$) as well as $h'(X)$ (from the representation $\repr{H'_i}{\mathbf{G}}$) and $r(X)$ (from the representation $\repr{R}{\mathbf{G}}$). By similarly applying the Schwartz-Zippel lemma and restricting the challenge space with $|\badch(\trprefix{\tr'}{x_1})|/|\ch| \leq \frac{n_a + 1}{|\ch|} \leq \epsilon$ we obtain (by construction of each $q'_i(X)$ and $r_i(X)$ in steps 12 and 13 of the protocol) that the prover's claimed value of $r$ in step 9 is equal to $r(x)$; that the value $h$ computed by the verifier in step 13 is equal to $h'(x)$; and that for all $i \in [0, n_q - 1]$ the prover's claimed values $(\mathbf{a_i})_j = a'_i(\omega^{(\mathbf{p_i})_j} x)$ for all $j \in [0, n_e - 1]$. - -By construction of $h'(X)$ (from the representation $\repr{H'}{\mathbf{G}}$) in step 7 we know that $h'(x) = h(x)$ where by $h(X)$ we refer to the polynomial of degree at most $(n_g - 1) \cdot (n - 1)$ whose coefficients correspond to the concatenated representations of each $\repr{H_i}{\mathbf{G}}$. As before, suppose that $h(X)$ does _not_ take the form $g'(X) / t(X)$. Then because $h(X)$ is determined prior to the choice of $x$ then by the Schwartz-Zippel lemma we know that it would only agree with $g'(X) / t(X)$ at $(n_g - 1) \cdot (n - 1)$ points at most if the polynomials were not equal. By restricting again $|\badch(\trprefix{\tr'}{x})|/|\ch| \leq \frac{(n_g - 1) \cdot (n - 1)}{|\ch|} \leq \epsilon$ we obtain $h(X) = g'(X) / t(X)$ and because $h(X)$ is a non-rational polynomial by the factor theorem we obtain that $g'(X)$ vanishes over the domain $D$. - -We now have that $g'(X)$ vanishes over $D$ but wish to show that $g(X, C_0, C_1, \cdots)$ vanishes over $D$ at all points to complete the proof. This just involves a sequence of applying the same technique to each of the challenges; since the polynomial $g(\cdots)$ has degree at most $n_g \cdot (n - 1)$ in any indeterminate by definition, and because each polynomial $a_i(X, C_0, C_1, ..., C_{i - 1}, \cdots)$ is determined prior to the choice of concrete challenge $c_i$ by similarly bounding $|\badch(\trprefix{\tr'}{c_i})|/|\ch| \leq \frac{n_g \cdot (n - 1)}{|\ch|} \leq \epsilon$ we ensure that $g(X, C_0, C_1, \cdots)$ vanishes over $D$, completing the proof. diff --git a/book/src/design/proving-system.md b/book/src/design/proving-system.md deleted file mode 100644 index a0ed0f1fb7..0000000000 --- a/book/src/design/proving-system.md +++ /dev/null @@ -1,74 +0,0 @@ -# Proving system - -The Halo 2 proving system can be broken down into five stages: - -1. Commit to polynomials encoding the main components of the circuit: - - Cell assignments. - - Permuted values and products for each lookup argument. - - Equality constraint permutations. -2. Construct the vanishing argument to constrain all circuit relations to zero: - - Standard and custom gates. - - Lookup argument rules. - - Equality constraint permutation rules. -3. Evaluate the above polynomials at all necessary points: - - All relative rotations used by custom gates across all columns. - - Vanishing argument pieces. -4. Construct the multipoint opening argument to check that all evaluations are consistent - with their respective commitments. -5. Run the inner product argument to create a polynomial commitment opening proof for the - multipoint opening argument polynomial. - -These stages are presented in turn across this section of the book. - -## Example - -To aid our explanations, we will at times refer to the following example constraint -system: - -- Four advice columns $a, b, c, d$. -- One fixed column $f$. -- Three custom gates: - - $a \cdot b \cdot c_{-1} - d = 0$ - - $f_{-1} \cdot c = 0$ - - $f \cdot d \cdot a = 0$ - -## tl;dr - -The table below provides a (probably too) succinct description of the Halo 2 protocol. -This description will likely be replaced by the Halo 2 paper and security proof, but for -now serves as a summary of the following sub-sections. - -| Prover | | Verifier | -| --------------------------------------------------------------------------- | ------- | ---------------------------------- | -| | $\larr$ | $t(X) = (X^n - 1)$ | -| | $\larr$ | $F = [F_0, F_1, \dots, F_{m - 1}]$ | -| $\mathbf{A} = [A_0, A_1, \dots, A_{m - 1}]$ | $\rarr$ | | -| | $\larr$ | $\theta$ | -| $\mathbf{L} = [(A'_0, S'_0), \dots, (A'_{m - 1}, S'_{m - 1})]$ | $\rarr$ | | -| | $\larr$ | $\beta, \gamma$ | -| $\mathbf{Z_P} = [Z_{P,0}, Z_{P,1}, \ldots]$ | $\rarr$ | | -| $\mathbf{Z_L} = [Z_{L,0}, Z_{L,1}, \ldots]$ | $\rarr$ | | -| | $\larr$ | $y$ | -| $h(X) = \frac{\text{gate}_0(X) + \dots + y^i \cdot \text{gate}_i(X)}{t(X)}$ | | | -| $h(X) = h_0(X) + \dots + X^{n(d-1)} h_{d-1}(X)$ | | | -| $\mathbf{H} = [H_0, H_1, \dots, H_{d-1}]$ | $\rarr$ | | -| | $\larr$ | $x$ | -| $evals = [A_0(x), \dots, H_{d - 1}(x)]$ | $\rarr$ | | -| | | Checks $h(x)$ | -| | $\larr$ | $x_1, x_2$ | -| Constructs $h'(X)$ multipoint opening poly | | | -| $U = \text{Commit}(h'(X))$ | $\rarr$ | | -| | $\larr$ | $x_3$ | -| $\mathbf{q}_\text{evals} = [Q_0(x_3), Q_1(x_3), \dots]$ | $\rarr$ | | -| $u_\text{eval} = U(x_3)$ | $\rarr$ | | -| | $\larr$ | $x_4$ | - -Then the prover and verifier: - -- Construct $\text{finalPoly}(X)$ as a linear combination of $\mathbf{Q}$ and $U$ using - powers of $x_4$; -- Construct $\text{finalPolyEval}$ as the equivalent linear combination of - $\mathbf{q}_\text{evals}$ and $u_\text{eval}$; and -- Perform $\text{InnerProduct}(\text{finalPoly}(X), x_3, \text{finalPolyEval}).$ - -> TODO: Write up protocol components that provide zero-knowledge. diff --git a/book/src/design/proving-system/circuit-commitments.md b/book/src/design/proving-system/circuit-commitments.md deleted file mode 100644 index 7352788929..0000000000 --- a/book/src/design/proving-system/circuit-commitments.md +++ /dev/null @@ -1,114 +0,0 @@ -# Circuit commitments - -## Committing to the circuit assignments - -At the start of proof creation, the prover has a table of cell assignments that it claims -satisfy the constraint system. The table has $n = 2^k$ rows, and is broken into advice, -instance, and fixed columns. We define $F_{i,j}$ as the assignment in the $j$th row of -the $i$th fixed column. Without loss of generality, we'll similarly define $A_{i,j}$ to -represent the advice and instance assignments. - -> We separate fixed columns here because they are provided by the verifier, whereas the -> advice and instance columns are provided by the prover. In practice, the commitments to -> instance and fixed columns are computed by both the prover and verifier, and only the -> advice commitments are stored in the proof. - -To commit to these assignments, we construct Lagrange polynomials of degree $n - 1$ for -each column, over an evaluation domain of size $n$ (where $\omega$ is the $n$th primitive -root of unity): - -- $a_i(X)$ interpolates such that $a_i(\omega^j) = A_{i,j}$. -- $f_i(X)$ interpolates such that $f_i(\omega^j) = F_{i,j}$. - -We then create a blinding commitment to the polynomial for each column: - -$$\mathbf{A} = [\text{Commit}(a_0(X)), \dots, \text{Commit}(a_i(X))]$$ -$$\mathbf{F} = [\text{Commit}(f_0(X)), \dots, \text{Commit}(f_i(X))]$$ - -$\mathbf{F}$ is constructed as part of key generation, using a blinding factor of $1$. -$\mathbf{A}$ is constructed by the prover and sent to the verifier. - -## Committing to the lookup permutations - -The verifier starts by sampling $\theta$, which is used to keep individual columns within -lookups independent. Then, the prover commits to the permutations for each lookup as -follows: - -- Given a lookup with input column polynomials $[A_0(X), \dots, A_{m-1}(X)]$ and table - column polynomials $[S_0(X), \dots, S_{m-1}(X)]$, the prover constructs two compressed - polynomials - - $$A_\text{compressed}(X) = \theta^{m-1} A_0(X) + \theta^{m-2} A_1(X) + \dots + \theta A_{m-2}(X) + A_{m-1}(X)$$ - $$S_\text{compressed}(X) = \theta^{m-1} S_0(X) + \theta^{m-2} S_1(X) + \dots + \theta S_{m-2}(X) + S_{m-1}(X)$$ - -- The prover then permutes $A_\text{compressed}(X)$ and $S_\text{compressed}(X)$ according - to the [rules of the lookup argument](lookup.md), obtaining $A'(X)$ and $S'(X)$. - -The prover creates blinding commitments for all of the lookups - -$$\mathbf{L} = \left[ (\text{Commit}(A'(X))), \text{Commit}(S'(X))), \dots \right]$$ - -and sends them to the verifier. - -After the verifier receives $\mathbf{A}$, $\mathbf{F}$, and $\mathbf{L}$, it samples -challenges $\beta$ and $\gamma$ that will be used in the permutation argument and the -remainder of the lookup argument below. (These challenges can be reused because the -arguments are independent.) - -## Committing to the equality constraint permutation - -Let $c$ be the number of columns that are enabled for equality constraints. - -Let $m$ be the maximum number of columns that can accommodated by a -[column set](permutation.md#spanning-a-large-number-of-columns) without exceeding -the PLONK configuration's maximum constraint degree. - -Let $u$ be the number of “usable” rows as defined in the -[Permutation argument](permutation.md#zero-knowledge-adjustment) section. - -Let $b = \mathsf{ceiling}(c/m).$ - -The prover constructs a vector $\mathbf{P}$ of length $bu$ such that for each -column set $0 \leq a < b$ and each row $0 \leq j < u,$ - -$$ -\mathbf{P}_{au + j} = \prod\limits_{i=am}^{\min(c, (a+1)m)-1} \frac{v_i(\omega^j) + \beta \cdot \delta^i \cdot \omega^j + \gamma}{v_i(\omega^j) + \beta \cdot s_i(\omega^j) + \gamma}. -$$ - -The prover then computes a running product of $\mathbf{P}$, starting at $1$, -and a vector of polynomials $Z_{P,0..b-1}$ that each have a Lagrange basis -representation corresponding to a $u$-sized slice of this running product, as -described in the [Permutation argument](permutation.md#argument-specification) -section. - -The prover creates blinding commitments to each $Z_{P,a}$ polynomial: - -$$\mathbf{Z_P} = \left[\text{Commit}(Z_{P,0}(X)), \dots, \text{Commit}(Z_{P,b-1}(X))\right]$$ - -and sends them to the verifier. - -## Committing to the lookup permutation product columns - -In addition to committing to the individual permuted lookups, for each lookup, -the prover needs to commit to the permutation product column: - -- The prover constructs a vector $P$: - -$$ -P_j = \frac{(A_\text{compressed}(\omega^j) + \beta)(S_\text{compressed}(\omega^j) + \gamma)}{(A'(\omega^j) + \beta)(S'(\omega^j) + \gamma)} -$$ - -- The prover constructs a polynomial $Z_L$ which has a Lagrange basis representation - corresponding to a running product of $P$, starting at $Z_L(1) = 1$. - -$\beta$ and $\gamma$ are used to combine the permutation arguments for $A'(X)$ and $S'(X)$ -while keeping them independent. The important thing here is that the verifier samples -$\beta$ and $\gamma$ after the prover has created $\mathbf{A}$, $\mathbf{F}$, and -$\mathbf{L}$ (and thus committed to all the cell values used in lookup columns, as well -as $A'(X)$ and $S'(X)$ for each lookup). - -As before, the prover creates blinding commitments to each $Z_L$ polynomial: - -$$\mathbf{Z_L} = \left[\text{Commit}(Z_L(X)), \dots \right]$$ - -and sends them to the verifier. diff --git a/book/src/design/proving-system/comparison.md b/book/src/design/proving-system/comparison.md deleted file mode 100644 index 87496a82b0..0000000000 --- a/book/src/design/proving-system/comparison.md +++ /dev/null @@ -1,55 +0,0 @@ -# Comparison to other work - -## BCMS20 Appendix A.2 - -Appendix A.2 of [BCMS20] describes a polynomial commitment scheme that is similar to the -one described in [BGH19] (BCMS20 being a generalization of the original Halo paper). Halo -2 builds on both of these works, and thus itself uses a polynomial commitment scheme that -is very similar to the one in BCMS20. - -[BGH19]: https://eprint.iacr.org/2019/1021 -[BCMS20]: https://eprint.iacr.org/2020/499 - -The following table provides a mapping between the variable names in BCMS20, and the -equivalent objects in Halo 2 (which builds on the nomenclature from the Halo paper): - -| BCMS20 | Halo 2 | -| :------------: | :-----------------: | -| $S$ | $H$ | -| $H$ | $U$ | -| $C$ | `msm` or $P$ | -| $\alpha$ | $\iota$ | -| $\xi_0$ | $z$ | -| $\xi_i$ | `challenge_i` | -| $H'$ | $[z] U$ | -| $\bar{p}$ | `s_poly` | -| $\bar{\omega}$ | `s_poly_blind` | -| $\bar{C}$ | `s_poly_commitment` | -| $h(X)$ | $g(X)$ | -| $U$ | $G$ | -| $\omega'$ | `blind` / $\xi$ | -| $\mathbf{c}$ | $\mathbf{a}$ | -| $c$ | $a = \mathbf{a}_0$ | -| $v'$ | $ab$ | - -Halo 2's polynomial commitment scheme differs from Appendix A.2 of BCMS20 in two ways: - -1. Step 8 of the $\text{Open}$ algorithm computes a "non-hiding" commitment $C'$ prior to - the inner product argument, which opens to the same value as $C$ but is a commitment to - a randomly-drawn polynomial. The remainder of the protocol involves no blinding. By - contrast, in Halo 2 we blind every single commitment that we make (even for instance - and fixed polynomials, though using a blinding factor of 1 for the fixed polynomials); - this makes the protocol simpler to reason about. As a consequence of this, the verifier - needs to handle the cumulative blinding factor at the end of the protocol, and so there - is no need to derive an equivalent to $C'$ at the start of the protocol. - - - $C'$ is also an input to the random oracle for $\xi_0$; in Halo 2 we utilize a - transcript that has already committed to the equivalent components of $C'$ prior to - sampling $z$. - -2. The $\text{PC}_\text{DL}.\text{SuccinctCheck}$ subroutine (Figure 2 of BCMS20) computes - the initial group element $C_0$ by adding $[v] H' = [v \xi_0] H$, which requires two - scalar multiplications. Instead, we subtract $[v] G_0$ from the original commitment $P$, - so that we're effectively opening the polynomial at the point to the value zero. The - computation $[v] G_0$ is more efficient in the context of recursion because $G_0$ is a - fixed base (so we can use lookup tables). diff --git a/book/src/design/proving-system/inner-product.md b/book/src/design/proving-system/inner-product.md deleted file mode 100644 index 7ccc9b2fbf..0000000000 --- a/book/src/design/proving-system/inner-product.md +++ /dev/null @@ -1,11 +0,0 @@ -# Inner product argument - -Halo 2 uses a polynomial commitment scheme for which we can create polynomial commitment -opening proofs, based around the Inner Product Argument. - -> TODO: Explain Halo 2's variant of the IPA. -> -> It is very similar to $\text{PC}_\text{DL}.\text{Open}$ from Appendix A.2 of [BCMS20]. -> See [this comparison](comparison.md#bcms20-appendix-a2) for details. -> -> [BCMS20]: https://eprint.iacr.org/2020/499 diff --git a/book/src/design/proving-system/lookup.md b/book/src/design/proving-system/lookup.md deleted file mode 100644 index 3bc7a4a99d..0000000000 --- a/book/src/design/proving-system/lookup.md +++ /dev/null @@ -1,177 +0,0 @@ -# Lookup argument - -Halo 2 uses the following lookup technique, which allows for lookups in arbitrary sets, and -is arguably simpler than Plookup. - -## Note on Language - -In addition to the [general notes on language](../../design.md#note-on-language): - -- We call the $Z(X)$ polynomial (the grand product argument polynomial for the permutation - argument) the "permutation product" column. - -## Technique Description - -For ease of explanation, we'll first describe a simplified version of the argument that -ignores zero knowledge. - -We express lookups in terms of a "subset argument" over a table with $2^k$ rows (numbered -from 0), and columns $A$ and $S.$ - -The goal of the subset argument is to enforce that every cell in $A$ is equal to _some_ -cell in $S.$ This means that more than one cell in $A$ can be equal to the _same_ cell in -$S,$ and some cells in $S$ don't need to be equal to any of the cells in $A.$ - -- $S$ might be fixed, but it doesn't need to be. That is, we can support looking up values - in either fixed or variable tables (where the latter includes advice columns). -- $A$ and $S$ can contain duplicates. If the sets represented by $A$ and/or $S$ are not - naturally of size $2^k,$ we extend $S$ with duplicates and $A$ with dummy values known - to be in $S.$ - - Alternatively we could add a "lookup selector" that controls which elements of the $A$ - column participate in lookups. This would modify the occurrence of $A(X)$ in the - permutation rule below to replace $A$ with, say, $S_0$ if a lookup is not selected. - -Let $\ell_i$ be the Lagrange basis polynomial that evaluates to $1$ at row $i,$ and $0$ -otherwise. - -We start by allowing the prover to supply permutation columns of $A$ and $S.$ Let's call -these $A'$ and $S',$ respectively. We can enforce that they are permutations using a -permutation argument with product column $Z$ with the rules: - -$$ -Z(\omega X) \cdot (A'(X) + \beta) \cdot (S'(X) + \gamma) - Z(X) \cdot (A(X) + \beta) \cdot (S(X) + \gamma) = 0 -$$$$ -\ell_0(X) \cdot (1 - Z(X)) = 0 -$$ - -i.e. provided that division by zero does not occur, we have for all $i \in [0, 2^k)$: - -$$ -Z_{i+1} = Z_i \cdot \frac{(A_i + \beta) \cdot (S_i + \gamma)}{(A'_i + \beta) \cdot (S'_i + \gamma)} -$$$$ -Z_{2^k} = Z_0 = 1. -$$ - -This is a version of the permutation argument which allows $A'$ and $S'$ to be -permutations of $A$ and $S,$ respectively, but doesn't specify the exact permutations. -$\beta$ and $\gamma$ are separate challenges so that we can combine these two permutation -arguments into one without worrying that they might interfere with each other. - -The goal of these permutations is to allow $A'$ and $S'$ to be arranged by the prover in a -particular way: - -1. All the cells of column $A'$ are arranged so that like-valued cells are vertically - adjacent to each other. This could be done by some kind of sorting algorithm, but all - that matters is that like-valued cells are on consecutive rows in column $A',$ and that - $A'$ is a permutation of $A.$ -2. The first row in a sequence of like values in $A'$ is the row that has the - corresponding value in $S'.$ Apart from this constraint, $S'$ is any arbitrary - permutation of $S.$ - -Now, we'll enforce that either $A'_i = S'_i$ or that $A'_i = A'_{i-1},$ using the rule - -$$ -(A'(X) - S'(X)) \cdot (A'(X) - A'(\omega^{-1} X)) = 0 -$$ - -In addition, we enforce $A'_0 = S'_0$ using the rule - -$$ -\ell_0(X) \cdot (A'(X) - S'(X)) = 0 -$$ - -(The $A'(X) - A'(\omega^{-1} X)$ term of the first rule here has no effect at row $0,$ even -though $\omega^{-1} X$ "wraps", because of the second rule.) - -Together these constraints effectively force every element in $A'$ (and thus $A$) to equal -at least one element in $S'$ (and thus $S$). Proof: by induction on prefixes of the rows. - -## Zero-knowledge adjustment - -In order to achieve zero knowledge for the PLONK-based proof system, we will need the last -$t$ rows of each column to be filled with random values. This requires an adjustment to the -lookup argument, because these random values would not satisfy the constraints described -above. - -We limit the number of usable rows to $u = 2^k - t - 1.$ We add two selectors: - -* $q_\mathit{blind}$ is set to $1$ on the last $t$ rows, and $0$ elsewhere; -* $q_\mathit{last}$ is set to $1$ only on row $u,$ and $0$ elsewhere (i.e. it is set on the - row in between the usable rows and the blinding rows). - -We enable the constraints from above only for the usable rows: - -$$ -\big(1 - (q_\mathit{last}(X) + q_\mathit{blind}(X))\big) \cdot \big(Z(\omega X) \cdot (A'(X) + \beta) \cdot (S'(X) + \gamma) - Z(X) \cdot (A(X) + \beta) \cdot (S(X) + \gamma)\big) = 0 -$$$$ -\big(1 - (q_\mathit{last}(X) + q_\mathit{blind}(X))\big) \cdot (A'(X) - S'(X)) \cdot (A'(X) - A'(\omega^{-1} X)) = 0 -$$ - -The rules that are enabled on row $0$ remain the same: - -$$ -\ell_0(X) \cdot (A'(X) - S'(X)) = 0 -$$$$ -\ell_0(X) \cdot (1 - Z(X)) = 0 -$$ - -Since we can no longer rely on the wraparound to ensure that the product $Z$ becomes $1$ -again at $\omega^{2^k},$ we would instead need to constrain $Z(\omega^u)$ to $1.$ However, -there is a potential difficulty: if any of the values $A_i + \beta$ or $S_i + \gamma$ are -zero for $i \in [0, u),$ then it might not be possible to satisfy the permutation argument. -This occurs with negligible probability over choices of $\beta$ and $\gamma,$ but is an -obstacle to achieving *perfect* zero knowledge (because an adversary can rule out witnesses -that would cause this situation), as well as perfect completeness. - -To ensure both perfect completeness and perfect zero knowledge, we allow $Z(\omega^u)$ -to be either zero or one: - -$$ -q_\mathit{last}(X) \cdot (Z(X)^2 - Z(X)) = 0 -$$ - -Now if $A_i + \beta$ or $S_i + \gamma$ are zero for some $i,$ we can set $Z_j = 0$ for -$i < j \leq u,$ satisfying the constraint system. - -Note that the challenges $\beta$ and $\gamma$ are chosen after committing to $A$ and $S$ -(and to $A'$ and $S'$), so the prover cannot force the case where some $A_i + \beta$ or -$S_i + \gamma$ is zero to occur. Since this case occurs with negligible probability, -soundness is not affected. - -## Cost - -* There is the original column $A$ and the fixed column $S.$ -* There is a permutation product column $Z.$ -* There are the two permutations $A'$ and $S'.$ -* The gates are all of low degree. - -## Generalizations - -Halo 2's lookup argument implementation generalizes the above technique in the following -ways: - -- $A$ and $S$ can be extended to multiple columns, combined using a random challenge. $A'$ - and $S'$ stay as single columns. - - The commitments to the columns of $S$ can be precomputed, then combined cheaply once - the challenge is known by taking advantage of the homomorphic property of Pedersen - commitments. - - The columns of $A$ can be given as arbitrary polynomial expressions using relative - references. These will be substituted into the product column constraint, subject to - the maximum degree bound. This potentially saves one or more advice columns. -- Then, a lookup argument for an arbitrary-width relation can be implemented in terms of a - subset argument, i.e. to constrain $\mathcal{R}(x, y, ...)$ in each row, consider - $\mathcal{R}$ as a set of tuples $S$ (using the method of the previous point), and check - that $(x, y, ...) \in \mathcal{R}.$ - - In the case where $\mathcal{R}$ represents a function, this implicitly also checks - that the inputs are in the domain. This is typically what we want, and often saves an - additional range check. -- We can support multiple tables in the same circuit, by combining them into a single - table that includes a tag column to identify the original table. - - The tag column could be merged with the "lookup selector" mentioned earlier, if this - were implemented. - -These generalizations are similar to those in sections 4 and 5 of the -[Plookup paper](https://eprint.iacr.org/2020/315.pdf). That is, the differences from -Plookup are in the subset argument. This argument can then be used in all the same ways; -for instance, the optimized range check technique in section 5 of the Plookup paper can -also be used with this subset argument. diff --git a/book/src/design/proving-system/multipoint-opening.md b/book/src/design/proving-system/multipoint-opening.md deleted file mode 100644 index 98982928c0..0000000000 --- a/book/src/design/proving-system/multipoint-opening.md +++ /dev/null @@ -1,94 +0,0 @@ -# Multipoint opening argument - -Consider the commitments $A, B, C, D$ to polynomials $a(X), b(X), c(X), d(X)$. -Let's say that $a$ and $b$ were queried at the point $x$, while $c$ and $d$ -were queried at both points $x$ and $\omega x$. (Here, $\omega$ is the primitive -root of unity in the multiplicative subgroup over which we constructed the -polynomials). - -To open these commitments, we could create a polynomial $Q$ for each point that we queried -at (corresponding to each relative rotation used in the circuit). But this would not be -efficient in the circuit; for example, $c(X)$ would appear in multiple polynomials. - -Instead, we can group the commitments by the sets of points at which they were queried: -$$ -\begin{array}{cccc} -&\{x\}& &\{x, \omega x\}& \\ - &A& &C& \\ - &B& &D& -\end{array} -$$ - -For each of these groups, we combine them into a polynomial set, and create a single $Q$ -for that set, which we open at each rotation. - -## Optimization steps - -The multipoint opening optimization takes as input: - -- A random $x$ sampled by the verifier, at which we evaluate $a(X), b(X), c(X), d(X)$. -- Evaluations of each polynomial at each point of interest, provided by the prover: - $a(x), b(x), c(x), d(x), c(\omega x), d(\omega x)$ - -These are the outputs of the [vanishing argument](vanishing.md#evaluating-the-polynomials). - -The multipoint opening optimization proceeds as such: - -1. Sample random $x_1$, to keep $a, b, c, d$ linearly independent. -2. Accumulate polynomials and their corresponding evaluations according - to the point set at which they were queried: - `q_polys`: - $$ - \begin{array}{rccl} - q_1(X) &=& a(X) &+& x_1 b(X) \\ - q_2(X) &=& c(X) &+& x_1 d(X) - \end{array} - $$ - `q_eval_sets`: - ```math - [ - [a(x) + x_1 b(x)], - [ - c(x) + x_1 d(x), - c(\omega x) + x_1 d(\omega x) - ] - ] - ``` - NB: `q_eval_sets` is a vector of sets of evaluations, where the outer vector - corresponds to the point sets, which in this example are $\{x\}$ and $\{x, \omega x\}$, - and the inner vector corresponds to the points in each set. -3. Interpolate each set of values in `q_eval_sets`: - `r_polys`: - $$ - \begin{array}{cccc} - r_1(X) s.t.&&& \\ - &r_1(x) &=& a(x) + x_1 b(x) \\ - r_2(X) s.t.&&& \\ - &r_2(x) &=& c(x) + x_1 d(x) \\ - &r_2(\omega x) &=& c(\omega x) + x_1 d(\omega x) \\ - \end{array} - $$ -4. Construct `f_polys` which check the correctness of `q_polys`: - `f_polys` - $$ - \begin{array}{rcl} - f_1(X) &=& \frac{ q_1(X) - r_1(X)}{X - x} \\ - f_2(X) &=& \frac{ q_2(X) - r_2(X)}{(X - x)(X - \omega x)} \\ - \end{array} - $$ - - If $q_1(x) = r_1(x)$, then $f_1(X)$ should be a polynomial. - If $q_2(x) = r_2(x)$ and $q_2(\omega x) = r_2(\omega x)$ - then $f_2(X)$ should be a polynomial. -5. Sample random $x_2$ to keep the `f_polys` linearly independent. -6. Construct $f(X) = f_1(X) + x_2 f_2(X)$. -7. Sample random $x_3$, at which we evaluate $f(X)$: - $$ - \begin{array}{rcccl} - f(x_3) &=& f_1(x_3) &+& x_2 f_2(x_3) \\ - &=& \frac{q_1(x_3) - r_1(x_3)}{x_3 - x} &+& x_2\frac{q_2(x_3) - r_2(x_3)}{(x_3 - x)(x_3 - \omega x)} - \end{array} - $$ -8. Sample random $x_4$ to keep $f(X)$ and `q_polys` linearly independent. -9. Construct `final_poly`, $$final\_poly(X) = f(X) + x_4 q_1(X) + x_4^2 q_2(X),$$ - which is the polynomial we commit to in the inner product argument. diff --git a/book/src/design/proving-system/permutation-diagram.png b/book/src/design/proving-system/permutation-diagram.png deleted file mode 100644 index a0f683a90d6097b2affb47a0002d53fca0a7e85a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82914 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 diff --git a/book/src/design/proving-system/permutation-diagram.svg b/book/src/design/proving-system/permutation-diagram.svg deleted file mode 100644 index 2ae7f31461..0000000000 --- a/book/src/design/proving-system/permutation-diagram.svg +++ /dev/null @@ -1,1743 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - 7 - - - - - - 7 - - 7 - - 7 - 7 - = - - - - B - 7 - - - C - 7 - - - D - 7 - - - - A - 7 - - - 7 - - 3 - - - - B - 7 - - - D - 7 - - - - A - 7 - - - - 3 - C - - - - - - - - - - - - - σ - - - - - - - - - - - 7 - C - B - A - D - - - - - - - - - - - - σ - - - - - - - - - - - A - B - C - D - - diff --git a/book/src/design/proving-system/permutation.md b/book/src/design/proving-system/permutation.md deleted file mode 100644 index f71e4edf75..0000000000 --- a/book/src/design/proving-system/permutation.md +++ /dev/null @@ -1,272 +0,0 @@ -# Permutation argument - -Given that gates in halo2 circuits operate "locally" (on cells in the current row or -defined relative rows), it is common to need to copy a value from some arbitrary cell into -the current row for use in a gate. This is performed with an equality constraint, which -enforces that the source and destination cells contain the same value. - -We implement these equality constraints by constructing a permutation that represents the -constraints, and then using a permutation argument within the proof to enforce them. - -## Notation - -A permutation is a one-to-one and onto mapping of a set onto itself. A permutation can be -factored uniquely into a composition of cycles (up to ordering of cycles, and rotation of -each cycle). - -We sometimes use [cycle notation](https://en.wikipedia.org/wiki/Permutation#Cycle_notation) -to write permutations. Let $(a\ b\ c)$ denote a cycle where $a$ maps to $b,$ $b$ maps to -$c,$ and $c$ maps to $a$ (with the obvious generalization to arbitrary-sized cycles). -Writing two or more cycles next to each other denotes a composition of the corresponding -permutations. For example, $(a\ b)\ (c\ d)$ denotes the permutation that maps $a$ to $b,$ -$b$ to $a,$ $c$ to $d,$ and $d$ to $c.$ - -## Constructing the permutation - -### Goal - -We want to construct a permutation in which each subset of variables that are in a -equality-constraint set form a cycle. For example, suppose that we have a circuit that -defines the following equality constraints: - -- $a \equiv b$ -- $a \equiv c$ -- $d \equiv e$ - -From this we have the equality-constraint sets $\{a, b, c\}$ and $\{d, e\}.$ We want to -construct the permutation: - -$$(a\ b\ c)\ (d\ e)$$ - -which defines the mapping of $[a, b, c, d, e]$ to $[b, c, a, e, d].$ - -### Algorithm - -We need to keep track of the set of cycles, which is a -[set of disjoint sets](https://en.wikipedia.org/wiki/Disjoint-set_data_structure). -Efficient data structures for this problem are known; for the sake of simplicity we choose -one that is not asymptotically optimal but is easy to implement. - -We represent the current state as: - -- an array $\mathsf{mapping}$ for the permutation itself; -- an auxiliary array $\mathsf{aux}$ that keeps track of a distinguished element of each - cycle; -- another array $\mathsf{sizes}$ that keeps track of the size of each cycle. - -We have the invariant that for each element $x$ in a given cycle $C,$ $\mathsf{aux}(x)$ -points to the same element $c \in C.$ This allows us to quickly decide whether two given -elements $x$ and $y$ are in the same cycle, by checking whether -$\mathsf{aux}(x) = \mathsf{aux}(y).$ Also, $\mathsf{sizes}(\mathsf{aux}(x))$ gives the -size of the cycle containing $x.$ (This is guaranteed only for -$\mathsf{sizes}(\mathsf{aux}(x)),$ not for $\mathsf{sizes}(x).$) - -The algorithm starts with a representation of the identity permutation: -for all $x,$ we set $\mathsf{mapping}(x) = x,$ $\mathsf{aux}(x) = x,$ and -$\mathsf{sizes}(x) = 1.$ - -To add an equality constraint $\mathit{left} \equiv \mathit{right}$: - -1. Check whether $\mathit{left}$ and $\mathit{right}$ are already in the same cycle, i.e. - whether $\mathsf{aux}(\mathit{left}) = \mathsf{aux}(\mathit{right}).$ If so, there is - nothing to do. -2. Otherwise, $\mathit{left}$ and $\mathit{right}$ belong to different cycles. Make - $\mathit{left}$ the larger cycle and $\mathit{right}$ the smaller one, by swapping them - iff $\mathsf{sizes}(\mathsf{aux}(\mathit{left})) < \mathsf{sizes}(\mathsf{aux}(\mathit{right})).$ -3. Set $\mathsf{sizes}(\mathsf{aux}(\mathit{left})) := - \mathsf{sizes}(\mathsf{aux}(\mathit{left})) + \mathsf{sizes}(\mathsf{aux}(\mathit{right})).$ -4. Following the mapping around the right (smaller) cycle, for each element $x$ set - $\mathsf{aux}(x) := \mathsf{aux}(\mathit{left}).$ -5. Splice the smaller cycle into the larger one by swapping $\mathsf{mapping}(\mathit{left})$ - with $\mathsf{mapping}(\mathit{right}).$ - -For example, given two disjoint cycles $(A\ B\ C\ D)$ and $(E\ F\ G\ H)$: - -```plaintext -A +---> B -^ + -| | -+ v -D <---+ C E +---> F - ^ + - | | - + v - H <---+ G -``` - -After adding constraint $B \equiv E$ the above algorithm produces the cycle: - -```plaintext -A +---> B +-------------+ -^ | -| | -+ v -D <---+ C <---+ E F - ^ + - | | - + v - H <---+ G -``` - -### Broken alternatives - -If we did not check whether $\mathit{left}$ and $\mathit{right}$ were already in the same -cycle, then we could end up undoing an equality constraint. For example, if we have the -following constraints: - -- $a \equiv b$ -- $b \equiv c$ -- $c \equiv d$ -- $b \equiv d$ - -and we tried to implement adding an equality constraint just using step 5 of the above -algorithm, then we would end up constructing the cycle $(a\ b)\ (c\ d),$ rather than the -correct $(a\ b\ c\ d).$ - -## Argument specification - -We need to check a permutation of cells in $m$ columns, represented in Lagrange basis by -polynomials $v_0, \ldots, v_{m-1}.$ - -We will label *each cell* in those $m$ columns with a unique element of $\mathbb{F}^\times.$ - -Suppose that we have a permutation on these labels, -$$ -\sigma(\mathsf{column}: i, \mathsf{row}: j) = (\mathsf{column}: i', \mathsf{row}: j'). -$$ -in which the cycles correspond to equality-constraint sets. - -> If we consider the set of pairs $\{(\mathit{label}, \mathit{value})\}$, then the values within -> each cycle are equal if and only if permuting the label in each pair by $\sigma$ yields the -> same set: -> -> ![An example for a cycle (A B C D). The set before permuting the labels is {(A, 7), (B, 7), (C, 7), (D, 7)}, and the set after is {(D, 7), (A, 7), (B, 7), (C, 7)} which is the same. If one of the 7s is replaced by 3, then the set after permuting labels is not the same.](./permutation-diagram.png) -> -> Since the labels are distinct, set equality is the same as multiset equality, which we can -> check using a product argument. - -Let $\omega$ be a $2^k$ root of unity and let $\delta$ be a $T$ root of unity, where -${T \cdot 2^S + 1 = p}$ with $T$ odd and ${k \leq S.}$ -We will use $\delta^i \cdot \omega^j \in \mathbb{F}^\times$ as the label for the -cell in the $j$th row of the $i$th column of the permutation argument. - -We represent $\sigma$ by a vector of $m$ polynomials $s_i(X)$ such that -$s_i(\omega^j) = \delta^{i'} \cdot \omega^{j'}.$ - -Notice that the identity permutation can be represented by the vector of $m$ polynomials -$\mathsf{ID}_i(\omega^j)$ such that $\mathsf{ID}_i(\omega^j) = \delta^i \cdot \omega^j.$ - -We will use a challenge $\beta$ to compress each ${(\mathit{label}, \mathit{value})}$ pair -to $\mathit{value} + \beta \cdot \mathit{label}.$ Just as in the product argument we used -for [lookups](lookup.md), we also use a challenge $\gamma$ to randomize each term of the -product. - -Now given our permutation represented by $s_0, \ldots, s_{m-1}$ over columns represented by -$v_0, \ldots, v_{m-1},$ we want to ensure that: -$$ -\prod\limits_{i=0}^{m-1} \prod\limits_{j=0}^{n-1} \left(\frac{v_i(\omega^j) + \beta \cdot \delta^i \cdot \omega^j + \gamma}{v_i(\omega^j) + \beta \cdot s_i(\omega^j) + \gamma}\right) = 1 -$$ - -> Here ${v_i(\omega^j) + \beta \cdot \delta^i \cdot \omega^j}$ represents the unpermuted -> $(\mathit{label}, value)$ pair, and ${v_i(\omega^j) + \beta \cdot s_i(\omega^j)}$ -> represents the permuted $(\sigma(\mathit{label}), value)$ pair. - -Let $Z_P$ be such that $Z_P(\omega^0) = Z_P(\omega^n) = 1$ and for $0 \leq j < n$: -$$\begin{array}{rl} -Z_P(\omega^{j+1}) &= \prod\limits_{h=0}^{j} \prod\limits_{i=0}^{m-1} \frac{v_i(\omega^h) + \beta \cdot \delta^i \cdot \omega^h + \gamma}{v_i(\omega^h) + \beta \cdot s_i(\omega^h) + \gamma} \\ - &= Z_P(\omega^j) \prod\limits_{i=0}^{m-1} \frac{v_i(\omega^j) + \beta \cdot \delta^i \cdot \omega^j + \gamma}{v_i(\omega^j) + \beta \cdot s_i(\omega^j) + \gamma} -\end{array}$$ - -Then it is sufficient to enforce the rules: -$$ -Z_P(\omega X) \cdot \prod\limits_{i=0}^{m-1} \left(v_i(X) + \beta \cdot s_i(X) + \gamma\right) - Z_P(X) \cdot \prod\limits_{i=0}^{m-1} \left(v_i(X) + \beta \cdot \delta^i \cdot X + \gamma\right) = 0 \\ -\ell_0 \cdot (1 - Z_P(X)) = 0 -$$ - -This assumes that the number of columns $m$ is such that the polynomial in the first -rule above fits within the degree bound of the PLONK configuration. We will see -[below](#spanning-a-large-number-of-columns) how to handle a larger number of columns. - -> The optimization used to obtain the simple representation of the identity permutation was suggested -> by Vitalik Buterin for PLONK, and is described at the end of section 8 of the PLONK paper. Note that -> the $\delta^i$ are all distinct quadratic non-residues, provided that the number of columns that -> are enabled for equality is no more than $T$, which always holds in practice for the curves used in -> Halo 2. - -## Zero-knowledge adjustment - -Similarly to the [lookup argument](lookup.md#zero-knowledge-adjustment), we need an -adjustment to the above argument to account for the last $t$ rows of each column being -filled with random values. - -We limit the number of usable rows to $u = 2^k - t - 1.$ We add two selectors, -defined in the same way as for the lookup argument: - -* $q_\mathit{blind}$ is set to $1$ on the last $t$ rows, and $0$ elsewhere; -* $q_\mathit{last}$ is set to $1$ only on row $u,$ and $0$ elsewhere (i.e. it is set on - the row in between the usable rows and the blinding rows). - -We enable the product rule from above only for the usable rows: - -$\big(1 - (q_\mathit{last}(X) + q_\mathit{blind}(X))\big) \cdot$ -$\hspace{1em}\left(Z_P(\omega X) \cdot \prod\limits_{i=0}^{m-1} \left(v_i(X) + \beta \cdot s_i(X) + \gamma\right) - Z_P(X) \cdot \prod\limits_{i=0}^{m-1} \left(v_i(X) + \beta \cdot \delta^i \cdot X + \gamma\right)\right) = 0$ - -The rule that is enabled on row $0$ remains the same: - -$$ -\ell_0(X) \cdot (1 - Z_P(X)) = 0 -$$ - -Since we can no longer rely on the wraparound to ensure that each product $Z_P$ becomes -$1$ again at $\omega^{2^k},$ we would instead need to constrain $Z(\omega^u) = 1.$ This -raises the same problem that was described for the lookup argument. So we allow -$Z(\omega^u)$ to be either zero or one: - -$$ -q_\mathit{last}(X) \cdot (Z_P(X)^2 - Z_P(X)) = 0 -$$ - -which gives perfect completeness and zero knowledge. - -## Spanning a large number of columns - -The halo2 implementation does not in practice limit the number of columns for which -equality constraints can be enabled. Therefore, it must solve the problem that the -above approach might yield a product rule with a polynomial that exceeds the PLONK -configuration's degree bound. The degree bound could be raised, but this would be -inefficient if no other rules require a larger degree. - -Instead, we split the product across $b$ sets of $m$ columns, using product columns -$Z_{P,0}, \ldots Z_{P,b-1},$ and we use another rule to copy the product from the end -of one column set to the beginning of the next. - -That is, for $0 \leq a < b$ we have: - -$\big(1 - (q_\mathit{last}(X) + q_\mathit{blind}(X))\big) \cdot$ -$\hspace{1em}\left(Z_{P,a}(\omega X) \cdot \!\prod\limits_{i=am}^{(a+1)m-1}\! \left(v_i(X) + \beta \cdot s_i(X) + \gamma\right) - Z_P(X) \cdot \!\prod\limits_{i=am}^{(a+1)m-1}\! \left(v_i(X) + \beta \cdot \delta^i \cdot X + \gamma\right)\right)$ -$\hspace{2em}= 0$ - -> For simplicity this is written assuming that the number of columns enabled for -> equality constraints is a multiple of $m$; if not then the products for the last -> column set will have fewer than $m$ terms. - -For the first column set we have: - -$$ -\ell_0 \cdot (1 - Z_{P,0}(X)) = 0 -$$ - -For each subsequent column set, $0 < a < b,$ we use the following rule to copy -$Z_{P,a-1}(\omega^u)$ to the start of the next column set, $Z_{P,a}(\omega^0)$: - -$$ -\ell_0 \cdot \left(Z_{P,a}(X) - Z_{P,a-1}(\omega^u X)\right) = 0 -$$ - -For the last column set, we allow $Z_{P,b-1}(\omega^u)$ to be either zero or one: - -$$ -q_\mathit{last}(X) \cdot \left(Z_{P,b-1}(X)^2 - Z_{P,b-1}(X)\right) = 0 -$$ - -which gives perfect completeness and zero knowledge as before. diff --git a/book/src/design/proving-system/vanishing.md b/book/src/design/proving-system/vanishing.md deleted file mode 100644 index 72e1e21ee3..0000000000 --- a/book/src/design/proving-system/vanishing.md +++ /dev/null @@ -1,79 +0,0 @@ -# Vanishing argument - -Having committed to the circuit assignments, the prover now needs to demonstrate that the -various circuit relations are satisfied: - -- The custom gates, represented by polynomials $\text{gate}_i(X)$. -- The rules of the lookup arguments. -- The rules of the equality constraint permutations. - -Each of these relations is represented as a polynomial of degree $d$ (the maximum degree -of any of the relations) with respect to the circuit columns. Given that the degree of the -assignment polynomials for each column is $n - 1$, the relation polynomials have degree -$d(n - 1)$ with respect to $X$. - -> In our [example](../proving-system.md#example), these would be the gate polynomials, of -> degree $3n - 3$: -> -> - $\text{gate}_0(X) = a_0(X) \cdot a_1(X) \cdot a_2(X \omega^{-1}) - a_3(X)$ -> - $\text{gate}_1(X) = f_0(X \omega^{-1}) \cdot a_2(X)$ -> - $\text{gate}_2(X) = f_0(X) \cdot a_3(X) \cdot a_0(X)$ - -A relation is satisfied if its polynomial is equal to zero. One way to demonstrate this is -to divide each polynomial relation by the vanishing polynomial $t(X) = (X^n - 1)$, which -is the lowest-degree monomial that has roots at every $\omega^i$. If relation's polynomial -is perfectly divisible by $t(X)$, it is equal to zero over the domain (as desired). - -This simple construction would require a polynomial commitment per relation. Instead, we -commit to all of the circuit relations simultaneously: the verifier samples $y$, and then -the prover constructs the quotient polynomial - -$$h(X) = \frac{\text{gate}_0(X) + y \cdot \text{gate}_1(X) + \dots + y^i \cdot \text{gate}_i(X) + \dots}{t(X)},$$ - -where the numerator is a random (the prover commits to the cell assignments before the -verifier samples $y$) linear combination of the circuit relations. - -- If the numerator polynomial (in formal indeterminate $X$) is perfectly divisible by - $t(X)$, then with high probability all relations are satisfied. -- Conversely, if at least one relation is not satisfied, then with high probability - $h(x) \cdot t(x)$ will not equal the evaluation of the numerator at $x$. In this case, - the numerator polynomial would not be perfectly divisible by $t(X)$. - -## Committing to $h(X)$ - -$h(X)$ has degree $d(n - 1) - n$ (because the divisor $t(X)$ has degree $n$). However, the -polynomial commitment scheme we use for Halo 2 only supports committing to polynomials of -degree $n - 1$ (which is the maximum degree that the rest of the protocol needs to commit -to). Instead of increasing the cost of the polynomial commitment scheme, the prover split -$h(X)$ into pieces of degree $n - 1$ - -$$h_0(X) + X^n h_1(X) + \dots + X^{n(d-1)} h_{d-1}(X),$$ - -and produces blinding commitments to each piece - -$$\mathbf{H} = [\text{Commit}(h_0(X)), \text{Commit}(h_1(X)), \dots, \text{Commit}(h_{d-1}(X))].$$ - -## Evaluating the polynomials - -At this point, all properties of the circuit have been committed to. The verifier now -wants to see if the prover committed to the correct $h(X)$ polynomial. The verifier -samples $x$, and the prover produces the purported evaluations of the various polynomials -at $x$, for all the relative offsets used in the circuit, as well as $h(X)$. - -> In our [example](../proving-system.md#example), this would be: -> -> - $a_0(x)$ -> - $a_1(x)$ -> - $a_2(x)$, $a_2(x \omega^{-1})$ -> - $a_3(x)$ -> - $f_0(x)$, $f_0(x \omega^{-1})$ -> - $h_0(x)$, ..., $h_{d-1}(x)$ - -The verifier checks that these evaluations satisfy the form of $h(X)$: - -$$\frac{\text{gate}_0(x) + \dots + y^i \cdot \text{gate}_i(x) + \dots}{t(x)} = h_0(x) + \dots + x^{n(d-1)} h_{d-1}(x)$$ - -Now content that the evaluations collectively satisfy the gate constraints, the verifier -needs to check that the evaluations themselves are consistent with the original -[circuit commitments](circuit-commitments.md), as well as $\mathbf{H}$. To implement this -efficiently, we use a [multipoint opening argument](multipoint-opening.md). diff --git a/book/src/user.md b/book/src/user.md deleted file mode 100644 index c13533a519..0000000000 --- a/book/src/user.md +++ /dev/null @@ -1,5 +0,0 @@ -# User Documentation - -You're probably here because you want to write circuits? Excellent! - -This section will guide you through the process of creating circuits with halo2. diff --git a/book/src/user/dev-tools.md b/book/src/user/dev-tools.md deleted file mode 100644 index 5aebf78deb..0000000000 --- a/book/src/user/dev-tools.md +++ /dev/null @@ -1,114 +0,0 @@ -# Developer tools - -The `halo2` crate includes several utilities to help you design and implement your -circuits. - -## Mock prover - -`halo2_proofs::dev::MockProver` is a tool for debugging circuits, as well as cheaply verifying -their correctness in unit tests. The private and public inputs to the circuit are -constructed as would normally be done to create a proof, but `MockProver::run` instead -creates an object that will test every constraint in the circuit directly. It returns -granular error messages that indicate which specific constraint (if any) is not satisfied. - -## Circuit visualizations - -The `dev-graph` feature flag exposes several helper methods for creating graphical -representations of circuits. - -On Debian systems, you will need the following additional packages: -```plaintext -sudo apt install cmake libexpat1-dev libfreetype6-dev -``` - -### Circuit layout - -`halo2_proofs::dev::CircuitLayout` renders the circuit layout as a grid: - -```rust,ignore,no_run -{{#include ../../../halo2_proofs/examples/circuit-layout.rs:dev-graph}} -``` - -- Columns are laid out from left to right as instance, advice and fixed. The order of - columns is otherwise without meaning. - - Instance columns have a white background. - - Advice columns have a red background. - - Fixed columns have a blue background. -- Regions are shown as labelled green boxes (overlaying the background colour). A region - may appear as multiple boxes if some of its columns happen to not be adjacent. -- Cells that have been assigned to by the circuit will be shaded in grey. If any cells are - assigned to more than once (which is usually a mistake), they will be shaded darker than - the surrounding cells. - -### Circuit structure - -`halo2_proofs::dev::circuit_dot_graph` builds a [DOT graph string] representing the given -circuit, which can then be rendered with a variety of [layout programs]. The graph is built -from calls to `Layouter::namespace` both within the circuit, and inside the gadgets and -chips that it uses. - -[DOT graph string]: https://graphviz.org/doc/info/lang.html -[layout programs]: https://en.wikipedia.org/wiki/DOT_(graph_description_language)#Layout_programs - -```rust,ignore,no_run -fn main() { - // Prepare the circuit you want to render. - // You don't need to include any witness variables. - let a = Fp::rand(); - let instance = Fp::one() + Fp::one(); - let lookup_table = vec![instance, a, a, Fp::zero()]; - let circuit: MyCircuit = MyCircuit { - a: None, - lookup_table, - }; - - // Generate the DOT graph string. - let dot_string = halo2_proofs::dev::circuit_dot_graph(&circuit); - - // Now you can either handle it in Rust, or just - // print it out to use with command-line tools. - print!("{}", dot_string); -} -``` - -## Cost estimator - -The `cost-model` binary takes high-level parameters for a circuit design, and estimates -the verification cost, as well as resulting proof size. - -```plaintext -Usage: cargo run --example cost-model -- [OPTIONS] k - -Positional arguments: - k 2^K bound on the number of rows. - -Optional arguments: - -h, --help Print this message. - -a, --advice R[,R..] An advice column with the given rotations. May be repeated. - -i, --instance R[,R..] An instance column with the given rotations. May be repeated. - -f, --fixed R[,R..] A fixed column with the given rotations. May be repeated. - -g, --gate-degree D Maximum degree of the custom gates. - -l, --lookup N,I,T A lookup over N columns with max input degree I and max table degree T. May be repeated. - -p, --permutation N A permutation over N columns. May be repeated. -``` - -For example, to estimate the cost of a circuit with three advice columns and one fixed -column (with various rotations), and a maximum gate degree of 4: - -```plaintext -> cargo run --example cost-model -- -a 0,1 -a 0 -a-0,-1,1 -f 0 -g 4 11 - Finished dev [unoptimized + debuginfo] target(s) in 0.03s - Running `target/debug/examples/cost-model -a 0,1 -a 0 -a 0,-1,1 -f 0 -g 4 11` -Circuit { - k: 11, - max_deg: 4, - advice_columns: 3, - lookups: 0, - permutations: [], - column_queries: 7, - point_sets: 3, - estimator: Estimator, -} -Proof size: 1440 bytes -Verification: at least 81.689ms -``` diff --git a/book/src/user/experimental-features.md b/book/src/user/experimental-features.md deleted file mode 100644 index 78ea802ce4..0000000000 --- a/book/src/user/experimental-features.md +++ /dev/null @@ -1,140 +0,0 @@ -# Experimental features - -In `privacy-scaling-explorations/halo2` fork we have implemented many experimental features to cover different use cases, especially for those with huge circuits. We collect them in this page for easier tracking and referencing. - -## Commitment scheme abstraction - -To support different kinds of polynomial commitment schemes, we've added a trait `CommitmentScheme` to allow create/verify proofs with different commitment scheme implementations, currently there are 2 available implementations in this fork: - -- [`IPACommitmentScheme`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/poly/ipa/commitment/struct.IPACommitmentScheme.html) - - The original implementation from `zcash/halo2` with the original multi-open strategy `{Prover,Verifier}IPA` - -- [`KZGCommitmentScheme`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/poly/kzg/commitment/struct.KZGCommitmentScheme.html) - - KZG commitment scheme as in [GWC19](https://eprint.iacr.org/2019/953), which doesn't commit the instance columns, with 2 multi-open strategies available: - - - `{Prover,Verifier}GWC` - The original strategy in [GWC19](https://eprint.iacr.org/2019/953) - - `{Prover,Verifier}SHPLONK` - The strategy proposed in [BDFG20](https://eprint.iacr.org/2020/081) - -When using `create_proof` and `verify_proof`, we need to specify the commitment scheme and multi-open strategy like: - -```rust -// Using IPA -create_proof, ProverIPA<_>, _, _, _, _> -verify_proof, ProverIPA<_>, _, _, _> - -// Using KZG with GWC19 mutli-open strategy -create_proof, ProverGWC<_>, _, _, _, _> -verify_proof, ProverGWC<_>, _, _, _> - -// Using KZG with BDFG20 mutli-open strategy -create_proof, ProverSHPLONK<_>, _, _, _, _> -verify_proof, ProverSHPLONK<_>, _, _, _> -``` - -## `ConstraintSystem` extension - -### Dynamic lookup - -[`ConstraintSystem::lookup_any`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/struct.ConstraintSystem.html#method.lookup_any) is added for use cases that need to lookup dynamic table instead of fixed table. - -Unlike `ConstraintSystem::lookup` which only allows `TableColumn`(s) as table, it allows any `Expression`(s) without simple selector. - -### Shuffle - -[`ConstraintSystem::shuffle`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/struct.ConstraintSystem.html#method.shuffle) is added for use cases that only need shuffle without pre-defined mapping. - -It allows us to prove any `Expression`(s) without simple selector is shuffled from the other. For example `halo2_proofs/examples/shuffle_api.rs` shows how to prove two lists of 2-tuples are shuffled of each other. - -### Multi-phase - -[`ConstraintSystem::advice_column_in`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/struct.ConstraintSystem.html#method.advice_column_in) and [`ConstraintSystem::challenge_usable_after`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/struct.ConstraintSystem.html#method.challenge_usable_after) are added for use cases that build PIOP sub-routine themselves, currently it supports up-to 3 phases as `{First,Second,Third}Phase`. - -It allows us to allocate advice column in different interactive phases with extra challenges squeezed in-between. For example in `halo2_proofs/examples/shuffle.rs` it shows how to build a customized shuffle argument with such API. - -### Unblinded advice column - -[`ConstraintSystem::unblinded_advice_column`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/struct.ConstraintSystem.html#method.unblinded_advice_column) is added for use cases that want to reuse advice column commitment among different proofs. For example in `halo2_proofs/examples/vector-ops-unblinded.rs` it shows with this API and same assignment, two advice commitment from different proof can be same. - -Worth mentioning, re-using advice column commitment in different proofs will need more blinding factors than the amount that prover adds, otherwise some information will be leaked and it's no longer perfect zero-knowledge. - -## [`Expression`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/enum.Expression.html) extension - -- [`Expression::Challenge`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/enum.Expression.html#variant.Challenge) - - A variant added for multi-phase. It can be obtained by [`ConstraintSystem::challenge_usable_after`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/struct.ConstraintSystem.html#method.challenge_usable_after) and used as a pseudo-random constant. - -- [`Expression::identifier`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/enum.Expression.html#method.identifier) - - It prints `Expression` in a less verbose and human-readable way. - -## [`Circuit`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/trait.Circuit.html) extension - -- [`Circuit::Params`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/trait.Circuit.html#associatedtype.Params) - - To use this, feature `circuit-params` needs to be turned on. - - A associated type added for configuring circuit with runtime parameter. - - It allows us to implement [`Circuit::params`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/trait.Circuit.html#method.params) to return the parameter of a circuit, and implement [`Circuit::configure_with_params`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/trait.Circuit.html#method.configure_with_params) to configure circuit with runtime parameter retrieved earlier. - -## [`ProvingKey`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/struct.ProvingKey.html) & [`VerifyingKey`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/struct.VerifyingKey.html) de/serialization and [`SerdeFormat`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/enum.SerdeFormat.html) - -`ProvingKey::{read,write}` and `VerifyingKey::{read,write}` is added to serialize proving key and verifying key. - -For field elements and elliptic curve points inside pk and vk, currently we have 3 different de/serialization format: - -- [`SerdeFormat::Processed`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/enum.SerdeFormat.html#variant.Processed) - - It de/serializes them as `PrimeField::Repr` and `GroupEncoding::Repr` respectively, and checks all elements are valid. - -- [`SerdeFormat::RawBytes`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/enum.SerdeFormat.html#variant.RawBytes) - - It de/serializes them as `SerdeObject::{from_raw_bytes,to_raw_bytes}` respectively, and checks all elements are valid. - -- [`SerdeFormat::RawBytesUnchecked`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/enum.SerdeFormat.html#variant.RawBytesUnchecked) - - It de/serializes them as `SerdeObject::{from_raw_bytes,to_raw_bytes}` respectively, without checking if elements are valid. - -Also `ParamsKZG::{read_custom,write_custom}` follows the same rule, and by default `ParamsKZG::{read,write}` uses `SerdeFormat::RawBytes` for efficiency. - -## Thread safe [`Region`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/circuit/struct.Region.html) - -To use this, feature `thread-safe-region` needs to be turned on. - -It constrains the `RegionLayouter` to be `Send` so we can have a `Region` in different threads. It's useful when we want to arrange witness computation and assignment in the same place, but still keep the function `Send` so the caller can parallelize multiple of them. - -For example `halo2_proofs/examples/vector-mul.rs` shows how to parallelize region computation and assignment. - -## Optional selector compression - -Currently [`keygen_vk`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/fn.keygen_vk.html) changes configured `ConstraintSystem` to compresses simple selectors into smaller set of fixed columns to reduce cost. - -For some use cases that want to keep configured `ConstraintSystem` unchanged they can do the verifying key generation by calling [`keygen_vk_custom`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/plonk/fn.keygen_vk_custom.html) with second argument as `false` instead, which disables the selector compression. - -## [`MockProver`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/dev/struct.MockProver.html) improvement - -- [`MockProver::verify_par`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/dev/struct.MockProver.html#method.verify_par) - - Same checks as `MockProver::verify`, but parallelized. - -- [`MockProver::verify_at_rows`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/dev/struct.MockProver.html#method.verify_at_rows) - - Same checks as `MockProver::verify`, but only on specified rows. - -- [`MockProver::verify_at_rows_par`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/dev/struct.MockProver.html#method.verify_at_rows_par) - - Same checks as `MockProver::verify_at_rows`, but parallelized. - -- [`MockProver::assert_satisfied_par`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/dev/struct.MockProver.html#method.assert_satisfied_par) - - Same assertions as `MockProver::assert_satisfied`, but parallelized. - -- [`MockProver::assert_satisfied_at_rows_par`](https://privacy-scaling-explorations.github.io/halo2/halo2_proofs/dev/struct.MockProver.html#method.assert_satisfied_at_rows_par) - - Same assertions as `MockProver::assert_satisfied_par`, but only on specified rows. - -## `Evaluator` and `evaluate_h` - -They are introduced to improve quotient computation speed and memory usage for circuit with complicated `Expression`. diff --git a/book/src/user/gadgets.md b/book/src/user/gadgets.md deleted file mode 100644 index 406e8aa011..0000000000 --- a/book/src/user/gadgets.md +++ /dev/null @@ -1 +0,0 @@ -# Gadgets diff --git a/book/src/user/lookup-tables.md b/book/src/user/lookup-tables.md deleted file mode 100644 index 2709a94f36..0000000000 --- a/book/src/user/lookup-tables.md +++ /dev/null @@ -1,11 +0,0 @@ -# Lookup tables - -In normal programs, you can trade memory for CPU to improve performance, by pre-computing -and storing lookup tables for some part of the computation. We can do the same thing in -halo2 circuits! - -A lookup table can be thought of as enforcing a *relation* between variables, where the relation is expressed as a table. -Assuming we have only one lookup argument in our constraint system, the total size of tables is constrained by the size of the circuit: -each table entry costs one row, and it also costs one row to do each lookup. - -TODO diff --git a/book/src/user/simple-example.md b/book/src/user/simple-example.md deleted file mode 100644 index fecf518f76..0000000000 --- a/book/src/user/simple-example.md +++ /dev/null @@ -1,86 +0,0 @@ -# A simple example - -Let's start with a simple circuit, to introduce you to the common APIs and how they are -used. The circuit will take a public input $c$, and will prove knowledge of two private -inputs $a$ and $b$ such that - -$$a^2 \cdot b^2 = c.$$ - -## Define instructions - -Firstly, we need to define the instructions that our circuit will rely on. Instructions -are the boundary between high-level [gadgets](../concepts/gadgets.md) and the low-level -circuit operations. Instructions may be as coarse or as granular as desired, but in -practice you want to strike a balance between an instruction being large enough to -effectively optimize its implementation, and small enough that it is meaningfully -reusable. - -For our circuit, we will use three instructions: -- Load a private number into the circuit. -- Multiply two numbers. -- Expose a number as a public input to the circuit. - -We also need a type for a variable representing a number. Instruction interfaces provide -associated types for their inputs and outputs, to allow the implementations to represent -these in a way that makes the most sense for their optimization goals. - -```rust,ignore,no_run -{{#include ../../../halo2_proofs/examples/simple-example.rs:instructions}} -``` - -## Define a chip implementation - -For our circuit, we will build a [chip](../concepts/chips.md) that provides the above -numeric instructions for a finite field. - -```rust,ignore,no_run -{{#include ../../../halo2_proofs/examples/simple-example.rs:chip}} -``` - -Every chip needs to implement the `Chip` trait. This defines the properties of the chip -that a `Layouter` may rely on when synthesizing a circuit, as well as enabling any initial -state that the chip requires to be loaded into the circuit. - -```rust,ignore,no_run -{{#include ../../../halo2_proofs/examples/simple-example.rs:chip-impl}} -``` - -## Configure the chip - -The chip needs to be configured with the columns, permutations, and gates that will be -required to implement all of the desired instructions. - -```rust,ignore,no_run -{{#include ../../../halo2_proofs/examples/simple-example.rs:chip-config}} -``` - -## Implement chip traits - -```rust,ignore,no_run -{{#include ../../../halo2_proofs/examples/simple-example.rs:instructions-impl}} -``` - -## Build the circuit - -Now that we have the instructions we need, and a chip that implements them, we can finally -build our circuit! - -```rust,ignore,no_run -{{#include ../../../halo2_proofs/examples/simple-example.rs:circuit}} -``` - -## Testing the circuit - -`halo2_proofs::dev::MockProver` can be used to test that the circuit is working correctly. The -private and public inputs to the circuit are constructed as we will do to create a proof, -but by passing them to `MockProver::run` we get an object that can test every constraint -in the circuit, and tell us exactly what is failing (if anything). - -```rust,ignore,no_run -{{#include ../../../halo2_proofs/examples/simple-example.rs:test-circuit}} -``` - -## Full example - -You can find the source code for this example -[here](https://github.com/zcash/halo2/tree/main/halo2_proofs/examples/simple-example.rs). diff --git a/book/src/user/tips-and-tricks.md b/book/src/user/tips-and-tricks.md deleted file mode 100644 index b2d7be0d95..0000000000 --- a/book/src/user/tips-and-tricks.md +++ /dev/null @@ -1,71 +0,0 @@ -# Tips and tricks - -This section contains various ideas and snippets that you might find useful while writing -halo2 circuits. - -## Small range constraints - -A common constraint used in R1CS circuits is the boolean constraint: $b * (1 - b) = 0$. -This constraint can only be satisfied by $b = 0$ or $b = 1$. - -In halo2 circuits, you can similarly constrain a cell to have one of a small set of -values. For example, to constrain $a$ to the range $[0..5]$, you would create a gate of -the form: - -$$a \cdot (1 - a) \cdot (2 - a) \cdot (3 - a) \cdot (4 - a) = 0$$ - -while to constrain $c$ to be either 7 or 13, you would use: - -$$(7 - c) \cdot (13 - c) = 0$$ - -> The underlying principle here is that we create a polynomial constraint with roots at -> each value in the set of possible values we want to allow. In R1CS circuits, the maximum -> supported polynomial degree is 2 (due to all constraints being of the form $a * b = c$). -> In halo2 circuits, you can use arbitrary-degree polynomials - with the proviso that -> higher-degree constraints are more expensive to use. - -Note that the roots don't have to be constants; for example $(a - x) \cdot (a - y) \cdot (a - z) = 0$ will constrain $a$ to be equal to one of $\{ x, y, z \}$ where the latter can be arbitrary polynomials, as long as the whole expression stays within the maximum degree bound. - -## Small set interpolation -We can use Lagrange interpolation to create a polynomial constraint that maps -$f(X) = Y$ for small sets of $X \in \{x_i\}, Y \in \{y_i\}$. - -For instance, say we want to map a 2-bit value to a "spread" version interleaved -with zeros. We first precompute the evaluations at each point: - -$$ -\begin{array}{rcl} -00 \rightarrow 0000 &\implies& 0 \rightarrow 0 \\ -01 \rightarrow 0001 &\implies& 1 \rightarrow 1 \\ -10 \rightarrow 0100 &\implies& 2 \rightarrow 4 \\ -11 \rightarrow 0101 &\implies& 3 \rightarrow 5 -\end{array} -$$ - -Then, we construct the Lagrange basis polynomial for each point using the -identity: -$$\mathcal{l}_j(X) = \prod_{0 \leq m < k,\; m \neq j} \frac{x - x_m}{x_j - x_m},$$ -where $k$ is the number of data points. ($k = 4$ in our example above.) - -Recall that the Lagrange basis polynomial $\mathcal{l}_j(X)$ evaluates to $1$ at -$X = x_j$ and $0$ at all other $x_i, j \neq i.$ - -Continuing our example, we get four Lagrange basis polynomials: - -$$ -\begin{array}{ccc} -l_0(X) &=& \frac{(X - 3)(X - 2)(X - 1)}{(-3)(-2)(-1)} \\[1ex] -l_1(X) &=& \frac{(X - 3)(X - 2)(X)}{(-2)(-1)(1)} \\[1ex] -l_2(X) &=& \frac{(X - 3)(X - 1)(X)}{(-1)(1)(2)} \\[1ex] -l_3(X) &=& \frac{(X - 2)(X - 1)(X)}{(1)(2)(3)} -\end{array} -$$ - -Our polynomial constraint is then - -$$ -\begin{array}{cccccccccccl} -&f(0) \cdot l_0(X) &+& f(1) \cdot l_1(X) &+& f(2) \cdot l_2(X) &+& f(3) \cdot l_3(X) &-& f(X) &=& 0 \\ -\implies& 0 \cdot l_0(X) &+& 1 \cdot l_1(X) &+& 4 \cdot l_2(X) &+& 5 \cdot l_3(X) &-& f(X) &=& 0. \\ -\end{array} -$$ diff --git a/halo2_proofs/examples/circuit-layout.rs b/examples/circuit-layout.rs similarity index 100% rename from halo2_proofs/examples/circuit-layout.rs rename to examples/circuit-layout.rs diff --git a/halo2_proofs/examples/proof-size.rs b/examples/proof-size.rs similarity index 100% rename from halo2_proofs/examples/proof-size.rs rename to examples/proof-size.rs diff --git a/halo2_proofs/examples/serialization.rs b/examples/serialization.rs similarity index 100% rename from halo2_proofs/examples/serialization.rs rename to examples/serialization.rs diff --git a/halo2_proofs/examples/shuffle.rs b/examples/shuffle.rs similarity index 100% rename from halo2_proofs/examples/shuffle.rs rename to examples/shuffle.rs diff --git a/halo2_proofs/examples/shuffle_api.rs b/examples/shuffle_api.rs similarity index 100% rename from halo2_proofs/examples/shuffle_api.rs rename to examples/shuffle_api.rs diff --git a/halo2_proofs/examples/simple-example.rs b/examples/simple-example.rs similarity index 100% rename from halo2_proofs/examples/simple-example.rs rename to examples/simple-example.rs diff --git a/halo2_proofs/examples/two-chip.rs b/examples/two-chip.rs similarity index 100% rename from halo2_proofs/examples/two-chip.rs rename to examples/two-chip.rs diff --git a/halo2_proofs/examples/vector-mul.rs b/examples/vector-mul.rs similarity index 100% rename from halo2_proofs/examples/vector-mul.rs rename to examples/vector-mul.rs diff --git a/halo2_proofs/examples/vector-ops-unblinded.rs b/examples/vector-ops-unblinded.rs similarity index 100% rename from halo2_proofs/examples/vector-ops-unblinded.rs rename to examples/vector-ops-unblinded.rs diff --git a/halo2/CHANGELOG.md b/halo2/CHANGELOG.md deleted file mode 100644 index 19cfec4d18..0000000000 --- a/halo2/CHANGELOG.md +++ /dev/null @@ -1,15 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to Rust's notion of -[Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.1.0-beta.2] - 2022-02-14 -### Removed -- Everything (moved to `halo2_proofs` crate). - -## [0.1.0-beta.1] - 2021-09-24 -Initial beta release! diff --git a/halo2/Cargo.toml b/halo2/Cargo.toml deleted file mode 100644 index 5618165271..0000000000 --- a/halo2/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "halo2" -version = "0.1.0-beta.2" -authors = [ - "Jack Grigg ", -] -edition = "2021" -rust-version = "1.56.1" -description = "[BETA] Fast zero-knowledge proof-carrying data implementation with no trusted setup" -license = "MIT OR Apache-2.0" -repository = "https://github.com/zcash/halo2" -documentation = "https://docs.rs/halo2" -readme = "../README.md" -categories = ["cryptography"] -keywords = ["halo", "proofs", "recursive", "zkp", "zkSNARKs"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] - -[dependencies] -halo2_proofs = { version = "0.3", path = "../halo2_proofs", default-features = false } - -[lib] -bench = false diff --git a/halo2/katex-header.html b/halo2/katex-header.html deleted file mode 100644 index 98e85904fa..0000000000 --- a/halo2/katex-header.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - \ No newline at end of file diff --git a/halo2/src/lib.rs b/halo2/src/lib.rs deleted file mode 100644 index 33cb67b4df..0000000000 --- a/halo2/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! # halo2 - -#![cfg_attr(docsrs, feature(doc_cfg))] -#![deny(rustdoc::broken_intra_doc_links)] -#![deny(missing_debug_implementations)] -#![deny(missing_docs)] -#![deny(unsafe_code)] diff --git a/halo2_proofs/Cargo.toml b/halo2_proofs/Cargo.toml deleted file mode 100644 index e340407dd6..0000000000 --- a/halo2_proofs/Cargo.toml +++ /dev/null @@ -1,109 +0,0 @@ -[package] -name = "halo2_proofs" -version = "0.3.0" -authors = [ - "Sean Bowe ", - "Ying Tong Lai ", - "Daira Hopwood ", - "Jack Grigg ", -] -edition = "2021" -rust-version = "1.66.0" -description = """ -Fast PLONK-based zero-knowledge proving system with no trusted setup -""" -license = "MIT OR Apache-2.0" -repository = "https://github.com/zcash/halo2" -documentation = "https://docs.rs/halo2_proofs" -readme = "README.md" -categories = ["cryptography"] -keywords = ["halo", "proofs", "zkp", "zkSNARKs"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] - -[[bench]] -name = "arithmetic" -harness = false - -[[bench]] -name = "commit_zk" -harness = false - -[[bench]] -name = "hashtocurve" -harness = false - -[[bench]] -name = "plonk" -harness = false - -[[bench]] -name = "dev_lookup" -harness = false - -[[bench]] -name = "fft" -harness = false - -[dependencies] -backtrace = { version = "0.3", optional = true } -ff = "0.13" -group = "0.13" -halo2curves = { version = "0.6.0", default-features = false } -rand_core = { version = "0.6", default-features = false } -tracing = "0.1" -blake2b_simd = "1" # MSRV 1.66.0 -sha3 = "0.9.1" -rand_chacha = "0.3" -serde = { version = "1", optional = true, features = ["derive"] } -serde_derive = { version = "1", optional = true} -rayon = "1.8" - -# Developer tooling dependencies -plotters = { version = "0.3.0", default-features = false, optional = true } -tabbycat = { version = "0.1", features = ["attributes"], optional = true } - -# Legacy circuit compatibility -halo2_legacy_pdqsort = { version = "0.1.0", optional = true } - -[dev-dependencies] -assert_matches = "1.5" -criterion = "0.3" -gumdrop = "0.8" -proptest = "1" -rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } -serde_json = "1" - -[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies] -getrandom = { version = "0.2", features = ["js"] } - -[features] -default = ["batch", "bits"] -dev-graph = ["plotters", "tabbycat"] -test-dev-graph = [ - "dev-graph", - "plotters/bitmap_backend", - "plotters/bitmap_encoder", - "plotters/ttf", -] -bits = ["halo2curves/bits"] -gadget-traces = ["backtrace"] -thread-safe-region = [] -sanity-checks = [] -batch = ["rand_core/getrandom"] -circuit-params = [] -cost-estimator = ["serde", "serde_derive"] -derive_serde = ["halo2curves/derive_serde"] - -[lib] -bench = false - -[[example]] -name = "circuit-layout" -required-features = ["test-dev-graph"] - -[[example]] -name = "proof-size" -required-features = ["cost-estimator"] diff --git a/halo2_proofs/README.md b/halo2_proofs/README.md deleted file mode 100644 index bdb9a63639..0000000000 --- a/halo2_proofs/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# halo2_proofs [![Crates.io](https://img.shields.io/crates/v/halo2_proofs.svg)](https://crates.io/crates/halo2_proofs) # - -## [Documentation](https://docs.rs/halo2_proofs) - -## Minimum Supported Rust Version - -Requires Rust **1.65.0** or higher. - -Minimum supported Rust version can be changed in the future, but it will be done with a -minor version bump. - -## Controlling parallelism - -`halo2_proofs` currently uses [rayon](https://github.com/rayon-rs/rayon) for parallel -computation. The `RAYON_NUM_THREADS` environment variable can be used to set the number of -threads. - -When compiling to WASM-targets, notice that since version `1.7`, `rayon` will fallback automatically (with no need to handle features) to require `getrandom` in order to be able to work. For more info related to WASM-compilation. - -See: [Rayon: Usage with WebAssembly](https://github.com/rayon-rs/rayon#usage-with-webassembly) for more - -## License - -Licensed under either of - - * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally -submitted for inclusion in the work by you, as defined in the Apache-2.0 -license, shall be dual licensed as above, without any additional terms or -conditions. diff --git a/halo2_proofs/katex-header.html b/halo2_proofs/katex-header.html deleted file mode 100644 index 98e85904fa..0000000000 --- a/halo2_proofs/katex-header.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - \ No newline at end of file diff --git a/halo2_proofs/proptest-regressions/plonk/assigned.txt b/proptest-regressions/plonk/assigned.txt similarity index 100% rename from halo2_proofs/proptest-regressions/plonk/assigned.txt rename to proptest-regressions/plonk/assigned.txt diff --git a/halo2_proofs/proptest-regressions/plonk/circuit/compress_selectors.txt b/proptest-regressions/plonk/circuit/compress_selectors.txt similarity index 100% rename from halo2_proofs/proptest-regressions/plonk/circuit/compress_selectors.txt rename to proptest-regressions/plonk/circuit/compress_selectors.txt diff --git a/halo2_proofs/src/arithmetic.rs b/src/arithmetic.rs similarity index 100% rename from halo2_proofs/src/arithmetic.rs rename to src/arithmetic.rs diff --git a/halo2_proofs/src/circuit.rs b/src/circuit.rs similarity index 100% rename from halo2_proofs/src/circuit.rs rename to src/circuit.rs diff --git a/halo2_proofs/src/circuit/floor_planner.rs b/src/circuit/floor_planner.rs similarity index 100% rename from halo2_proofs/src/circuit/floor_planner.rs rename to src/circuit/floor_planner.rs diff --git a/halo2_proofs/src/circuit/floor_planner/single_pass.rs b/src/circuit/floor_planner/single_pass.rs similarity index 100% rename from halo2_proofs/src/circuit/floor_planner/single_pass.rs rename to src/circuit/floor_planner/single_pass.rs diff --git a/halo2_proofs/src/circuit/floor_planner/v1.rs b/src/circuit/floor_planner/v1.rs similarity index 100% rename from halo2_proofs/src/circuit/floor_planner/v1.rs rename to src/circuit/floor_planner/v1.rs diff --git a/halo2_proofs/src/circuit/floor_planner/v1/strategy.rs b/src/circuit/floor_planner/v1/strategy.rs similarity index 100% rename from halo2_proofs/src/circuit/floor_planner/v1/strategy.rs rename to src/circuit/floor_planner/v1/strategy.rs diff --git a/halo2_proofs/src/circuit/layouter.rs b/src/circuit/layouter.rs similarity index 100% rename from halo2_proofs/src/circuit/layouter.rs rename to src/circuit/layouter.rs diff --git a/halo2_proofs/src/circuit/table_layouter.rs b/src/circuit/table_layouter.rs similarity index 100% rename from halo2_proofs/src/circuit/table_layouter.rs rename to src/circuit/table_layouter.rs diff --git a/halo2_proofs/src/circuit/value.rs b/src/circuit/value.rs similarity index 100% rename from halo2_proofs/src/circuit/value.rs rename to src/circuit/value.rs diff --git a/halo2_proofs/src/dev.rs b/src/dev.rs similarity index 100% rename from halo2_proofs/src/dev.rs rename to src/dev.rs diff --git a/halo2_proofs/src/dev/cost.rs b/src/dev/cost.rs similarity index 100% rename from halo2_proofs/src/dev/cost.rs rename to src/dev/cost.rs diff --git a/halo2_proofs/src/dev/cost_model.rs b/src/dev/cost_model.rs similarity index 100% rename from halo2_proofs/src/dev/cost_model.rs rename to src/dev/cost_model.rs diff --git a/halo2_proofs/src/dev/failure.rs b/src/dev/failure.rs similarity index 100% rename from halo2_proofs/src/dev/failure.rs rename to src/dev/failure.rs diff --git a/halo2_proofs/src/dev/failure/emitter.rs b/src/dev/failure/emitter.rs similarity index 100% rename from halo2_proofs/src/dev/failure/emitter.rs rename to src/dev/failure/emitter.rs diff --git a/halo2_proofs/src/dev/gates.rs b/src/dev/gates.rs similarity index 100% rename from halo2_proofs/src/dev/gates.rs rename to src/dev/gates.rs diff --git a/halo2_proofs/src/dev/graph.rs b/src/dev/graph.rs similarity index 100% rename from halo2_proofs/src/dev/graph.rs rename to src/dev/graph.rs diff --git a/halo2_proofs/src/dev/graph/layout.rs b/src/dev/graph/layout.rs similarity index 100% rename from halo2_proofs/src/dev/graph/layout.rs rename to src/dev/graph/layout.rs diff --git a/halo2_proofs/src/dev/metadata.rs b/src/dev/metadata.rs similarity index 100% rename from halo2_proofs/src/dev/metadata.rs rename to src/dev/metadata.rs diff --git a/halo2_proofs/src/dev/tfp.rs b/src/dev/tfp.rs similarity index 100% rename from halo2_proofs/src/dev/tfp.rs rename to src/dev/tfp.rs diff --git a/halo2_proofs/src/dev/util.rs b/src/dev/util.rs similarity index 100% rename from halo2_proofs/src/dev/util.rs rename to src/dev/util.rs diff --git a/halo2_proofs/src/helpers.rs b/src/helpers.rs similarity index 100% rename from halo2_proofs/src/helpers.rs rename to src/helpers.rs diff --git a/halo2_proofs/src/lib.rs b/src/lib.rs similarity index 100% rename from halo2_proofs/src/lib.rs rename to src/lib.rs diff --git a/halo2_proofs/src/multicore.rs b/src/multicore.rs similarity index 100% rename from halo2_proofs/src/multicore.rs rename to src/multicore.rs diff --git a/halo2_proofs/src/plonk.rs b/src/plonk.rs similarity index 100% rename from halo2_proofs/src/plonk.rs rename to src/plonk.rs diff --git a/halo2_proofs/src/plonk/assigned.rs b/src/plonk/assigned.rs similarity index 100% rename from halo2_proofs/src/plonk/assigned.rs rename to src/plonk/assigned.rs diff --git a/halo2_proofs/src/plonk/circuit.rs b/src/plonk/circuit.rs similarity index 100% rename from halo2_proofs/src/plonk/circuit.rs rename to src/plonk/circuit.rs diff --git a/halo2_proofs/src/plonk/circuit/compress_selectors.rs b/src/plonk/circuit/compress_selectors.rs similarity index 100% rename from halo2_proofs/src/plonk/circuit/compress_selectors.rs rename to src/plonk/circuit/compress_selectors.rs diff --git a/halo2_proofs/src/plonk/error.rs b/src/plonk/error.rs similarity index 100% rename from halo2_proofs/src/plonk/error.rs rename to src/plonk/error.rs diff --git a/halo2_proofs/src/plonk/evaluation.rs b/src/plonk/evaluation.rs similarity index 100% rename from halo2_proofs/src/plonk/evaluation.rs rename to src/plonk/evaluation.rs diff --git a/halo2_proofs/src/plonk/keygen.rs b/src/plonk/keygen.rs similarity index 100% rename from halo2_proofs/src/plonk/keygen.rs rename to src/plonk/keygen.rs diff --git a/halo2_proofs/src/plonk/lookup.rs b/src/plonk/lookup.rs similarity index 100% rename from halo2_proofs/src/plonk/lookup.rs rename to src/plonk/lookup.rs diff --git a/halo2_proofs/src/plonk/lookup/prover.rs b/src/plonk/lookup/prover.rs similarity index 100% rename from halo2_proofs/src/plonk/lookup/prover.rs rename to src/plonk/lookup/prover.rs diff --git a/halo2_proofs/src/plonk/lookup/verifier.rs b/src/plonk/lookup/verifier.rs similarity index 100% rename from halo2_proofs/src/plonk/lookup/verifier.rs rename to src/plonk/lookup/verifier.rs diff --git a/halo2_proofs/src/plonk/permutation.rs b/src/plonk/permutation.rs similarity index 100% rename from halo2_proofs/src/plonk/permutation.rs rename to src/plonk/permutation.rs diff --git a/halo2_proofs/src/plonk/permutation/keygen.rs b/src/plonk/permutation/keygen.rs similarity index 100% rename from halo2_proofs/src/plonk/permutation/keygen.rs rename to src/plonk/permutation/keygen.rs diff --git a/halo2_proofs/src/plonk/permutation/prover.rs b/src/plonk/permutation/prover.rs similarity index 100% rename from halo2_proofs/src/plonk/permutation/prover.rs rename to src/plonk/permutation/prover.rs diff --git a/halo2_proofs/src/plonk/permutation/verifier.rs b/src/plonk/permutation/verifier.rs similarity index 100% rename from halo2_proofs/src/plonk/permutation/verifier.rs rename to src/plonk/permutation/verifier.rs diff --git a/halo2_proofs/src/plonk/prover.rs b/src/plonk/prover.rs similarity index 100% rename from halo2_proofs/src/plonk/prover.rs rename to src/plonk/prover.rs diff --git a/halo2_proofs/src/plonk/shuffle.rs b/src/plonk/shuffle.rs similarity index 100% rename from halo2_proofs/src/plonk/shuffle.rs rename to src/plonk/shuffle.rs diff --git a/halo2_proofs/src/plonk/shuffle/prover.rs b/src/plonk/shuffle/prover.rs similarity index 100% rename from halo2_proofs/src/plonk/shuffle/prover.rs rename to src/plonk/shuffle/prover.rs diff --git a/halo2_proofs/src/plonk/shuffle/verifier.rs b/src/plonk/shuffle/verifier.rs similarity index 100% rename from halo2_proofs/src/plonk/shuffle/verifier.rs rename to src/plonk/shuffle/verifier.rs diff --git a/halo2_proofs/src/plonk/vanishing.rs b/src/plonk/vanishing.rs similarity index 100% rename from halo2_proofs/src/plonk/vanishing.rs rename to src/plonk/vanishing.rs diff --git a/halo2_proofs/src/plonk/vanishing/prover.rs b/src/plonk/vanishing/prover.rs similarity index 100% rename from halo2_proofs/src/plonk/vanishing/prover.rs rename to src/plonk/vanishing/prover.rs diff --git a/halo2_proofs/src/plonk/vanishing/verifier.rs b/src/plonk/vanishing/verifier.rs similarity index 100% rename from halo2_proofs/src/plonk/vanishing/verifier.rs rename to src/plonk/vanishing/verifier.rs diff --git a/halo2_proofs/src/plonk/verifier.rs b/src/plonk/verifier.rs similarity index 100% rename from halo2_proofs/src/plonk/verifier.rs rename to src/plonk/verifier.rs diff --git a/halo2_proofs/src/plonk/verifier/batch.rs b/src/plonk/verifier/batch.rs similarity index 100% rename from halo2_proofs/src/plonk/verifier/batch.rs rename to src/plonk/verifier/batch.rs diff --git a/halo2_proofs/src/poly.rs b/src/poly.rs similarity index 100% rename from halo2_proofs/src/poly.rs rename to src/poly.rs diff --git a/halo2_proofs/src/poly/commitment.rs b/src/poly/commitment.rs similarity index 100% rename from halo2_proofs/src/poly/commitment.rs rename to src/poly/commitment.rs diff --git a/halo2_proofs/src/poly/domain.rs b/src/poly/domain.rs similarity index 100% rename from halo2_proofs/src/poly/domain.rs rename to src/poly/domain.rs diff --git a/halo2_proofs/src/poly/ipa/commitment.rs b/src/poly/ipa/commitment.rs similarity index 100% rename from halo2_proofs/src/poly/ipa/commitment.rs rename to src/poly/ipa/commitment.rs diff --git a/halo2_proofs/src/poly/ipa/commitment/prover.rs b/src/poly/ipa/commitment/prover.rs similarity index 100% rename from halo2_proofs/src/poly/ipa/commitment/prover.rs rename to src/poly/ipa/commitment/prover.rs diff --git a/halo2_proofs/src/poly/ipa/commitment/verifier.rs b/src/poly/ipa/commitment/verifier.rs similarity index 100% rename from halo2_proofs/src/poly/ipa/commitment/verifier.rs rename to src/poly/ipa/commitment/verifier.rs diff --git a/halo2_proofs/src/poly/ipa/mod.rs b/src/poly/ipa/mod.rs similarity index 100% rename from halo2_proofs/src/poly/ipa/mod.rs rename to src/poly/ipa/mod.rs diff --git a/halo2_proofs/src/poly/ipa/msm.rs b/src/poly/ipa/msm.rs similarity index 100% rename from halo2_proofs/src/poly/ipa/msm.rs rename to src/poly/ipa/msm.rs diff --git a/halo2_proofs/src/poly/ipa/multiopen.rs b/src/poly/ipa/multiopen.rs similarity index 100% rename from halo2_proofs/src/poly/ipa/multiopen.rs rename to src/poly/ipa/multiopen.rs diff --git a/halo2_proofs/src/poly/ipa/multiopen/prover.rs b/src/poly/ipa/multiopen/prover.rs similarity index 100% rename from halo2_proofs/src/poly/ipa/multiopen/prover.rs rename to src/poly/ipa/multiopen/prover.rs diff --git a/halo2_proofs/src/poly/ipa/multiopen/verifier.rs b/src/poly/ipa/multiopen/verifier.rs similarity index 100% rename from halo2_proofs/src/poly/ipa/multiopen/verifier.rs rename to src/poly/ipa/multiopen/verifier.rs diff --git a/halo2_proofs/src/poly/ipa/strategy.rs b/src/poly/ipa/strategy.rs similarity index 100% rename from halo2_proofs/src/poly/ipa/strategy.rs rename to src/poly/ipa/strategy.rs diff --git a/halo2_proofs/src/poly/kzg/commitment.rs b/src/poly/kzg/commitment.rs similarity index 100% rename from halo2_proofs/src/poly/kzg/commitment.rs rename to src/poly/kzg/commitment.rs diff --git a/halo2_proofs/src/poly/kzg/mod.rs b/src/poly/kzg/mod.rs similarity index 100% rename from halo2_proofs/src/poly/kzg/mod.rs rename to src/poly/kzg/mod.rs diff --git a/halo2_proofs/src/poly/kzg/msm.rs b/src/poly/kzg/msm.rs similarity index 100% rename from halo2_proofs/src/poly/kzg/msm.rs rename to src/poly/kzg/msm.rs diff --git a/halo2_proofs/src/poly/kzg/multiopen.rs b/src/poly/kzg/multiopen.rs similarity index 100% rename from halo2_proofs/src/poly/kzg/multiopen.rs rename to src/poly/kzg/multiopen.rs diff --git a/halo2_proofs/src/poly/kzg/multiopen/gwc.rs b/src/poly/kzg/multiopen/gwc.rs similarity index 100% rename from halo2_proofs/src/poly/kzg/multiopen/gwc.rs rename to src/poly/kzg/multiopen/gwc.rs diff --git a/halo2_proofs/src/poly/kzg/multiopen/gwc/prover.rs b/src/poly/kzg/multiopen/gwc/prover.rs similarity index 100% rename from halo2_proofs/src/poly/kzg/multiopen/gwc/prover.rs rename to src/poly/kzg/multiopen/gwc/prover.rs diff --git a/halo2_proofs/src/poly/kzg/multiopen/gwc/verifier.rs b/src/poly/kzg/multiopen/gwc/verifier.rs similarity index 100% rename from halo2_proofs/src/poly/kzg/multiopen/gwc/verifier.rs rename to src/poly/kzg/multiopen/gwc/verifier.rs diff --git a/halo2_proofs/src/poly/kzg/multiopen/shplonk.rs b/src/poly/kzg/multiopen/shplonk.rs similarity index 100% rename from halo2_proofs/src/poly/kzg/multiopen/shplonk.rs rename to src/poly/kzg/multiopen/shplonk.rs diff --git a/halo2_proofs/src/poly/kzg/multiopen/shplonk/prover.rs b/src/poly/kzg/multiopen/shplonk/prover.rs similarity index 100% rename from halo2_proofs/src/poly/kzg/multiopen/shplonk/prover.rs rename to src/poly/kzg/multiopen/shplonk/prover.rs diff --git a/halo2_proofs/src/poly/kzg/multiopen/shplonk/verifier.rs b/src/poly/kzg/multiopen/shplonk/verifier.rs similarity index 100% rename from halo2_proofs/src/poly/kzg/multiopen/shplonk/verifier.rs rename to src/poly/kzg/multiopen/shplonk/verifier.rs diff --git a/halo2_proofs/src/poly/kzg/strategy.rs b/src/poly/kzg/strategy.rs similarity index 100% rename from halo2_proofs/src/poly/kzg/strategy.rs rename to src/poly/kzg/strategy.rs diff --git a/halo2_proofs/src/poly/multiopen_test.rs b/src/poly/multiopen_test.rs similarity index 100% rename from halo2_proofs/src/poly/multiopen_test.rs rename to src/poly/multiopen_test.rs diff --git a/halo2_proofs/src/poly/query.rs b/src/poly/query.rs similarity index 100% rename from halo2_proofs/src/poly/query.rs rename to src/poly/query.rs diff --git a/halo2_proofs/src/poly/strategy.rs b/src/poly/strategy.rs similarity index 100% rename from halo2_proofs/src/poly/strategy.rs rename to src/poly/strategy.rs diff --git a/halo2_proofs/src/transcript.rs b/src/transcript.rs similarity index 100% rename from halo2_proofs/src/transcript.rs rename to src/transcript.rs diff --git a/halo2_proofs/tests/plonk_api.rs b/tests/plonk_api.rs similarity index 100% rename from halo2_proofs/tests/plonk_api.rs rename to tests/plonk_api.rs From 4361bada45cb068c98744771e7e8b8931acb3a26 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Fri, 29 Nov 2024 10:33:54 +0100 Subject: [PATCH 02/20] Remove Shuffle --- examples/shuffle_api.rs | 216 ----------------------------- src/dev.rs | 61 --------- src/dev/failure.rs | 202 --------------------------- src/plonk.rs | 1 - src/plonk/circuit.rs | 60 +------- src/plonk/evaluation.rs | 101 +------------- src/plonk/prover.rs | 48 +------ src/plonk/shuffle.rs | 67 --------- src/plonk/shuffle/prover.rs | 250 ---------------------------------- src/plonk/shuffle/verifier.rs | 138 ------------------- src/plonk/verifier.rs | 162 ++++++++-------------- 11 files changed, 62 insertions(+), 1244 deletions(-) delete mode 100644 examples/shuffle_api.rs delete mode 100644 src/plonk/shuffle.rs delete mode 100644 src/plonk/shuffle/prover.rs delete mode 100644 src/plonk/shuffle/verifier.rs diff --git a/examples/shuffle_api.rs b/examples/shuffle_api.rs deleted file mode 100644 index 259e038d06..0000000000 --- a/examples/shuffle_api.rs +++ /dev/null @@ -1,216 +0,0 @@ -use std::{marker::PhantomData, vec}; - -use ff::FromUniformBytes; -use halo2_proofs::{ - arithmetic::Field, - circuit::{Layouter, SimpleFloorPlanner, Value}, - plonk::{ - create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, - ConstraintSystem, Error, Fixed, Selector, - }, - poly::Rotation, - poly::{ - commitment::ParamsProver, - ipa::{ - commitment::{IPACommitmentScheme, ParamsIPA}, - multiopen::{ProverIPA, VerifierIPA}, - strategy::AccumulatorStrategy, - }, - VerificationStrategy, - }, - transcript::{ - Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, - }, -}; -use halo2curves::{pasta::EqAffine, CurveAffine}; -use rand_core::OsRng; - -struct ShuffleChip { - config: ShuffleConfig, - _marker: PhantomData, -} - -#[derive(Clone, Debug)] -struct ShuffleConfig { - input_0: Column, - input_1: Column, - shuffle_0: Column, - shuffle_1: Column, - s_input: Selector, - s_shuffle: Selector, -} - -impl ShuffleChip { - fn construct(config: ShuffleConfig) -> Self { - Self { - config, - _marker: PhantomData, - } - } - - fn configure( - meta: &mut ConstraintSystem, - input_0: Column, - input_1: Column, - shuffle_0: Column, - shuffle_1: Column, - ) -> ShuffleConfig { - let s_shuffle = meta.complex_selector(); - let s_input = meta.complex_selector(); - meta.shuffle("shuffle", |meta| { - let s_input = meta.query_selector(s_input); - let s_shuffle = meta.query_selector(s_shuffle); - let input_0 = meta.query_advice(input_0, Rotation::cur()); - let input_1 = meta.query_fixed(input_1, Rotation::cur()); - let shuffle_0 = meta.query_advice(shuffle_0, Rotation::cur()); - let shuffle_1 = meta.query_advice(shuffle_1, Rotation::cur()); - vec![ - (s_input.clone() * input_0, s_shuffle.clone() * shuffle_0), - (s_input * input_1, s_shuffle * shuffle_1), - ] - }); - ShuffleConfig { - input_0, - input_1, - shuffle_0, - shuffle_1, - s_input, - s_shuffle, - } - } -} - -#[derive(Default)] -struct MyCircuit { - input_0: Vec>, - input_1: Vec, - shuffle_0: Vec>, - shuffle_1: Vec>, -} - -impl Circuit for MyCircuit { - // Since we are using a single chip for everything, we can just reuse its config. - type Config = ShuffleConfig; - type FloorPlanner = SimpleFloorPlanner; - #[cfg(feature = "circuit-params")] - type Params = (); - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let input_0 = meta.advice_column(); - let input_1 = meta.fixed_column(); - let shuffle_0 = meta.advice_column(); - let shuffle_1 = meta.advice_column(); - ShuffleChip::configure(meta, input_0, input_1, shuffle_0, shuffle_1) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let ch = ShuffleChip::::construct(config); - layouter.assign_region( - || "load inputs", - |mut region| { - for (i, (input_0, input_1)) in - self.input_0.iter().zip(self.input_1.iter()).enumerate() - { - region.assign_advice(|| "input_0", ch.config.input_0, i, || *input_0)?; - region.assign_fixed( - || "input_1", - ch.config.input_1, - i, - || Value::known(*input_1), - )?; - ch.config.s_input.enable(&mut region, i)?; - } - Ok(()) - }, - )?; - layouter.assign_region( - || "load shuffles", - |mut region| { - for (i, (shuffle_0, shuffle_1)) in - self.shuffle_0.iter().zip(self.shuffle_1.iter()).enumerate() - { - region.assign_advice(|| "shuffle_0", ch.config.shuffle_0, i, || *shuffle_0)?; - region.assign_advice(|| "shuffle_1", ch.config.shuffle_1, i, || *shuffle_1)?; - ch.config.s_shuffle.enable(&mut region, i)?; - } - Ok(()) - }, - )?; - Ok(()) - } -} - -fn test_prover(k: u32, circuit: MyCircuit, expected: bool) -where - C::Scalar: FromUniformBytes<64>, -{ - let params = ParamsIPA::::new(k); - let vk = keygen_vk(¶ms, &circuit).unwrap(); - let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); - - let proof = { - let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - - create_proof::, ProverIPA, _, _, _, _>( - ¶ms, - &pk, - &[circuit], - &[&[]], - OsRng, - &mut transcript, - ) - .expect("proof generation should not fail"); - - transcript.finalize() - }; - - let accepted = { - let strategy = AccumulatorStrategy::new(¶ms); - let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - - verify_proof::, VerifierIPA, _, _, _>( - ¶ms, - pk.get_vk(), - strategy, - &[&[]], - &mut transcript, - ) - .map(|strategy| strategy.finalize()) - .unwrap_or_default() - }; - - assert_eq!(accepted, expected); -} - -fn main() { - use halo2_proofs::dev::MockProver; - use halo2curves::pasta::Fp; - const K: u32 = 4; - let input_0 = [1, 2, 4, 1] - .map(|e: u64| Value::known(Fp::from(e))) - .to_vec(); - let input_1 = [10, 20, 40, 10].map(Fp::from).to_vec(); - let shuffle_0 = [4, 1, 1, 2] - .map(|e: u64| Value::known(Fp::from(e))) - .to_vec(); - let shuffle_1 = [40, 10, 10, 20] - .map(|e: u64| Value::known(Fp::from(e))) - .to_vec(); - let circuit = MyCircuit { - input_0, - input_1, - shuffle_0, - shuffle_1, - }; - let prover = MockProver::run(K, &circuit, vec![]).unwrap(); - prover.assert_satisfied(); - test_prover::(K, circuit, true); -} diff --git a/src/dev.rs b/src/dev.rs index 7a3aca10cc..7dc2cbd597 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -1050,66 +1050,6 @@ impl + Ord> MockProver { .collect::>() }); - let shuffle_errors = - self.cs - .shuffles - .iter() - .enumerate() - .flat_map(|(shuffle_index, shuffle)| { - assert!(shuffle.shuffle_expressions.len() == shuffle.input_expressions.len()); - assert!(self.usable_rows.end > 0); - - let mut shuffle_rows: Vec>> = self - .usable_rows - .clone() - .map(|row| { - let t = shuffle - .shuffle_expressions - .iter() - .map(move |c| load(c, row)) - .collect(); - t - }) - .collect(); - shuffle_rows.sort(); - - let mut input_rows: Vec<(Vec>, usize)> = self - .usable_rows - .clone() - .map(|input_row| { - let t = shuffle - .input_expressions - .iter() - .map(move |c| load(c, input_row)) - .collect(); - - (t, input_row) - }) - .collect(); - input_rows.sort(); - - input_rows - .iter() - .zip(shuffle_rows.iter()) - .filter_map(|((input_value, row), shuffle_value)| { - if shuffle_value != input_value { - Some(VerifyFailure::Shuffle { - name: shuffle.name.clone(), - shuffle_index, - location: FailureLocation::find_expressions( - &self.cs, - &self.regions, - *row, - shuffle.input_expressions.iter(), - ), - }) - } else { - None - } - }) - .collect::>() - }); - let mapping = self.permutation.mapping(); // Check that permutations preserve the original values of the cells. let perm_errors = { @@ -1163,7 +1103,6 @@ impl + Ord> MockProver { .chain(gate_errors) .chain(lookup_errors) .chain(perm_errors) - .chain(shuffle_errors) .collect(); if errors.is_empty() { Ok(()) diff --git a/src/dev/failure.rs b/src/dev/failure.rs index f9f5c27ded..0abdbe8639 100644 --- a/src/dev/failure.rs +++ b/src/dev/failure.rs @@ -192,28 +192,6 @@ pub enum VerifyFailure { /// lookup is active on a row adjacent to an unrelated region. location: FailureLocation, }, - /// A shuffle input did not exist in its corresponding map. - Shuffle { - /// The name of the lookup that is not satisfied. - name: String, - /// The index of the lookup that is not satisfied. These indices are assigned in - /// the order in which `ConstraintSystem::lookup` is called during - /// `Circuit::configure`. - shuffle_index: usize, - /// The location at which the lookup is not satisfied. - /// - /// `FailureLocation::InRegion` is most common, and may be due to the intentional - /// use of a lookup (if its inputs are conditional on a complex selector), or an - /// unintentional lookup constraint that overlaps the region (indicating that the - /// lookup's inputs should be made conditional). - /// - /// `FailureLocation::OutsideRegion` is uncommon, and could mean that: - /// - The input expressions do not correctly constrain a default value that exists - /// in the table when the lookup is not being used. - /// - The input expressions use a column queried at a non-zero `Rotation`, and the - /// lookup is active on a row adjacent to an unrelated region. - location: FailureLocation, - }, /// A permutation did not preserve the original value of a cell. Permutation { /// The column in which this permutation is not satisfied. @@ -287,16 +265,6 @@ impl fmt::Display for VerifyFailure { "Lookup {name}(index: {lookup_index}) is not satisfied {location}", ) } - Self::Shuffle { - name, - shuffle_index, - location, - } => { - write!( - f, - "Shuffle {name}(index: {shuffle_index}) is not satisfied {location}" - ) - } Self::Permutation { column, location } => { write!( f, @@ -667,171 +635,6 @@ fn render_lookup( } } -fn render_shuffle( - prover: &MockProver, - name: &str, - shuffle_index: usize, - location: &FailureLocation, -) { - let n = prover.n as i32; - let cs = &prover.cs; - let shuffle = &cs.shuffles[shuffle_index]; - - // Get the absolute row on which the shuffle's inputs are being queried, so we can - // fetch the input values. - let row = match location { - FailureLocation::InRegion { region, offset } => { - prover.regions[region.index].rows.unwrap().0 + offset - } - FailureLocation::OutsideRegion { row } => *row, - } as i32; - - let shuffle_columns = shuffle.shuffle_expressions.iter().map(|expr| { - expr.evaluate( - &|f| format! {"Const: {f:#?}"}, - &|s| format! {"S{}", s.0}, - &|query| { - format!( - "{:?}", - prover - .cs - .general_column_annotations - .get(&metadata::Column::from((Any::Fixed, query.column_index))) - .cloned() - .unwrap_or_else(|| format!("F{}", query.column_index())) - ) - }, - &|query| { - format!( - "{:?}", - prover - .cs - .general_column_annotations - .get(&metadata::Column::from((Any::advice(), query.column_index))) - .cloned() - .unwrap_or_else(|| format!("A{}", query.column_index())) - ) - }, - &|query| { - format!( - "{:?}", - prover - .cs - .general_column_annotations - .get(&metadata::Column::from((Any::Instance, query.column_index))) - .cloned() - .unwrap_or_else(|| format!("I{}", query.column_index())) - ) - }, - &|challenge| format! {"C{}", challenge.index()}, - &|query| format! {"-{query}"}, - &|a, b| format! {"{a} + {b}"}, - &|a, b| format! {"{a} * {b}"}, - &|a, b| format! {"{a} * {b:?}"}, - ) - }); - - fn cell_value<'a, F: Field, Q: Into + Copy>( - load: impl Fn(Q) -> Value + 'a, - ) -> impl Fn(Q) -> BTreeMap + 'a { - move |query| { - let AnyQuery { - column_type, - column_index, - rotation, - .. - } = query.into(); - Some(( - ((column_type, column_index).into(), rotation.0).into(), - match load(query) { - Value::Real(v) => util::format_value(v), - Value::Poison => unreachable!(), - }, - )) - .into_iter() - .collect() - } - } - - eprintln!("error: input does not exist in shuffle"); - eprint!(" ("); - for i in 0..shuffle.input_expressions.len() { - eprint!("{}L{}", if i == 0 { "" } else { ", " }, i); - } - eprint!(") <-> ("); - for (i, column) in shuffle_columns.enumerate() { - eprint!("{}{}", if i == 0 { "" } else { ", " }, column); - } - eprintln!(")"); - - eprintln!(); - eprintln!(" Shuffle '{name}' inputs:"); - for (i, input) in shuffle.input_expressions.iter().enumerate() { - // Fetch the cell values (since we don't store them in VerifyFailure::Shuffle). - let cell_values = input.evaluate( - &|_| BTreeMap::default(), - &|_| panic!("virtual selectors are removed during optimization"), - &cell_value(&util::load(n, row, &cs.fixed_queries, &prover.fixed)), - &cell_value(&util::load(n, row, &cs.advice_queries, &prover.advice)), - &cell_value(&util::load_instance( - n, - row, - &cs.instance_queries, - &prover.instance, - )), - &|_| BTreeMap::default(), - &|a| a, - &|mut a, mut b| { - a.append(&mut b); - a - }, - &|mut a, mut b| { - a.append(&mut b); - a - }, - &|a, _| a, - ); - - // Collect the necessary rendering information: - // - The columns involved in this constraint. - // - How many cells are in each column. - // - The grid of cell values, indexed by rotation. - let mut columns = BTreeMap::::default(); - let mut layout = BTreeMap::>::default(); - for (i, (cell, _)) in cell_values.iter().enumerate() { - *columns.entry(cell.column).or_default() += 1; - layout - .entry(cell.rotation) - .or_default() - .entry(cell.column) - .or_insert(format!("x{i}")); - } - - if i != 0 { - eprintln!(); - } - eprintln!( - " Sh{} = {}", - i, - emitter::expression_to_string(input, &layout) - ); - eprintln!(" ^"); - - emitter::render_cell_layout(" | ", location, &columns, &layout, |_, rotation| { - if rotation == 0 { - eprint!(" <--{{ Shuffle '{name}' inputs queried here"); - } - }); - - // Print the map from local variables to assigned values. - eprintln!(" |"); - eprintln!(" | Assigned cell values:"); - for (i, (_, value)) in cell_values.iter().enumerate() { - eprintln!(" | x{i} = {value}"); - } - } -} - impl VerifyFailure { /// Emits this failure in pretty-printed format to stderr. pub(super) fn emit(&self, prover: &MockProver) { @@ -862,11 +665,6 @@ impl VerifyFailure { lookup_index, location, } => render_lookup(prover, name, *lookup_index, location), - Self::Shuffle { - name, - shuffle_index, - location, - } => render_shuffle(prover, name, *shuffle_index, location), _ => eprintln!("{self}"), } } diff --git a/src/plonk.rs b/src/plonk.rs index 78bfc21501..67de4fb163 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -27,7 +27,6 @@ mod evaluation; mod keygen; mod lookup; pub mod permutation; -mod shuffle; mod vanishing; mod prover; diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index 5107554186..85425cc8e4 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -1,4 +1,4 @@ -use super::{lookup, permutation, shuffle, Assigned, Error}; +use super::{lookup, permutation, Assigned, Error}; use crate::circuit::layouter::SyncDeps; use crate::dev::metadata; use crate::{ @@ -1584,10 +1584,6 @@ pub struct ConstraintSystem { // input expressions and a sequence of table expressions involved in the lookup. pub(crate) lookups: Vec>, - // Vector of shuffle arguments, where each corresponds to a sequence of - // input expressions and a sequence of shuffle expressions involved in the shuffle. - pub(crate) shuffles: Vec>, - // List of indexes of Fixed columns which are associated to a circuit-general Column tied to their annotation. pub(crate) general_column_annotations: HashMap, @@ -1614,7 +1610,6 @@ pub struct PinnedConstraintSystem<'a, F: Field> { fixed_queries: &'a Vec<(Column, Rotation)>, permutation: &'a permutation::Argument, lookups: &'a Vec>, - shuffles: &'a Vec>, constants: &'a Vec>, minimum_degree: &'a Option, } @@ -1641,9 +1636,6 @@ impl<'a, F: Field> std::fmt::Debug for PinnedConstraintSystem<'a, F> { .field("fixed_queries", self.fixed_queries) .field("permutation", self.permutation) .field("lookups", self.lookups); - if !self.shuffles.is_empty() { - debug_struct.field("shuffles", self.shuffles); - } debug_struct .field("constants", self.constants) .field("minimum_degree", self.minimum_degree); @@ -1680,7 +1672,6 @@ impl Default for ConstraintSystem { instance_queries: Vec::new(), permutation: permutation::Argument::new(), lookups: Vec::new(), - shuffles: Vec::new(), general_column_annotations: HashMap::new(), constants: vec![], minimum_degree: None, @@ -1707,7 +1698,6 @@ impl ConstraintSystem { instance_queries: &self.instance_queries, permutation: &self.permutation, lookups: &self.lookups, - shuffles: &self.shuffles, constants: &self.constants, minimum_degree: &self.minimum_degree, } @@ -1794,29 +1784,6 @@ impl ConstraintSystem { index } - /// Add a shuffle argument for some input expressions and table expressions. - pub fn shuffle>( - &mut self, - name: S, - shuffle_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, Expression)>, - ) -> usize { - let mut cells = VirtualCells::new(self); - let shuffle_map = shuffle_map(&mut cells) - .into_iter() - .map(|(mut input, mut table)| { - input.query_cells(&mut cells); - table.query_cells(&mut cells); - (input, table) - }) - .collect(); - let index = self.shuffles.len(); - - self.shuffles - .push(shuffle::Argument::new(name.as_ref(), shuffle_map)); - - index - } - fn query_fixed_index(&mut self, column: Column, at: Rotation) -> usize { // Return existing query, if it exists for (index, fixed_query) in self.fixed_queries.iter().enumerate() { @@ -2115,15 +2082,6 @@ impl ConstraintSystem { }) { replace_selectors(expr, selector_replacements, true); } - - for expr in self.shuffles.iter_mut().flat_map(|shuffle| { - shuffle - .input_expressions - .iter_mut() - .chain(shuffle.shuffle_expressions.iter_mut()) - }) { - replace_selectors(expr, selector_replacements, true); - } } /// Allocate a new (simple) selector. Simple selectors cannot be added to @@ -2318,17 +2276,6 @@ impl ConstraintSystem { .unwrap_or(1), ); - // The lookup argument also serves alongside the gates and must be accounted - // for. - degree = std::cmp::max( - degree, - self.shuffles - .iter() - .map(|l| l.required_degree()) - .max() - .unwrap_or(1), - ); - // Account for each gate to ensure our quotient polynomial is the // correct degree and that our extended domain is the right size. degree = std::cmp::max( @@ -2457,11 +2404,6 @@ impl ConstraintSystem { &self.lookups } - /// Returns shuffle arguments - pub fn shuffles(&self) -> &Vec> { - &self.shuffles - } - /// Returns constants pub fn constants(&self) -> &Vec> { &self.constants diff --git a/src/plonk/evaluation.rs b/src/plonk/evaluation.rs index 431c487c7e..b32547a2f9 100644 --- a/src/plonk/evaluation.rs +++ b/src/plonk/evaluation.rs @@ -7,7 +7,7 @@ use crate::{ }; use group::ff::{Field, PrimeField, WithSmallOrderMulGroup}; -use super::{shuffle, ConstraintSystem, Expression}; +use super::{ConstraintSystem, Expression}; /// Return the index in the polynomial of size `isize` after rotation `rot`. fn get_rotation_idx(idx: usize, rot: i32, rot_scale: i32, isize: i32) -> usize { @@ -258,39 +258,6 @@ impl Evaluator { ev.lookups.push(graph); } - // Shuffles - for shuffle in cs.shuffles.iter() { - let evaluate_lc = |expressions: &Vec>, graph: &mut GraphEvaluator| { - let parts = expressions - .iter() - .map(|expr| graph.add_expression(expr)) - .collect(); - graph.add_calculation(Calculation::Horner( - ValueSource::Constant(0), - parts, - ValueSource::Theta(), - )) - }; - - let mut graph_input = GraphEvaluator::default(); - let compressed_input_coset = evaluate_lc(&shuffle.input_expressions, &mut graph_input); - let _ = graph_input.add_calculation(Calculation::Add( - compressed_input_coset, - ValueSource::Gamma(), - )); - - let mut graph_shuffle = GraphEvaluator::default(); - let compressed_shuffle_coset = - evaluate_lc(&shuffle.shuffle_expressions, &mut graph_shuffle); - let _ = graph_shuffle.add_calculation(Calculation::Add( - compressed_shuffle_coset, - ValueSource::Gamma(), - )); - - ev.shuffles.push(graph_input); - ev.shuffles.push(graph_shuffle); - } - ev } @@ -307,7 +274,6 @@ impl Evaluator { gamma: C::ScalarExt, theta: C::ScalarExt, lookups: &[Vec>], - shuffles: &[Vec>], permutations: &[permutation::prover::Committed], ) -> Polynomial { let domain = &pk.vk.domain; @@ -346,11 +312,10 @@ impl Evaluator { // Core expression evaluations let num_threads = multicore::current_num_threads(); - for ((((advice, instance), lookups), shuffles), permutation) in advice + for (((advice, instance), lookups), permutation) in advice .iter() .zip(instance.iter()) .zip(lookups.iter()) - .zip(shuffles.iter()) .zip(permutations.iter()) { // Custom gates @@ -538,68 +503,6 @@ impl Evaluator { } }); } - - // Shuffle constraints - for (n, shuffle) in shuffles.iter().enumerate() { - let product_coset = pk.vk.domain.coeff_to_extended(shuffle.product_poly.clone()); - - // Shuffle constraints - parallelize(&mut values, |values, start| { - let input_evaluator = &self.shuffles[2 * n]; - let shuffle_evaluator = &self.shuffles[2 * n + 1]; - let mut eval_data_input = shuffle_evaluator.instance(); - let mut eval_data_shuffle = shuffle_evaluator.instance(); - for (i, value) in values.iter_mut().enumerate() { - let idx = start + i; - - let input_value = input_evaluator.evaluate( - &mut eval_data_input, - fixed, - advice, - instance, - challenges, - &beta, - &gamma, - &theta, - &y, - &C::ScalarExt::ZERO, - idx, - rot_scale, - isize, - ); - - let shuffle_value = shuffle_evaluator.evaluate( - &mut eval_data_shuffle, - fixed, - advice, - instance, - challenges, - &beta, - &gamma, - &theta, - &y, - &C::ScalarExt::ZERO, - idx, - rot_scale, - isize, - ); - - let r_next = get_rotation_idx(idx, 1, rot_scale, isize); - - // l_0(X) * (1 - z(X)) = 0 - *value = *value * y + ((one - product_coset[idx]) * l0[idx]); - // l_last(X) * (z(X)^2 - z(X)) = 0 - *value = *value * y - + ((product_coset[idx] * product_coset[idx] - product_coset[idx]) - * l_last[idx]); - // (1 - (l_last(X) + l_blind(X))) * (z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma)) = 0 - *value = *value * y - + l_active_row[idx] - * (product_coset[r_next] * shuffle_value - - product_coset[idx] * input_value) - } - }); - } } values } diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index cd0d7306a9..312e7a0cf3 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -11,8 +11,8 @@ use super::{ Advice, Any, Assignment, Challenge, Circuit, Column, ConstraintSystem, Fixed, FloorPlanner, Instance, Selector, }, - lookup, permutation, shuffle, vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, - ChallengeX, ChallengeY, Error, ProvingKey, + lookup, permutation, vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, + ChallengeY, Error, ProvingKey, }; use crate::{ @@ -488,34 +488,6 @@ where }) .collect::, _>>()?; - let shuffles: Vec>> = instance - .iter() - .zip(advice.iter()) - .map(|(instance, advice)| -> Result, _> { - // Compress expressions for each shuffle - pk.vk - .cs - .shuffles - .iter() - .map(|shuffle| { - shuffle.commit_product( - pk, - params, - domain, - theta, - gamma, - &advice.advice_polys, - &pk.fixed_values, - &instance.instance_values, - &challenges, - &mut rng, - transcript, - ) - }) - .collect::, _>>() - }) - .collect::, _>>()?; - // Commit to the vanishing argument's random polynomial for blinding h(x_3) let vanishing = vanishing::Argument::commit(params, domain, &mut rng, transcript)?; @@ -558,7 +530,6 @@ where *gamma, *theta, &lookups, - &shuffles, &permutations, ); @@ -646,24 +617,12 @@ where }) .collect::, _>>()?; - // Evaluate the shuffles, if any, at omega^i x. - let shuffles: Vec>> = shuffles - .into_iter() - .map(|shuffles| -> Result, _> { - shuffles - .into_iter() - .map(|p| p.evaluate(pk, x, transcript)) - .collect::, _>>() - }) - .collect::, _>>()?; - let instances = instance .iter() .zip(advice.iter()) .zip(permutations.iter()) .zip(lookups.iter()) - .zip(shuffles.iter()) - .flat_map(|((((instance, advice), permutation), lookups), shuffles)| { + .flat_map(|(((instance, advice), permutation), lookups)| { iter::empty() .chain( P::QUERY_INSTANCE @@ -690,7 +649,6 @@ where ) .chain(permutation.open(pk, x)) .chain(lookups.iter().flat_map(move |p| p.open(pk, x))) - .chain(shuffles.iter().flat_map(move |p| p.open(pk, x))) }) .chain( pk.vk diff --git a/src/plonk/shuffle.rs b/src/plonk/shuffle.rs deleted file mode 100644 index e32353c710..0000000000 --- a/src/plonk/shuffle.rs +++ /dev/null @@ -1,67 +0,0 @@ -use super::circuit::Expression; -use ff::Field; -use std::fmt::{self, Debug}; - -pub(crate) mod prover; -pub(crate) mod verifier; - -#[derive(Clone)] -pub struct Argument { - pub(crate) name: String, - pub(crate) input_expressions: Vec>, - pub(crate) shuffle_expressions: Vec>, -} - -impl Debug for Argument { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Argument") - .field("input_expressions", &self.input_expressions) - .field("shuffle_expressions", &self.shuffle_expressions) - .finish() - } -} - -impl Argument { - /// Constructs a new shuffle argument. - /// - /// `shuffle` is a sequence of `(input, shuffle)` tuples. - pub fn new>(name: S, shuffle: Vec<(Expression, Expression)>) -> Self { - let (input_expressions, shuffle_expressions) = shuffle.into_iter().unzip(); - Argument { - name: name.as_ref().to_string(), - input_expressions, - shuffle_expressions, - } - } - - pub(crate) fn required_degree(&self) -> usize { - assert_eq!(self.input_expressions.len(), self.shuffle_expressions.len()); - - let mut input_degree = 1; - for expr in self.input_expressions.iter() { - input_degree = std::cmp::max(input_degree, expr.degree()); - } - let mut shuffle_degree = 1; - for expr in self.shuffle_expressions.iter() { - shuffle_degree = std::cmp::max(shuffle_degree, expr.degree()); - } - - // (1 - (l_last + l_blind)) (z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma)) - std::cmp::max(2 + shuffle_degree, 2 + input_degree) - } - - /// Returns input of this argument - pub fn input_expressions(&self) -> &Vec> { - &self.input_expressions - } - - /// Returns table of this argument - pub fn shuffle_expressions(&self) -> &Vec> { - &self.shuffle_expressions - } - - /// Returns name of this argument - pub fn name(&self) -> &str { - &self.name - } -} diff --git a/src/plonk/shuffle/prover.rs b/src/plonk/shuffle/prover.rs deleted file mode 100644 index fd30436a47..0000000000 --- a/src/plonk/shuffle/prover.rs +++ /dev/null @@ -1,250 +0,0 @@ -use super::super::{ - circuit::Expression, ChallengeGamma, ChallengeTheta, ChallengeX, Error, ProvingKey, -}; -use super::Argument; -use crate::plonk::evaluation::evaluate; -use crate::{ - arithmetic::{eval_polynomial, parallelize, CurveAffine}, - poly::{ - commitment::{Blind, Params}, - Coeff, EvaluationDomain, LagrangeCoeff, Polynomial, ProverQuery, Rotation, - }, - transcript::{EncodedChallenge, TranscriptWrite}, -}; -use ff::WithSmallOrderMulGroup; -use group::{ff::BatchInvert, Curve}; -use rand_core::RngCore; -use std::{ - iter, - ops::{Mul, MulAssign}, -}; - -#[derive(Debug)] -struct Compressed { - input_expression: Polynomial, - shuffle_expression: Polynomial, -} - -#[derive(Debug)] -pub(in crate::plonk) struct Committed { - pub(in crate::plonk) product_poly: Polynomial, - product_blind: Blind, -} - -pub(in crate::plonk) struct Evaluated { - constructed: Committed, -} - -impl> Argument { - /// Given a Shuffle with input expressions [A_0, A_1, ..., A_{m-1}] and table expressions - /// [S_0, S_1, ..., S_{m-1}], this method - /// - constructs A_compressed = \theta^{m-1} A_0 + theta^{m-2} A_1 + ... + \theta A_{m-2} + A_{m-1} - /// and S_compressed = \theta^{m-1} S_0 + theta^{m-2} S_1 + ... + \theta S_{m-2} + S_{m-1}, - #[allow(clippy::too_many_arguments)] - fn compress<'a, 'params: 'a, C, P: Params<'params, C>>( - &self, - pk: &ProvingKey, - params: &P, - domain: &EvaluationDomain, - theta: ChallengeTheta, - advice_values: &'a [Polynomial], - fixed_values: &'a [Polynomial], - instance_values: &'a [Polynomial], - challenges: &'a [C::Scalar], - ) -> Compressed - where - C: CurveAffine, - C::Curve: Mul + MulAssign, - { - // Closure to get values of expressions and compress them - let compress_expressions = |expressions: &[Expression]| { - let compressed_expression = expressions - .iter() - .map(|expression| { - pk.vk.domain.lagrange_from_vec(evaluate( - expression, - params.n() as usize, - 1, - fixed_values, - advice_values, - instance_values, - challenges, - )) - }) - .fold(domain.empty_lagrange(), |acc, expression| { - acc * *theta + &expression - }); - compressed_expression - }; - - // Get values of input expressions involved in the shuffle and compress them - let input_expression = compress_expressions(&self.input_expressions); - - // Get values of table expressions involved in the shuffle and compress them - let shuffle_expression = compress_expressions(&self.shuffle_expressions); - - Compressed { - input_expression, - shuffle_expression, - } - } - - /// Given a Shuffle with input expressions and table expressions this method - /// constructs the grand product polynomial over the shuffle. - /// The grand product polynomial is used to populate the Product struct. - /// The Product struct is added to the Shuffle and finally returned by the method. - #[allow(clippy::too_many_arguments)] - pub(in crate::plonk) fn commit_product< - 'a, - 'params: 'a, - C, - P: Params<'params, C>, - E: EncodedChallenge, - R: RngCore, - T: TranscriptWrite, - >( - &self, - pk: &ProvingKey, - params: &P, - domain: &EvaluationDomain, - theta: ChallengeTheta, - gamma: ChallengeGamma, - advice_values: &'a [Polynomial], - fixed_values: &'a [Polynomial], - instance_values: &'a [Polynomial], - challenges: &'a [C::Scalar], - mut rng: R, - transcript: &mut T, - ) -> Result, Error> - where - C: CurveAffine, - C::Curve: Mul + MulAssign, - { - let compressed = self.compress( - pk, - params, - domain, - theta, - advice_values, - fixed_values, - instance_values, - challenges, - ); - - let blinding_factors = pk.vk.cs.blinding_factors(); - - let mut shuffle_product = vec![C::Scalar::ZERO; params.n() as usize]; - parallelize(&mut shuffle_product, |shuffle_product, start| { - for (shuffle_product, shuffle_value) in shuffle_product - .iter_mut() - .zip(compressed.shuffle_expression[start..].iter()) - { - *shuffle_product = *gamma + shuffle_value; - } - }); - - shuffle_product.iter_mut().batch_invert(); - - parallelize(&mut shuffle_product, |product, start| { - for (i, product) in product.iter_mut().enumerate() { - let i = i + start; - *product *= &(*gamma + compressed.input_expression[i]); - } - }); - - // Compute the evaluations of the shuffle product polynomial - // over our domain, starting with z[0] = 1 - let z = iter::once(C::Scalar::ONE) - .chain(shuffle_product) - .scan(C::Scalar::ONE, |state, cur| { - *state *= &cur; - Some(*state) - }) - // Take all rows including the "last" row which should - // be a boolean (and ideally 1, else soundness is broken) - .take(params.n() as usize - blinding_factors) - // Chain random blinding factors. - .chain((0..blinding_factors).map(|_| C::Scalar::random(&mut rng))) - .collect::>(); - assert_eq!(z.len(), params.n() as usize); - let z = pk.vk.domain.lagrange_from_vec(z); - - #[cfg(feature = "sanity-checks")] - { - // While in Lagrange basis, check that product is correctly constructed - let u = (params.n() as usize) - (blinding_factors + 1); - assert_eq!(z[0], C::Scalar::ONE); - for i in 0..u { - let mut left = z[i + 1]; - let input_value = &compressed.input_expression[i]; - let shuffle_value = &compressed.shuffle_expression[i]; - left *= &(*gamma + shuffle_value); - let mut right = z[i]; - right *= &(*gamma + input_value); - assert_eq!(left, right); - } - assert_eq!(z[u], C::Scalar::ONE); - } - - let product_blind = Blind(C::Scalar::random(rng)); - let product_commitment = params.commit_lagrange(&z, product_blind).to_affine(); - let z = pk.vk.domain.lagrange_to_coeff(z); - - // Hash product commitment - transcript.write_point(product_commitment)?; - - Ok(Committed:: { - product_poly: z, - product_blind, - }) - } -} - -impl Committed { - pub(in crate::plonk) fn evaluate, T: TranscriptWrite>( - self, - pk: &ProvingKey, - x: ChallengeX, - transcript: &mut T, - ) -> Result, Error> { - let domain = &pk.vk.domain; - let x_next = domain.rotate_omega(*x, Rotation::next()); - - let product_eval = eval_polynomial(&self.product_poly, *x); - let product_next_eval = eval_polynomial(&self.product_poly, x_next); - - // Hash each advice evaluation - for eval in iter::empty() - .chain(Some(product_eval)) - .chain(Some(product_next_eval)) - { - transcript.write_scalar(eval)?; - } - - Ok(Evaluated { constructed: self }) - } -} - -impl Evaluated { - pub(in crate::plonk) fn open<'a>( - &'a self, - pk: &'a ProvingKey, - x: ChallengeX, - ) -> impl Iterator> + Clone { - let x_next = pk.vk.domain.rotate_omega(*x, Rotation::next()); - - iter::empty() - // Open shuffle product commitments at x - .chain(Some(ProverQuery { - point: *x, - poly: &self.constructed.product_poly, - blind: self.constructed.product_blind, - })) - // Open shuffle product commitments at x_next - .chain(Some(ProverQuery { - point: x_next, - poly: &self.constructed.product_poly, - blind: self.constructed.product_blind, - })) - } -} diff --git a/src/plonk/shuffle/verifier.rs b/src/plonk/shuffle/verifier.rs deleted file mode 100644 index 379cc5c8a1..0000000000 --- a/src/plonk/shuffle/verifier.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::iter; - -use super::super::{circuit::Expression, ChallengeGamma, ChallengeTheta, ChallengeX}; -use super::Argument; -use crate::{ - arithmetic::CurveAffine, - plonk::{Error, VerifyingKey}, - poly::{commitment::MSM, Rotation, VerifierQuery}, - transcript::{EncodedChallenge, TranscriptRead}, -}; -use ff::Field; - -pub struct Committed { - product_commitment: C, -} - -pub struct Evaluated { - committed: Committed, - product_eval: C::Scalar, - product_next_eval: C::Scalar, -} - -impl Argument { - pub(in crate::plonk) fn read_product_commitment< - C: CurveAffine, - E: EncodedChallenge, - T: TranscriptRead, - >( - &self, - transcript: &mut T, - ) -> Result, Error> { - let product_commitment = transcript.read_point()?; - - Ok(Committed { product_commitment }) - } -} - -impl Committed { - pub(crate) fn evaluate, T: TranscriptRead>( - self, - transcript: &mut T, - ) -> Result, Error> { - let product_eval = transcript.read_scalar()?; - let product_next_eval = transcript.read_scalar()?; - - Ok(Evaluated { - committed: self, - product_eval, - product_next_eval, - }) - } -} - -impl Evaluated { - #[allow(clippy::too_many_arguments)] - pub(in crate::plonk) fn expressions<'a>( - &'a self, - l_0: C::Scalar, - l_last: C::Scalar, - l_blind: C::Scalar, - argument: &'a Argument, - theta: ChallengeTheta, - gamma: ChallengeGamma, - advice_evals: &[C::Scalar], - fixed_evals: &[C::Scalar], - instance_evals: &[C::Scalar], - challenges: &[C::Scalar], - ) -> impl Iterator + 'a { - let active_rows = C::Scalar::ONE - (l_last + l_blind); - - let product_expression = || { - // z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma) - let compress_expressions = |expressions: &[Expression]| { - expressions - .iter() - .map(|expression| { - expression.evaluate( - &|scalar| scalar, - &|_| panic!("virtual selectors are removed during optimization"), - &|query| fixed_evals[query.index.unwrap()], - &|query| advice_evals[query.index.unwrap()], - &|query| instance_evals[query.index.unwrap()], - &|challenge| challenges[challenge.index()], - &|a| -a, - &|a, b| a + &b, - &|a, b| a * &b, - &|a, scalar| a * &scalar, - ) - }) - .fold(C::Scalar::ZERO, |acc, eval| acc * &*theta + &eval) - }; - // z(\omega X) (s(X) + \gamma) - let left = self.product_next_eval - * &(compress_expressions(&argument.shuffle_expressions) + &*gamma); - // z(X) (a(X) + \gamma) - let right = - self.product_eval * &(compress_expressions(&argument.input_expressions) + &*gamma); - - (left - &right) * &active_rows - }; - - std::iter::empty() - .chain( - // l_0(X) * (1 - z'(X)) = 0 - Some(l_0 * &(C::Scalar::ONE - &self.product_eval)), - ) - .chain( - // l_last(X) * (z(X)^2 - z(X)) = 0 - Some(l_last * &(self.product_eval.square() - &self.product_eval)), - ) - .chain( - // (1 - (l_last(X) + l_blind(X))) * ( z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma)) - Some(product_expression()), - ) - } - - pub(in crate::plonk) fn queries<'r, M: MSM + 'r>( - &'r self, - vk: &'r VerifyingKey, - x: ChallengeX, - ) -> impl Iterator> + Clone { - let x_next = vk.domain.rotate_omega(*x, Rotation::next()); - - iter::empty() - // Open shuffle product commitment at x - .chain(Some(VerifierQuery::new_commitment( - &self.committed.product_commitment, - *x, - self.product_eval, - ))) - // Open shuffle product commitment at \omega x - .chain(Some(VerifierQuery::new_commitment( - &self.committed.product_commitment, - x_next, - self.product_next_eval, - ))) - } -} diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 76675bcdfa..068c6a93e9 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -157,17 +157,6 @@ where }) .collect::, _>>()?; - let shuffles_committed = (0..num_proofs) - .map(|_| -> Result, _> { - // Hash each shuffle product commitment - vk.cs - .shuffles - .iter() - .map(|argument| argument.read_product_commitment(transcript)) - .collect::, _>>() - }) - .collect::, _>>()?; - let vanishing = vanishing::Argument::read_commitments_before_y(transcript)?; // Sample y challenge, which keeps the gates linearly independent. @@ -250,16 +239,6 @@ where }) .collect::, _>>()?; - let shuffles_evaluated = shuffles_committed - .into_iter() - .map(|shuffles| -> Result, _> { - shuffles - .into_iter() - .map(|shuffle| shuffle.evaluate(transcript)) - .collect::, _>>() - }) - .collect::, _>>()?; - // This check ensures the circuit is satisfied so long as the polynomial // commitments open to the correct values. let vanishing = { @@ -283,80 +262,59 @@ where .zip(instance_evals.iter()) .zip(permutations_evaluated.iter()) .zip(lookups_evaluated.iter()) - .zip(shuffles_evaluated.iter()) - .flat_map( - |((((advice_evals, instance_evals), permutation), lookups), shuffles)| { - let challenges = &challenges; - let fixed_evals = &fixed_evals; - std::iter::empty() - // Evaluate the circuit using the custom gates provided - .chain(vk.cs.gates.iter().flat_map(move |gate| { - gate.polynomials().iter().map(move |poly| { - poly.evaluate( - &|scalar| scalar, - &|_| { - panic!("virtual selectors are removed during optimization") - }, - &|query| fixed_evals[query.index.unwrap()], - &|query| advice_evals[query.index.unwrap()], - &|query| instance_evals[query.index.unwrap()], - &|challenge| challenges[challenge.index()], - &|a| -a, - &|a, b| a + &b, - &|a, b| a * &b, - &|a, scalar| a * &scalar, - ) - }) - })) - .chain(permutation.expressions( - vk, - &vk.cs.permutation, - &permutations_common, - advice_evals, - fixed_evals, - instance_evals, - l_0, - l_last, - l_blind, - beta, - gamma, - x, - )) - .chain(lookups.iter().zip(vk.cs.lookups.iter()).flat_map( - move |(p, argument)| { - p.expressions( - l_0, - l_last, - l_blind, - argument, - theta, - beta, - gamma, - advice_evals, - fixed_evals, - instance_evals, - challenges, - ) - }, - )) - .chain(shuffles.iter().zip(vk.cs.shuffles.iter()).flat_map( - move |(p, argument)| { - p.expressions( - l_0, - l_last, - l_blind, - argument, - theta, - gamma, - advice_evals, - fixed_evals, - instance_evals, - challenges, - ) - }, - )) - }, - ); + .flat_map(|(((advice_evals, instance_evals), permutation), lookups)| { + let challenges = &challenges; + let fixed_evals = &fixed_evals; + std::iter::empty() + // Evaluate the circuit using the custom gates provided + .chain(vk.cs.gates.iter().flat_map(move |gate| { + gate.polynomials().iter().map(move |poly| { + poly.evaluate( + &|scalar| scalar, + &|_| panic!("virtual selectors are removed during optimization"), + &|query| fixed_evals[query.index.unwrap()], + &|query| advice_evals[query.index.unwrap()], + &|query| instance_evals[query.index.unwrap()], + &|challenge| challenges[challenge.index()], + &|a| -a, + &|a, b| a + &b, + &|a, b| a * &b, + &|a, scalar| a * &scalar, + ) + }) + })) + .chain(permutation.expressions( + vk, + &vk.cs.permutation, + &permutations_common, + advice_evals, + fixed_evals, + instance_evals, + l_0, + l_last, + l_blind, + beta, + gamma, + x, + )) + .chain(lookups.iter().zip(vk.cs.lookups.iter()).flat_map( + move |(p, argument)| { + p.expressions( + l_0, + l_last, + l_blind, + argument, + theta, + beta, + gamma, + advice_evals, + fixed_evals, + instance_evals, + challenges, + ) + }, + )) + }); vanishing.verify(params, expressions, y, xn) }; @@ -368,20 +326,13 @@ where .zip(advice_evals.iter()) .zip(permutations_evaluated.iter()) .zip(lookups_evaluated.iter()) - .zip(shuffles_evaluated.iter()) .flat_map( |( ( - ( - ( - ((instance_commitments, instance_evals), advice_commitments), - advice_evals, - ), - permutation, - ), - lookups, + (((instance_commitments, instance_evals), advice_commitments), advice_evals), + permutation, ), - shuffles, + lookups, )| { iter::empty() .chain( @@ -409,7 +360,6 @@ where )) .chain(permutation.queries(vk, x)) .chain(lookups.iter().flat_map(move |p| p.queries(vk, x))) - .chain(shuffles.iter().flat_map(move |p| p.queries(vk, x))) }, ) .chain( From de18a15b7771c59bff0efe810f5c3d125cbe9126 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Fri, 29 Nov 2024 11:11:04 +0100 Subject: [PATCH 03/20] Remove IPA --- Cargo.toml | 4 - benches/arithmetic.rs | 38 --- benches/plonk.rs | 52 ++-- examples/shuffle.rs | 48 ++-- examples/vector-ops-unblinded.rs | 59 ++-- src/helpers.rs | 5 + src/lib.rs | 2 +- src/plonk/lookup/prover.rs | 21 +- src/plonk/permutation/prover.rs | 16 +- src/plonk/prover.rs | 3 - src/plonk/vanishing/prover.rs | 18 +- src/plonk/verifier.rs | 5 - src/plonk/verifier/batch.rs | 135 --------- src/poly.rs | 3 - src/poly/ipa/commitment.rs | 370 ------------------------ src/poly/ipa/commitment/prover.rs | 167 ----------- src/poly/ipa/commitment/verifier.rs | 100 ------- src/poly/ipa/mod.rs | 7 - src/poly/ipa/msm.rs | 271 ----------------- src/poly/ipa/multiopen.rs | 172 ----------- src/poly/ipa/multiopen/prover.rs | 122 -------- src/poly/ipa/multiopen/verifier.rs | 148 ---------- src/poly/ipa/strategy.rs | 171 ----------- src/poly/multiopen_test.rs | 81 +----- src/poly/query.rs | 18 +- tests/plonk_api.rs | 432 ---------------------------- 26 files changed, 108 insertions(+), 2360 deletions(-) delete mode 100644 benches/arithmetic.rs delete mode 100644 src/plonk/verifier/batch.rs delete mode 100644 src/poly/ipa/commitment.rs delete mode 100644 src/poly/ipa/commitment/prover.rs delete mode 100644 src/poly/ipa/commitment/verifier.rs delete mode 100644 src/poly/ipa/mod.rs delete mode 100644 src/poly/ipa/msm.rs delete mode 100644 src/poly/ipa/multiopen.rs delete mode 100644 src/poly/ipa/multiopen/prover.rs delete mode 100644 src/poly/ipa/multiopen/verifier.rs delete mode 100644 src/poly/ipa/strategy.rs diff --git a/Cargo.toml b/Cargo.toml index e340407dd6..1e865a63ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,10 +23,6 @@ keywords = ["halo", "proofs", "zkp", "zkSNARKs"] all-features = true rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] -[[bench]] -name = "arithmetic" -harness = false - [[bench]] name = "commit_zk" harness = false diff --git a/benches/arithmetic.rs b/benches/arithmetic.rs deleted file mode 100644 index 4ae88af137..0000000000 --- a/benches/arithmetic.rs +++ /dev/null @@ -1,38 +0,0 @@ -#[macro_use] -extern crate criterion; - -use crate::arithmetic::small_multiexp; -use crate::halo2curves::pasta::{EqAffine, Fp}; -use group::ff::Field; -use halo2_proofs::*; - -use halo2_proofs::poly::{commitment::ParamsProver, ipa::commitment::ParamsIPA}; - -use criterion::{black_box, Criterion}; -use rand_core::OsRng; - -fn criterion_benchmark(c: &mut Criterion) { - let rng = OsRng; - - // small multiexp - { - let params: ParamsIPA = ParamsIPA::new(5); - let g = &mut params.get_g().to_vec(); - let len = g.len() / 2; - let (g_lo, g_hi) = g.split_at_mut(len); - - let coeff_1 = Fp::random(rng); - let coeff_2 = Fp::random(rng); - - c.bench_function("double-and-add", |b| { - b.iter(|| { - for (g_lo, g_hi) in g_lo.iter().zip(g_hi.iter()) { - small_multiexp(&[black_box(coeff_1), black_box(coeff_2)], &[*g_lo, *g_hi]); - } - }) - }); - } -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/benches/plonk.rs b/benches/plonk.rs index 9c9bd2618a..97dccbe1bd 100644 --- a/benches/plonk.rs +++ b/benches/plonk.rs @@ -6,24 +6,17 @@ use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; use halo2_proofs::plonk::*; use halo2_proofs::poly::{commitment::ParamsProver, Rotation}; use halo2_proofs::transcript::{Blake2bRead, Blake2bWrite, Challenge255}; -use halo2curves::pasta::{EqAffine, Fp}; +use halo2curves::bn256; use rand_core::OsRng; -use halo2_proofs::{ - poly::{ - ipa::{ - commitment::{IPACommitmentScheme, ParamsIPA}, - multiopen::ProverIPA, - strategy::SingleStrategy, - }, - VerificationStrategy, - }, - transcript::{TranscriptReadBuffer, TranscriptWriterBuffer}, -}; +use halo2_proofs::transcript::{TranscriptReadBuffer, TranscriptWriterBuffer}; use std::marker::PhantomData; use criterion::{BenchmarkId, Criterion}; +use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; +use halo2_proofs::poly::kzg::multiopen::{ProverGWC, VerifierGWC}; +use halo2_proofs::poly::kzg::strategy::SingleStrategy; fn criterion_benchmark(c: &mut Criterion) { /// This represents an advice column at a certain row in the ConstraintSystem @@ -261,9 +254,9 @@ fn criterion_benchmark(c: &mut Criterion) { } } - fn keygen(k: u32) -> (ParamsIPA, ProvingKey) { - let params: ParamsIPA = ParamsIPA::new(k); - let empty_circuit: MyCircuit = MyCircuit { + fn keygen(k: u32) -> (ParamsKZG, ProvingKey) { + let params: ParamsKZG = ParamsKZG::new(k); + let empty_circuit: MyCircuit = MyCircuit { a: Value::unknown(), k, }; @@ -272,16 +265,20 @@ fn criterion_benchmark(c: &mut Criterion) { (params, pk) } - fn prover(k: u32, params: &ParamsIPA, pk: &ProvingKey) -> Vec { + fn prover( + k: u32, + params: &ParamsKZG, + pk: &ProvingKey, + ) -> Vec { let rng = OsRng; - let circuit: MyCircuit = MyCircuit { - a: Value::known(Fp::random(rng)), + let circuit: MyCircuit = MyCircuit { + a: Value::known(bn256::Fr::random(rng)), k, }; - let mut transcript = Blake2bWrite::<_, _, Challenge255>::init(vec![]); - create_proof::, ProverIPA, _, _, _, _>( + let mut transcript = Blake2bWrite::<_, _, Challenge255>::init(vec![]); + create_proof::, ProverGWC, _, _, _, _>( params, pk, &[circuit], @@ -293,10 +290,21 @@ fn criterion_benchmark(c: &mut Criterion) { transcript.finalize() } - fn verifier(params: &ParamsIPA, vk: &VerifyingKey, proof: &[u8]) { + fn verifier( + params: &ParamsKZG, + vk: &VerifyingKey, + proof: &[u8], + ) { let strategy = SingleStrategy::new(params); let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(proof); - assert!(verify_proof(params, vk, strategy, &[&[]], &mut transcript).is_ok()); + assert!(verify_proof::<_, VerifierGWC, _, _, _>( + params, + vk, + strategy, + &[&[]], + &mut transcript + ) + .is_ok()); } let k_range = 8..=16; diff --git a/examples/shuffle.rs b/examples/shuffle.rs index 35a85cb9f0..26fbd887f7 100644 --- a/examples/shuffle.rs +++ b/examples/shuffle.rs @@ -1,23 +1,21 @@ -use ff::{BatchInvert, FromUniformBytes}; +use ff::{BatchInvert, FromUniformBytes, WithSmallOrderMulGroup}; +use halo2_proofs::arithmetic::CurveExt; +use halo2_proofs::helpers::SerdeCurveAffine; +use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; +use halo2_proofs::poly::kzg::multiopen::{ProverGWC, VerifierGWC}; +use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; use halo2_proofs::{ - arithmetic::{CurveAffine, Field}, + arithmetic::Field, circuit::{floor_planner::V1, Layouter, Value}, dev::{metadata, FailureLocation, MockProver, VerifyFailure}, - halo2curves::pasta::EqAffine, plonk::*, - poly::{ - commitment::ParamsProver, - ipa::{ - commitment::{IPACommitmentScheme, ParamsIPA}, - multiopen::{ProverIPA, VerifierIPA}, - strategy::AccumulatorStrategy, - }, - VerificationStrategy, - }, + poly::{commitment::ParamsProver, VerificationStrategy}, transcript::{ Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, }, }; +use halo2curves::pairing::MultiMillerLoop; +use halo2curves::{bn256, pairing::Engine}; use rand_core::{OsRng, RngCore}; use std::iter; @@ -269,21 +267,24 @@ fn test_mock_prover, const W: usize, const H: usiz }; } -fn test_prover( +fn test_prover( k: u32, - circuit: MyCircuit, + circuit: MyCircuit, expected: bool, ) where - C::Scalar: FromUniformBytes<64>, + E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, + E::G1: CurveExt, + E::G2Affine: SerdeCurveAffine, + E::Fr: FromUniformBytes<64> + WithSmallOrderMulGroup<3>, { - let params = ParamsIPA::::new(k); + let params = ParamsKZG::::new(k); let vk = keygen_vk(¶ms, &circuit).unwrap(); let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); let proof = { let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - create_proof::, ProverIPA, _, _, _, _>( + create_proof::, ProverGWC, _, _, _, _>( ¶ms, &pk, &[circuit], @@ -300,14 +301,19 @@ fn test_prover( let strategy = AccumulatorStrategy::new(¶ms); let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - verify_proof::, VerifierIPA, _, _, _>( + verify_proof::, VerifierGWC, _, _, _>( ¶ms, pk.get_vk(), strategy, &[&[]], &mut transcript, ) - .map(|strategy| strategy.finalize()) + .map(|strategy| { + as VerificationStrategy< + KZGCommitmentScheme, + VerifierGWC, + >>::finalize(strategy) + }) .unwrap_or_default() }; @@ -323,7 +329,7 @@ fn main() { { test_mock_prover(K, circuit.clone(), Ok(())); - test_prover::(K, circuit.clone(), true); + test_prover::(K, circuit.clone(), true); } #[cfg(not(feature = "sanity-checks"))] @@ -347,6 +353,6 @@ fn main() { }, )]), ); - test_prover::(K, circuit, false); + test_prover::(K, circuit, false); } } diff --git a/examples/vector-ops-unblinded.rs b/examples/vector-ops-unblinded.rs index 7e9ebd1d81..65d5aaa8d5 100644 --- a/examples/vector-ops-unblinded.rs +++ b/examples/vector-ops-unblinded.rs @@ -3,24 +3,22 @@ /// This is a simple example of how to use unblinded inputs to match up circuits that might be proved on different host machines. use std::marker::PhantomData; -use ff::FromUniformBytes; +use ff::{FromUniformBytes, WithSmallOrderMulGroup}; +use halo2_proofs::arithmetic::CurveExt; +use halo2_proofs::helpers::SerdeCurveAffine; +use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; +use halo2_proofs::poly::kzg::multiopen::{ProverGWC, VerifierGWC}; +use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; use halo2_proofs::{ - arithmetic::{CurveAffine, Field}, + arithmetic::Field, circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, plonk::*, - poly::{ - commitment::ParamsProver, - ipa::{ - commitment::{IPACommitmentScheme, ParamsIPA}, - multiopen::{ProverIPA, VerifierIPA}, - strategy::AccumulatorStrategy, - }, - Rotation, VerificationStrategy, - }, + poly::{commitment::ParamsProver, Rotation, VerificationStrategy}, transcript::{ Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, }, }; +use halo2curves::pairing::{Engine, MultiMillerLoop}; use rand_core::OsRng; // ANCHOR: instructions @@ -466,23 +464,26 @@ impl Circuit for AddCircuit { } // ANCHOR_END: circuit -fn test_prover( +fn test_prover( k: u32, - circuit: impl Circuit, + circuit: impl Circuit, expected: bool, - instances: Vec, + instances: Vec, ) -> Vec where - C::Scalar: FromUniformBytes<64>, + E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, + E::G1: CurveExt, + E::G2Affine: SerdeCurveAffine, + E::Fr: FromUniformBytes<64> + WithSmallOrderMulGroup<3>, { - let params = ParamsIPA::::new(k); + let params = ParamsKZG::::new(k); let vk = keygen_vk(¶ms, &circuit).unwrap(); let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); let proof = { let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - create_proof::, ProverIPA, _, _, _, _>( + create_proof::, ProverGWC, _, _, _, _>( ¶ms, &pk, &[circuit], @@ -499,14 +500,19 @@ where let strategy = AccumulatorStrategy::new(¶ms); let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - verify_proof::, VerifierIPA, _, _, _>( + verify_proof::, VerifierGWC, _, _, AccumulatorStrategy>( ¶ms, pk.get_vk(), strategy, &[&[&instances]], &mut transcript, ) - .map(|strategy| strategy.finalize()) + .map(|strategy| { + as VerificationStrategy< + KZGCommitmentScheme, + VerifierGWC, + >>::finalize(strategy) + }) .unwrap_or_default() }; @@ -516,8 +522,7 @@ where } fn main() { - use halo2curves::pasta::Fp; - + use halo2curves::bn256::Fr; const N: usize = 10; // ANCHOR: test-circuit // The number of rows in our circuit cannot exceed 2^k. Since our example @@ -525,10 +530,10 @@ fn main() { let k = 7; // Prepare the inputs to the circuit! - let a = [Fp::from(2); N]; - let b = [Fp::from(3); N]; - let c_mul: Vec = a.iter().zip(b).map(|(&a, b)| a * b).collect(); - let c_add: Vec = a.iter().zip(b).map(|(&a, b)| a + b).collect(); + let a = [Fr::from(2); N]; + let b = [Fr::from(3); N]; + let c_mul: Vec = a.iter().zip(b).map(|(&a, b)| a * b).collect(); + let c_add: Vec = a.iter().zip(b).map(|(&a, b)| a + b).collect(); // Instantiate the mul circuit with the inputs. let mul_circuit = MulCircuit { @@ -543,9 +548,9 @@ fn main() { }; // the commitments will be the first columns of the proof transcript so we can compare them easily - let proof_1 = test_prover::(k, mul_circuit, true, c_mul); + let proof_1 = test_prover::(k, mul_circuit, true, c_mul); // the commitments will be the first columns of the proof transcript so we can compare them easily - let proof_2 = test_prover::(k, add_circuit, true, c_add); + let proof_2 = test_prover::(k, add_circuit, true, c_add); // the commitments will be the first columns of the proof transcript so we can compare them easily // here we compare the first 10 bytes of the commitments diff --git a/src/helpers.rs b/src/helpers.rs index faf7351a3e..92b977692f 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,3 +1,6 @@ +//! HELPER FUNCTIONS +//! TODO: CHECK IF WE ACTUALLY WANT TO EXPOSE THIS + use crate::poly::Polynomial; use ff::PrimeField; use halo2curves::{serde::SerdeObject, CurveAffine}; @@ -32,6 +35,7 @@ pub(crate) trait CurveRead: CurveAffine { } impl CurveRead for C {} +/// Trait for serialising SerdeObjects pub trait SerdeCurveAffine: CurveAffine + SerdeObject { /// Reads an element from the buffer and parses it according to the `format`: /// - `Processed`: Reads a compressed curve element and decompress it @@ -66,6 +70,7 @@ pub trait SerdeCurveAffine: CurveAffine + SerdeObject { } impl SerdeCurveAffine for C {} +/// Trait for implementing field SerdeObjects pub trait SerdePrimeField: PrimeField + SerdeObject { /// Reads a field element as bytes from the buffer according to the `format`: /// - `Processed`: Reads a field element in standard form, with endianness specified by the diff --git a/src/lib.rs b/src/lib.rs index acc26aff15..19994ce9be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,5 +17,5 @@ pub mod poly; pub mod transcript; pub mod dev; -mod helpers; +pub mod helpers; pub use helpers::SerdeFormat; diff --git a/src/plonk/lookup/prover.rs b/src/plonk/lookup/prover.rs index 028b298853..9e5005a4b7 100644 --- a/src/plonk/lookup/prover.rs +++ b/src/plonk/lookup/prover.rs @@ -29,21 +29,16 @@ pub(in crate::plonk) struct Permuted { compressed_input_expression: Polynomial, permuted_input_expression: Polynomial, permuted_input_poly: Polynomial, - permuted_input_blind: Blind, compressed_table_expression: Polynomial, permuted_table_expression: Polynomial, permuted_table_poly: Polynomial, - permuted_table_blind: Blind, } #[derive(Debug)] pub(in crate::plonk) struct Committed { pub(in crate::plonk) permuted_input_poly: Polynomial, - permuted_input_blind: Blind, pub(in crate::plonk) permuted_table_poly: Polynomial, - permuted_table_blind: Blind, pub(in crate::plonk) product_poly: Polynomial, - product_blind: Blind, } pub(in crate::plonk) struct Evaluated { @@ -128,15 +123,15 @@ impl> Argument { let poly = pk.vk.domain.lagrange_to_coeff(values.clone()); let blind = Blind(C::Scalar::random(&mut rng)); let commitment = params.commit_lagrange(values, blind).to_affine(); - (poly, blind, commitment) + (poly, commitment) }; // Commit to permuted input expression - let (permuted_input_poly, permuted_input_blind, permuted_input_commitment) = + let (permuted_input_poly, permuted_input_commitment) = commit_values(&permuted_input_expression); // Commit to permuted table expression - let (permuted_table_poly, permuted_table_blind, permuted_table_commitment) = + let (permuted_table_poly, permuted_table_commitment) = commit_values(&permuted_table_expression); // Hash permuted input commitment @@ -149,11 +144,9 @@ impl> Argument { compressed_input_expression, permuted_input_expression, permuted_input_poly, - permuted_input_blind, compressed_table_expression, permuted_table_expression, permuted_table_poly, - permuted_table_blind, }) } } @@ -296,11 +289,8 @@ impl Permuted { Ok(Committed:: { permuted_input_poly: self.permuted_input_poly, - permuted_input_blind: self.permuted_input_blind, permuted_table_poly: self.permuted_table_poly, - permuted_table_blind: self.permuted_table_blind, product_poly: z, - product_blind, }) } } @@ -351,31 +341,26 @@ impl Evaluated { .chain(Some(ProverQuery { point: *x, poly: &self.constructed.product_poly, - blind: self.constructed.product_blind, })) // Open lookup input commitments at x .chain(Some(ProverQuery { point: *x, poly: &self.constructed.permuted_input_poly, - blind: self.constructed.permuted_input_blind, })) // Open lookup table commitments at x .chain(Some(ProverQuery { point: *x, poly: &self.constructed.permuted_table_poly, - blind: self.constructed.permuted_table_blind, })) // Open lookup input commitments at x_inv .chain(Some(ProverQuery { point: x_inv, poly: &self.constructed.permuted_input_poly, - blind: self.constructed.permuted_input_blind, })) // Open lookup product commitments at x_next .chain(Some(ProverQuery { point: x_next, poly: &self.constructed.product_poly, - blind: self.constructed.product_blind, })) } } diff --git a/src/plonk/permutation/prover.rs b/src/plonk/permutation/prover.rs index d6b108554d..b6ecb06a1c 100644 --- a/src/plonk/permutation/prover.rs +++ b/src/plonk/permutation/prover.rs @@ -21,7 +21,6 @@ use crate::{ pub(crate) struct CommittedSet { pub(crate) permutation_product_poly: Polynomial, pub(crate) permutation_product_coset: Polynomial, - permutation_product_blind: Blind, } pub(crate) struct Committed { @@ -30,7 +29,6 @@ pub(crate) struct Committed { pub struct ConstructedSet { permutation_product_poly: Polynomial, - permutation_product_blind: Blind, } pub(crate) struct Constructed { @@ -169,7 +167,6 @@ impl Argument { let blind = Blind(C::Scalar::random(&mut rng)); let permutation_product_commitment_projective = params.commit_lagrange(&z, blind); - let permutation_product_blind = blind; let z = domain.lagrange_to_coeff(z); let permutation_product_poly = z.clone(); @@ -184,7 +181,6 @@ impl Argument { sets.push(CommittedSet { permutation_product_poly, permutation_product_coset, - permutation_product_blind, }); } @@ -200,7 +196,6 @@ impl Committed { .iter() .map(|set| ConstructedSet { permutation_product_poly: set.permutation_product_poly.clone(), - permutation_product_blind: set.permutation_product_blind, }) .collect(), } @@ -212,11 +207,9 @@ impl super::ProvingKey { &self, x: ChallengeX, ) -> impl Iterator> + Clone { - self.polys.iter().map(move |poly| ProverQuery { - point: *x, - poly, - blind: Blind::default(), - }) + self.polys + .iter() + .map(move |poly| ProverQuery { point: *x, poly }) } pub(in crate::plonk) fn evaluate, T: TranscriptWrite>( @@ -300,12 +293,10 @@ impl Evaluated { .chain(Some(ProverQuery { point: *x, poly: &set.permutation_product_poly, - blind: set.permutation_product_blind, })) .chain(Some(ProverQuery { point: x_next, poly: &set.permutation_product_poly, - blind: set.permutation_product_blind, })) })) // Open it at \omega^{last} x for all but the last set. This rotation is only @@ -321,7 +312,6 @@ impl Evaluated { Some(ProverQuery { point: x_last, poly: &set.permutation_product_poly, - blind: set.permutation_product_blind, }) }), ) diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 312e7a0cf3..61c18ea593 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -630,7 +630,6 @@ where ProverQuery { point: domain.rotate_omega(*x, at), poly: &instance.instance_polys[column.index()], - blind: Blind::default(), } })) .into_iter() @@ -644,7 +643,6 @@ where .map(move |&(column, at)| ProverQuery { point: domain.rotate_omega(*x, at), poly: &advice.advice_polys[column.index()], - blind: advice.advice_blinds[column.index()], }), ) .chain(permutation.open(pk, x)) @@ -658,7 +656,6 @@ where .map(|&(column, at)| ProverQuery { point: domain.rotate_omega(*x, at), poly: &pk.fixed_polys[column.index()], - blind: Blind::default(), }), ) .chain(pk.permutation.open(x)) diff --git a/src/plonk/vanishing/prover.rs b/src/plonk/vanishing/prover.rs index 7943086826..aff2595a46 100644 --- a/src/plonk/vanishing/prover.rs +++ b/src/plonk/vanishing/prover.rs @@ -19,18 +19,15 @@ use crate::{ pub(in crate::plonk) struct Committed { random_poly: Polynomial, - random_blind: Blind, } pub(in crate::plonk) struct Constructed { h_pieces: Vec>, - h_blinds: Vec>, committed: Committed, } pub(in crate::plonk) struct Evaluated { h_poly: Polynomial, - h_blind: Blind, committed: Committed, } @@ -86,10 +83,7 @@ impl Argument { let c = params.commit(&random_poly, random_blind).to_affine(); transcript.write_point(c)?; - Ok(Committed { - random_poly, - random_blind, - }) + Ok(Committed { random_poly }) } } @@ -142,7 +136,6 @@ impl Committed { Ok(Constructed { h_pieces, - h_blinds, committed: self, }) } @@ -162,18 +155,11 @@ impl Constructed { .rev() .fold(domain.empty_coeff(), |acc, eval| acc * xn + eval); - let h_blind = self - .h_blinds - .iter() - .rev() - .fold(Blind(C::Scalar::ZERO), |acc, eval| acc * Blind(xn) + *eval); - let random_eval = eval_polynomial(&self.committed.random_poly, *x); transcript.write_scalar(random_eval)?; Ok(Evaluated { h_poly, - h_blind, committed: self.committed, }) } @@ -188,12 +174,10 @@ impl Evaluated { .chain(Some(ProverQuery { point: *x, poly: &self.h_poly, - blind: self.h_blind, })) .chain(Some(ProverQuery { point: *x, poly: &self.committed.random_poly, - blind: self.committed.random_blind, })) } } diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 068c6a93e9..587fa8186f 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -15,11 +15,6 @@ use crate::poly::{ }; use crate::transcript::{read_n_scalars, EncodedChallenge, TranscriptRead}; -#[cfg(feature = "batch")] -mod batch; -#[cfg(feature = "batch")] -pub use batch::BatchVerifier; - /// Returns a boolean indicating whether or not the proof is valid pub fn verify_proof< 'params, diff --git a/src/plonk/verifier/batch.rs b/src/plonk/verifier/batch.rs deleted file mode 100644 index ba3e2419e6..0000000000 --- a/src/plonk/verifier/batch.rs +++ /dev/null @@ -1,135 +0,0 @@ -use ff::FromUniformBytes; -use group::ff::Field; -use halo2curves::CurveAffine; -use rand_core::OsRng; - -use super::{verify_proof, VerificationStrategy}; -use crate::{ - multicore::{ - IndexedParallelIterator, IntoParallelIterator, ParallelIterator, TryFoldAndReduce, - }, - plonk::{Error, VerifyingKey}, - poly::{ - commitment::{Params, MSM}, - ipa::{ - commitment::{IPACommitmentScheme, ParamsVerifierIPA}, - msm::MSMIPA, - multiopen::VerifierIPA, - strategy::GuardIPA, - }, - }, - transcript::{Blake2bRead, TranscriptReadBuffer}, -}; - -/// A proof verification strategy that returns the proof's MSM. -/// -/// `BatchVerifier` handles the accumulation of the MSMs for the batched proofs. -#[derive(Debug)] -struct BatchStrategy<'params, C: CurveAffine> { - msm: MSMIPA<'params, C>, -} - -impl<'params, C: CurveAffine> - VerificationStrategy<'params, IPACommitmentScheme, VerifierIPA<'params, C>> - for BatchStrategy<'params, C> -{ - type Output = MSMIPA<'params, C>; - - fn new(params: &'params ParamsVerifierIPA) -> Self { - BatchStrategy { - msm: MSMIPA::new(params), - } - } - - fn process( - self, - f: impl FnOnce(MSMIPA<'params, C>) -> Result, Error>, - ) -> Result { - let guard = f(self.msm)?; - Ok(guard.use_challenges()) - } - - fn finalize(self) -> bool { - unreachable!() - } -} - -#[derive(Debug)] -struct BatchItem { - instances: Vec>>, - proof: Vec, -} - -/// A verifier that checks multiple proofs in a batch. **This requires the -/// `batch` crate feature to be enabled.** -#[derive(Debug, Default)] -pub struct BatchVerifier { - items: Vec>, -} - -impl BatchVerifier -where - C::Scalar: FromUniformBytes<64>, -{ - /// Constructs a new batch verifier. - pub fn new() -> Self { - Self { items: vec![] } - } - - /// Adds a proof to the batch. - pub fn add_proof(&mut self, instances: Vec>>, proof: Vec) { - self.items.push(BatchItem { instances, proof }) - } - - /// Finalizes the batch and checks its validity. - /// - /// Returns `false` if *some* proof was invalid. If the caller needs to identify - /// specific failing proofs, it must re-process the proofs separately. - /// - /// This uses [`OsRng`] internally instead of taking an `R: RngCore` argument, because - /// the internal parallelization requires access to a RNG that is guaranteed to not - /// clone its internal state when shared between threads. - pub fn finalize(self, params: &ParamsVerifierIPA, vk: &VerifyingKey) -> bool { - fn accumulate_msm<'params, C: CurveAffine>( - mut acc: MSMIPA<'params, C>, - msm: MSMIPA<'params, C>, - ) -> MSMIPA<'params, C> { - // Scale the MSM by a random factor to ensure that if the existing MSM has - // `is_zero() == false` then this argument won't be able to interfere with it - // to make it true, with high probability. - acc.scale(C::Scalar::random(OsRng)); - - acc.add_msm(&msm); - acc - } - - let final_msm = self - .items - .into_par_iter() - .enumerate() - .map(|(i, item)| { - let instances: Vec> = item - .instances - .iter() - .map(|i| i.iter().map(|c| &c[..]).collect()) - .collect(); - let instances: Vec<_> = instances.iter().map(|i| &i[..]).collect(); - - let strategy = BatchStrategy::new(params); - let mut transcript = Blake2bRead::init(&item.proof[..]); - verify_proof(params, vk, strategy, &instances, &mut transcript).map_err(|e| { - tracing::debug!("Batch item {} failed verification: {}", i, e); - e - }) - }) - .try_fold_and_reduce( - || params.empty_msm(), - |acc, res| res.map(|proof_msm| accumulate_msm(acc, proof_msm)), - ); - - match final_msm { - Ok(msm) => msm.check(), - Err(_) => false, - } - } -} diff --git a/src/poly.rs b/src/poly.rs index 9cb6b149bc..f7c5c53b6b 100644 --- a/src/poly.rs +++ b/src/poly.rs @@ -19,9 +19,6 @@ mod domain; mod query; mod strategy; -/// Inner product argument commitment scheme -pub mod ipa; - /// KZG commitment scheme pub mod kzg; diff --git a/src/poly/ipa/commitment.rs b/src/poly/ipa/commitment.rs deleted file mode 100644 index 7be053c49c..0000000000 --- a/src/poly/ipa/commitment.rs +++ /dev/null @@ -1,370 +0,0 @@ -//! This module contains an implementation of the polynomial commitment scheme -//! described in the [Halo][halo] paper. -//! -//! [halo]: https://eprint.iacr.org/2019/1021 - -use crate::arithmetic::{best_multiexp, g_to_lagrange, parallelize, CurveAffine, CurveExt}; -use crate::helpers::CurveRead; -use crate::poly::commitment::{Blind, CommitmentScheme, Params, ParamsProver, ParamsVerifier}; -use crate::poly::ipa::msm::MSMIPA; -use crate::poly::{Coeff, LagrangeCoeff, Polynomial}; - -use group::{Curve, Group}; -use std::marker::PhantomData; - -mod prover; -mod verifier; - -pub use prover::create_proof; -pub use verifier::verify_proof; - -use std::io; - -/// Public parameters for IPA commitment scheme -#[derive(Debug, Clone)] -pub struct ParamsIPA { - pub(crate) k: u32, - pub(crate) n: u64, - pub(crate) g: Vec, - pub(crate) g_lagrange: Vec, - pub(crate) w: C, - pub(crate) u: C, -} - -/// Concrete IPA commitment scheme -#[derive(Debug)] -pub struct IPACommitmentScheme { - _marker: PhantomData, -} - -impl CommitmentScheme for IPACommitmentScheme { - type Scalar = C::ScalarExt; - type Curve = C; - - type ParamsProver = ParamsIPA; - type ParamsVerifier = ParamsVerifierIPA; - - fn new_params(k: u32) -> Self::ParamsProver { - ParamsIPA::new(k) - } - - fn read_params(reader: &mut R) -> io::Result { - ParamsIPA::read(reader) - } -} - -/// Verifier parameters -pub type ParamsVerifierIPA = ParamsIPA; - -impl<'params, C: CurveAffine> ParamsVerifier<'params, C> for ParamsIPA {} - -impl<'params, C: CurveAffine> Params<'params, C> for ParamsIPA { - type MSM = MSMIPA<'params, C>; - - fn k(&self) -> u32 { - self.k - } - - fn n(&self) -> u64 { - self.n - } - - fn downsize(&mut self, k: u32) { - assert!(k <= self.k); - - self.k = k; - self.n = 1 << k; - self.g.truncate(self.n as usize); - self.g_lagrange = g_to_lagrange(self.g.iter().map(|g| g.to_curve()).collect(), k); - } - - fn empty_msm(&'params self) -> MSMIPA { - MSMIPA::new(self) - } - - /// This commits to a polynomial using its evaluations over the $2^k$ size - /// evaluation domain. The commitment will be blinded by the blinding factor - /// `r`. - fn commit_lagrange( - &self, - poly: &Polynomial, - r: Blind, - ) -> C::Curve { - let mut tmp_scalars = Vec::with_capacity(poly.len() + 1); - let mut tmp_bases = Vec::with_capacity(poly.len() + 1); - - tmp_scalars.extend(poly.iter()); - tmp_scalars.push(r.0); - - tmp_bases.extend(self.g_lagrange.iter()); - tmp_bases.push(self.w); - - best_multiexp::(&tmp_scalars, &tmp_bases) - } - - /// Writes params to a buffer. - fn write(&self, writer: &mut W) -> io::Result<()> { - writer.write_all(&self.k.to_le_bytes())?; - for g_element in &self.g { - writer.write_all(g_element.to_bytes().as_ref())?; - } - for g_lagrange_element in &self.g_lagrange { - writer.write_all(g_lagrange_element.to_bytes().as_ref())?; - } - writer.write_all(self.w.to_bytes().as_ref())?; - writer.write_all(self.u.to_bytes().as_ref())?; - - Ok(()) - } - - /// Reads params from a buffer. - fn read(reader: &mut R) -> io::Result { - let mut k = [0u8; 4]; - reader.read_exact(&mut k[..])?; - let k = u32::from_le_bytes(k); - - let n: u64 = 1 << k; - - let g: Vec<_> = (0..n).map(|_| C::read(reader)).collect::>()?; - let g_lagrange: Vec<_> = (0..n).map(|_| C::read(reader)).collect::>()?; - - let w = C::read(reader)?; - let u = C::read(reader)?; - - Ok(Self { - k, - n, - g, - g_lagrange, - w, - u, - }) - } -} - -impl<'params, C: CurveAffine> ParamsProver<'params, C> for ParamsIPA { - type ParamsVerifier = ParamsVerifierIPA; - - fn verifier_params(&'params self) -> &'params Self::ParamsVerifier { - self - } - - /// Initializes parameters for the curve, given a random oracle to draw - /// points from. - fn new(k: u32) -> Self { - // This is usually a limitation on the curve, but we also want 32-bit - // architectures to be supported. - assert!(k < 32); - - // In src/arithmetic/fields.rs we ensure that usize is at least 32 bits. - - let n: u64 = 1 << k; - - let g_projective = { - let mut g = Vec::with_capacity(n as usize); - g.resize(n as usize, C::Curve::identity()); - - parallelize(&mut g, move |g, start| { - let hasher = C::CurveExt::hash_to_curve("Halo2-Parameters"); - - for (i, g) in g.iter_mut().enumerate() { - let i = (i + start) as u32; - - let mut message = [0u8; 5]; - message[1..5].copy_from_slice(&i.to_le_bytes()); - - *g = hasher(&message); - } - }); - - g - }; - - let g = { - let mut g = vec![C::identity(); n as usize]; - parallelize(&mut g, |g, starts| { - C::Curve::batch_normalize(&g_projective[starts..(starts + g.len())], g); - }); - g - }; - - // Let's evaluate all of the Lagrange basis polynomials - // using an inverse FFT. - let g_lagrange = g_to_lagrange(g_projective, k); - - let hasher = C::CurveExt::hash_to_curve("Halo2-Parameters"); - let w = hasher(&[1]).to_affine(); - let u = hasher(&[2]).to_affine(); - - ParamsIPA { - k, - n, - g, - g_lagrange, - w, - u, - } - } - - /// This computes a commitment to a polynomial described by the provided - /// slice of coefficients. The commitment will be blinded by the blinding - /// factor `r`. - fn commit(&self, poly: &Polynomial, r: Blind) -> C::Curve { - let mut tmp_scalars = Vec::with_capacity(poly.len() + 1); - let mut tmp_bases = Vec::with_capacity(poly.len() + 1); - - tmp_scalars.extend(poly.iter()); - tmp_scalars.push(r.0); - - tmp_bases.extend(self.g.iter()); - tmp_bases.push(self.w); - - best_multiexp::(&tmp_scalars, &tmp_bases) - } - - fn get_g(&self) -> &[C] { - &self.g - } -} - -#[cfg(test)] -mod test { - use crate::poly::commitment::ParamsProver; - use crate::poly::commitment::{Blind, Params, MSM}; - use crate::poly::ipa::commitment::{create_proof, verify_proof, ParamsIPA}; - use crate::poly::ipa::msm::MSMIPA; - - use ff::Field; - use group::Curve; - - #[test] - fn test_commit_lagrange_epaffine() { - const K: u32 = 6; - - use rand_core::OsRng; - - use crate::poly::EvaluationDomain; - use halo2curves::pasta::{EpAffine, Fq}; - - let params = ParamsIPA::::new(K); - let domain = EvaluationDomain::new(1, K); - - let mut a = domain.empty_lagrange(); - - for (i, a) in a.iter_mut().enumerate() { - *a = Fq::from(i as u64); - } - - let b = domain.lagrange_to_coeff(a.clone()); - - let alpha = Blind(Fq::random(OsRng)); - - assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha)); - } - - #[test] - fn test_commit_lagrange_eqaffine() { - const K: u32 = 6; - - use rand_core::OsRng; - - use crate::poly::EvaluationDomain; - use halo2curves::pasta::{EqAffine, Fp}; - - let params: ParamsIPA = ParamsIPA::::new(K); - let domain = EvaluationDomain::new(1, K); - - let mut a = domain.empty_lagrange(); - - for (i, a) in a.iter_mut().enumerate() { - *a = Fp::from(i as u64); - } - - let b = domain.lagrange_to_coeff(a.clone()); - - let alpha = Blind(Fp::random(OsRng)); - - assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha)); - } - - #[test] - fn test_opening_proof() { - const K: u32 = 6; - - use ff::Field; - use rand_core::OsRng; - - use super::super::commitment::{Blind, Params}; - use crate::arithmetic::eval_polynomial; - use crate::halo2curves::pasta::{EpAffine, Fq}; - use crate::poly::EvaluationDomain; - use crate::transcript::{ - Blake2bRead, Blake2bWrite, Challenge255, Transcript, TranscriptRead, TranscriptWrite, - }; - - use crate::transcript::TranscriptReadBuffer; - use crate::transcript::TranscriptWriterBuffer; - - let rng = OsRng; - - let params = ParamsIPA::::new(K); - let mut params_buffer = vec![]; - as Params<_>>::write(¶ms, &mut params_buffer).unwrap(); - let params: ParamsIPA = Params::read::<_>(&mut ¶ms_buffer[..]).unwrap(); - - let domain = EvaluationDomain::new(1, K); - - let mut px = domain.empty_coeff(); - - for (i, a) in px.iter_mut().enumerate() { - *a = Fq::from(i as u64); - } - - let blind = Blind(Fq::random(rng)); - - let p = params.commit(&px, blind).to_affine(); - - let mut transcript = - Blake2bWrite::, EpAffine, Challenge255>::init(vec![]); - transcript.write_point(p).unwrap(); - let x = transcript.squeeze_challenge_scalar::<()>(); - // Evaluate the polynomial - let v = eval_polynomial(&px, *x); - transcript.write_scalar(v).unwrap(); - - let (proof, ch_prover) = { - create_proof(¶ms, rng, &mut transcript, &px, blind, *x).unwrap(); - let ch_prover = transcript.squeeze_challenge(); - (transcript.finalize(), ch_prover) - }; - - // Verify the opening proof - let mut transcript = - Blake2bRead::<&[u8], EpAffine, Challenge255>::init(&proof[..]); - let p_prime = transcript.read_point().unwrap(); - assert_eq!(p, p_prime); - let x_prime = transcript.squeeze_challenge_scalar::<()>(); - assert_eq!(*x, *x_prime); - let v_prime = transcript.read_scalar().unwrap(); - assert_eq!(v, v_prime); - - let mut commitment_msm = MSMIPA::new(¶ms); - commitment_msm.append_term(Fq::one(), p.into()); - - let guard = verify_proof(¶ms, commitment_msm, &mut transcript, *x, v).unwrap(); - let ch_verifier = transcript.squeeze_challenge(); - assert_eq!(*ch_prover, *ch_verifier); - - // Test guard behavior prior to checking another proof - { - // Test use_challenges() - let msm_challenges = guard.clone().use_challenges(); - assert!(msm_challenges.check()); - - // Test use_g() - let g = guard.compute_g(); - let (msm_g, _accumulator) = guard.clone().use_g(g); - assert!(msm_g.check()); - } - } -} diff --git a/src/poly/ipa/commitment/prover.rs b/src/poly/ipa/commitment/prover.rs deleted file mode 100644 index 344dbc0e65..0000000000 --- a/src/poly/ipa/commitment/prover.rs +++ /dev/null @@ -1,167 +0,0 @@ -use ff::Field; -use rand_core::RngCore; - -use super::ParamsIPA; -use crate::arithmetic::{ - best_multiexp, compute_inner_product, eval_polynomial, parallelize, CurveAffine, -}; - -use crate::poly::commitment::ParamsProver; -use crate::poly::{commitment::Blind, Coeff, Polynomial}; -use crate::transcript::{EncodedChallenge, TranscriptWrite}; - -use group::Curve; -use std::io::{self}; - -/// Create a polynomial commitment opening proof for the polynomial defined -/// by the coefficients `px`, the blinding factor `blind` used for the -/// polynomial commitment, and the point `x` that the polynomial is -/// evaluated at. -/// -/// This function will panic if the provided polynomial is too large with -/// respect to the polynomial commitment parameters. -/// -/// **Important:** This function assumes that the provided `transcript` has -/// already seen the common inputs: the polynomial commitment P, the claimed -/// opening v, and the point x. It's probably also nice for the transcript -/// to have seen the elliptic curve description and the URS, if you want to -/// be rigorous. -pub fn create_proof< - C: CurveAffine, - E: EncodedChallenge, - R: RngCore, - T: TranscriptWrite, ->( - params: &ParamsIPA, - mut rng: R, - transcript: &mut T, - p_poly: &Polynomial, - p_blind: Blind, - x_3: C::Scalar, -) -> io::Result<()> { - // We're limited to polynomials of degree n - 1. - assert_eq!(p_poly.len(), params.n as usize); - - // Sample a random polynomial (of same degree) that has a root at x_3, first - // by setting all coefficients to random values. - let mut s_poly = (*p_poly).clone(); - for coeff in s_poly.iter_mut() { - *coeff = C::Scalar::random(&mut rng); - } - // Evaluate the random polynomial at x_3 - let s_at_x3 = eval_polynomial(&s_poly[..], x_3); - // Subtract constant coefficient to get a random polynomial with a root at x_3 - s_poly[0] -= &s_at_x3; - // And sample a random blind - let s_poly_blind = Blind(C::Scalar::random(&mut rng)); - - // Write a commitment to the random polynomial to the transcript - let s_poly_commitment = params.commit(&s_poly, s_poly_blind).to_affine(); - transcript.write_point(s_poly_commitment)?; - - // Challenge that will ensure that the prover cannot change P but can only - // witness a random polynomial commitment that agrees with P at x_3, with high - // probability. - let xi = *transcript.squeeze_challenge_scalar::<()>(); - - // Challenge that ensures that the prover did not interfere with the U term - // in their commitments. - let z = *transcript.squeeze_challenge_scalar::<()>(); - - // We'll be opening `P' = P - [v] G_0 + [ξ] S` to ensure it has a root at - // zero. - let mut p_prime_poly = s_poly * xi + p_poly; - let v = eval_polynomial(&p_prime_poly, x_3); - p_prime_poly[0] -= &v; - let p_prime_blind = s_poly_blind * Blind(xi) + p_blind; - - // This accumulates the synthetic blinding factor `f` starting - // with the blinding factor for `P'`. - let mut f = p_prime_blind.0; - - // Initialize the vector `p_prime` as the coefficients of the polynomial. - let mut p_prime = p_prime_poly.values; - assert_eq!(p_prime.len(), params.n as usize); - - // Initialize the vector `b` as the powers of `x_3`. The inner product of - // `p_prime` and `b` is the evaluation of the polynomial at `x_3`. - let mut b = Vec::with_capacity(1 << params.k); - { - let mut cur = C::Scalar::ONE; - for _ in 0..(1 << params.k) { - b.push(cur); - cur *= &x_3; - } - } - - // Initialize the vector `G'` from the URS. We'll be progressively collapsing - // this vector into smaller and smaller vectors until it is of length 1. - let mut g_prime = params.g.clone(); - - // Perform the inner product argument, round by round. - for j in 0..params.k { - let half = 1 << (params.k - j - 1); // half the length of `p_prime`, `b`, `G'` - - // Compute L, R - // - // TODO: If we modify multiexp to take "extra" bases, we could speed - // this piece up a bit by combining the multiexps. - let l_j = best_multiexp(&p_prime[half..], &g_prime[0..half]); - let r_j = best_multiexp(&p_prime[0..half], &g_prime[half..]); - let value_l_j = compute_inner_product(&p_prime[half..], &b[0..half]); - let value_r_j = compute_inner_product(&p_prime[0..half], &b[half..]); - let l_j_randomness = C::Scalar::random(&mut rng); - let r_j_randomness = C::Scalar::random(&mut rng); - let l_j = l_j + &best_multiexp(&[value_l_j * &z, l_j_randomness], &[params.u, params.w]); - let r_j = r_j + &best_multiexp(&[value_r_j * &z, r_j_randomness], &[params.u, params.w]); - let l_j = l_j.to_affine(); - let r_j = r_j.to_affine(); - - // Feed L and R into the real transcript - transcript.write_point(l_j)?; - transcript.write_point(r_j)?; - - let u_j = *transcript.squeeze_challenge_scalar::<()>(); - let u_j_inv = u_j.invert().unwrap(); // TODO, bubble this up - - // Collapse `p_prime` and `b`. - // TODO: parallelize - for i in 0..half { - p_prime[i] = p_prime[i] + &(p_prime[i + half] * &u_j_inv); - b[i] = b[i] + &(b[i + half] * &u_j); - } - p_prime.truncate(half); - b.truncate(half); - - // Collapse `G'` - parallel_generator_collapse(&mut g_prime, u_j); - g_prime.truncate(half); - - // Update randomness (the synthetic blinding factor at the end) - f += &(l_j_randomness * &u_j_inv); - f += &(r_j_randomness * &u_j); - } - - // We have fully collapsed `p_prime`, `b`, `G'` - assert_eq!(p_prime.len(), 1); - let c = p_prime[0]; - - transcript.write_scalar(c)?; - transcript.write_scalar(f)?; - - Ok(()) -} - -fn parallel_generator_collapse(g: &mut [C], challenge: C::Scalar) { - let len = g.len() / 2; - let (g_lo, g_hi) = g.split_at_mut(len); - - parallelize(g_lo, |g_lo, start| { - let g_hi = &g_hi[start..]; - let mut tmp = Vec::with_capacity(g_lo.len()); - for (g_lo, g_hi) in g_lo.iter().zip(g_hi.iter()) { - tmp.push(g_lo.to_curve() + &(*g_hi * challenge)); - } - C::Curve::batch_normalize(&tmp, g_lo); - }); -} diff --git a/src/poly/ipa/commitment/verifier.rs b/src/poly/ipa/commitment/verifier.rs deleted file mode 100644 index cf258625d5..0000000000 --- a/src/poly/ipa/commitment/verifier.rs +++ /dev/null @@ -1,100 +0,0 @@ -use group::ff::{BatchInvert, Field}; - -use super::ParamsIPA; -use crate::{arithmetic::CurveAffine, poly::ipa::strategy::GuardIPA}; -use crate::{ - poly::{commitment::MSM, ipa::msm::MSMIPA, Error}, - transcript::{EncodedChallenge, TranscriptRead}, -}; - -/// Checks to see if the proof represented within `transcript` is valid, and a -/// point `x` that the polynomial commitment `P` opens purportedly to the value -/// `v`. The provided `msm` should evaluate to the commitment `P` being opened. -pub fn verify_proof<'params, C: CurveAffine, E: EncodedChallenge, T: TranscriptRead>( - params: &'params ParamsIPA, - mut msm: MSMIPA<'params, C>, - transcript: &mut T, - x: C::Scalar, - v: C::Scalar, -) -> Result, Error> { - let k = params.k as usize; - - // P' = P - [v] G_0 + [ξ] S - msm.add_constant_term(-v); // add [-v] G_0 - let s_poly_commitment = transcript.read_point().map_err(|_| Error::OpeningError)?; - let xi = *transcript.squeeze_challenge_scalar::<()>(); - msm.append_term(xi, s_poly_commitment.into()); - - let z = *transcript.squeeze_challenge_scalar::<()>(); - - let mut rounds = vec![]; - for _ in 0..k { - // Read L and R from the proof and write them to the transcript - let l = transcript.read_point().map_err(|_| Error::OpeningError)?; - let r = transcript.read_point().map_err(|_| Error::OpeningError)?; - - let u_j_packed = transcript.squeeze_challenge(); - let u_j = *u_j_packed.as_challenge_scalar::<()>(); - - rounds.push((l, r, u_j, /* to be inverted */ u_j, u_j_packed)); - } - - rounds - .iter_mut() - .map(|&mut (_, _, _, ref mut u_j, _)| u_j) - .batch_invert(); - - // This is the left-hand side of the verifier equation. - // P' + \sum([u_j^{-1}] L_j) + \sum([u_j] R_j) - let mut u = Vec::with_capacity(k); - let mut u_packed: Vec = Vec::with_capacity(k); - for (l, r, u_j, u_j_inv, u_j_packed) in rounds { - msm.append_term(u_j_inv, l.into()); - msm.append_term(u_j, r.into()); - - u.push(u_j); - u_packed.push(u_j_packed.get_scalar()); - } - - // Our goal is to check that the left hand side of the verifier - // equation - // P' + \sum([u_j^{-1}] L_j) + \sum([u_j] R_j) - // equals (given b = \mathbf{b}_0, and the prover's values c, f), - // the right-hand side - // = [c] (G'_0 + [b * z] U) + [f] W - // Subtracting the right-hand side from both sides we get - // P' + \sum([u_j^{-1}] L_j) + \sum([u_j] R_j) - // + [-c] G'_0 + [-cbz] U + [-f] W - // = 0 - // - // Note that the guard returned from this function does not include - // the [-c]G'_0 term. - - let c = transcript.read_scalar().map_err(|_| Error::SamplingError)?; - let neg_c = -c; - let f = transcript.read_scalar().map_err(|_| Error::SamplingError)?; - let b = compute_b(x, &u); - - msm.add_to_u_scalar(neg_c * &b * &z); - msm.add_to_w_scalar(-f); - - let guard = GuardIPA { - msm, - neg_c, - u, - u_packed, - }; - - Ok(guard) -} - -/// Computes $\prod\limits_{i=0}^{k-1} (1 + u_{k - 1 - i} x^{2^i})$. -fn compute_b(x: F, u: &[F]) -> F { - let mut tmp = F::ONE; - let mut cur = x; - for u_j in u.iter().rev() { - tmp *= F::ONE + &(*u_j * &cur); - cur *= cur; - } - tmp -} diff --git a/src/poly/ipa/mod.rs b/src/poly/ipa/mod.rs deleted file mode 100644 index 3600e2f051..0000000000 --- a/src/poly/ipa/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod commitment; -/// Multiscalar multiplication engines -pub mod msm; -/// IPA multi-open scheme -pub mod multiopen; -/// Strategies used with KZG scheme -pub mod strategy; diff --git a/src/poly/ipa/msm.rs b/src/poly/ipa/msm.rs deleted file mode 100644 index a615ddce49..0000000000 --- a/src/poly/ipa/msm.rs +++ /dev/null @@ -1,271 +0,0 @@ -use crate::arithmetic::{best_multiexp, CurveAffine}; -use crate::poly::{commitment::MSM, ipa::commitment::ParamsVerifierIPA}; -use ff::Field; -use group::Group; -use std::collections::BTreeMap; - -/// A multiscalar multiplication in the polynomial commitment scheme -#[derive(Debug, Clone)] -pub struct MSMIPA<'params, C: CurveAffine> { - pub(crate) params: &'params ParamsVerifierIPA, - g_scalars: Option>, - w_scalar: Option, - u_scalar: Option, - // x-coordinate -> (scalar, y-coordinate) - other: BTreeMap, -} - -impl<'a, C: CurveAffine> MSMIPA<'a, C> { - /// Given verifier parameters Creates an empty multi scalar engine - pub fn new(params: &'a ParamsVerifierIPA) -> Self { - let g_scalars = None; - let w_scalar = None; - let u_scalar = None; - let other = BTreeMap::new(); - - Self { - g_scalars, - w_scalar, - u_scalar, - other, - - params, - } - } - - /// Add another multiexp into this one - pub fn add_msm(&mut self, other: &Self) { - for (x, (scalar, y)) in other.other.iter() { - self.other - .entry(*x) - .and_modify(|(our_scalar, our_y)| { - if our_y == y { - *our_scalar += *scalar; - } else { - assert!(*our_y == -*y); - *our_scalar -= *scalar; - } - }) - .or_insert((*scalar, *y)); - } - - if let Some(g_scalars) = &other.g_scalars { - self.add_to_g_scalars(g_scalars); - } - - if let Some(w_scalar) = &other.w_scalar { - self.add_to_w_scalar(*w_scalar); - } - - if let Some(u_scalar) = &other.u_scalar { - self.add_to_u_scalar(*u_scalar); - } - } -} - -impl<'a, C: CurveAffine> MSM for MSMIPA<'a, C> { - fn append_term(&mut self, scalar: C::Scalar, point: C::Curve) { - if !bool::from(point.is_identity()) { - use group::Curve; - let point = point.to_affine(); - let xy = point.coordinates().unwrap(); - let x = *xy.x(); - let y = *xy.y(); - - self.other - .entry(x) - .and_modify(|(our_scalar, our_y)| { - if *our_y == y { - *our_scalar += scalar; - } else { - assert!(*our_y == -y); - *our_scalar -= scalar; - } - }) - .or_insert((scalar, y)); - } - } - - /// Add another multiexp into this one - fn add_msm(&mut self, other: &Self) { - for (x, (scalar, y)) in other.other.iter() { - self.other - .entry(*x) - .and_modify(|(our_scalar, our_y)| { - if our_y == y { - *our_scalar += *scalar; - } else { - assert!(*our_y == -*y); - *our_scalar -= *scalar; - } - }) - .or_insert((*scalar, *y)); - } - - if let Some(g_scalars) = &other.g_scalars { - self.add_to_g_scalars(g_scalars); - } - - if let Some(w_scalar) = &other.w_scalar { - self.add_to_w_scalar(*w_scalar); - } - - if let Some(u_scalar) = &other.u_scalar { - self.add_to_u_scalar(*u_scalar); - } - } - - fn scale(&mut self, factor: C::Scalar) { - if let Some(g_scalars) = &mut self.g_scalars { - for g_scalar in g_scalars { - *g_scalar *= &factor; - } - } - - for other in self.other.values_mut() { - other.0 *= factor; - } - - self.w_scalar = self.w_scalar.map(|a| a * &factor); - self.u_scalar = self.u_scalar.map(|a| a * &factor); - } - - fn check(&self) -> bool { - bool::from(self.eval().is_identity()) - } - - fn eval(&self) -> C::Curve { - let len = self.g_scalars.as_ref().map(|v| v.len()).unwrap_or(0) - + self.w_scalar.map(|_| 1).unwrap_or(0) - + self.u_scalar.map(|_| 1).unwrap_or(0) - + self.other.len(); - let mut scalars: Vec = Vec::with_capacity(len); - let mut bases: Vec = Vec::with_capacity(len); - - scalars.extend(self.other.values().map(|(scalar, _)| scalar)); - bases.extend( - self.other - .iter() - .map(|(x, (_, y))| C::from_xy(*x, *y).unwrap()), - ); - - if let Some(w_scalar) = self.w_scalar { - scalars.push(w_scalar); - bases.push(self.params.w); - } - - if let Some(u_scalar) = self.u_scalar { - scalars.push(u_scalar); - bases.push(self.params.u); - } - - if let Some(g_scalars) = &self.g_scalars { - scalars.extend(g_scalars); - bases.extend(self.params.g.iter()); - } - - assert_eq!(scalars.len(), len); - - best_multiexp(&scalars, &bases) - } - - fn bases(&self) -> Vec { - self.other - .iter() - .map(|(x, (_, y))| C::from_xy(*x, *y).unwrap().into()) - .collect() - } - - fn scalars(&self) -> Vec { - self.other.values().map(|(scalar, _)| *scalar).collect() - } -} - -impl<'a, C: CurveAffine> MSMIPA<'a, C> { - /// Add a value to the first entry of `g_scalars`. - pub fn add_constant_term(&mut self, constant: C::Scalar) { - if let Some(g_scalars) = self.g_scalars.as_mut() { - g_scalars[0] += &constant; - } else { - let mut g_scalars = vec![C::Scalar::ZERO; self.params.n as usize]; - g_scalars[0] += &constant; - self.g_scalars = Some(g_scalars); - } - } - - /// Add a vector of scalars to `g_scalars`. This function will panic if the - /// caller provides a slice of scalars that is not of length `params.n`. - pub fn add_to_g_scalars(&mut self, scalars: &[C::Scalar]) { - assert_eq!(scalars.len(), self.params.n as usize); - if let Some(g_scalars) = &mut self.g_scalars { - for (g_scalar, scalar) in g_scalars.iter_mut().zip(scalars.iter()) { - *g_scalar += scalar; - } - } else { - self.g_scalars = Some(scalars.to_vec()); - } - } - /// Add to `w_scalar` - pub fn add_to_w_scalar(&mut self, scalar: C::Scalar) { - self.w_scalar = self.w_scalar.map_or(Some(scalar), |a| Some(a + &scalar)); - } - - /// Add to `u_scalar` - pub fn add_to_u_scalar(&mut self, scalar: C::Scalar) { - self.u_scalar = self.u_scalar.map_or(Some(scalar), |a| Some(a + &scalar)); - } -} - -#[cfg(test)] -mod tests { - use crate::poly::{ - commitment::{ParamsProver, MSM}, - ipa::{commitment::ParamsIPA, msm::MSMIPA}, - }; - use halo2curves::{ - pasta::{Ep, EpAffine, Fp, Fq}, - CurveAffine, - }; - - #[test] - fn msm_arithmetic() { - let base: Ep = EpAffine::from_xy(-Fp::one(), Fp::from(2)).unwrap().into(); - let base_viol = base + base; - - let params = ParamsIPA::new(4); - let mut a: MSMIPA = MSMIPA::new(¶ms); - a.append_term(Fq::one(), base); - // a = [1] P - assert!(!a.clone().check()); - a.append_term(Fq::one(), base); - // a = [1+1] P - assert!(!a.clone().check()); - a.append_term(-Fq::one(), base_viol); - // a = [1+1] P + [-1] 2P - assert!(a.clone().check()); - let b = a.clone(); - - // Append a point that is the negation of an existing one. - a.append_term(Fq::from(4), -base); - // a = [1+1-4] P + [-1] 2P - assert!(!a.clone().check()); - a.append_term(Fq::from(2), base_viol); - // a = [1+1-4] P + [-1+2] 2P - assert!(a.clone().check()); - - // Add two MSMs with common bases. - a.scale(Fq::from(3)); - a.add_msm(&b); - // a = [3*(1+1)+(1+1-4)] P + [3*(-1)+(-1+2)] 2P - assert!(a.clone().check()); - - let mut c: MSMIPA = MSMIPA::new(¶ms); - c.append_term(Fq::from(2), base); - c.append_term(Fq::one(), -base_viol); - // c = [2] P + [1] (-2P) - assert!(c.clone().check()); - // Add two MSMs with bases that differ only in sign. - a.add_msm(&c); - assert!(a.check()); - } -} diff --git a/src/poly/ipa/multiopen.rs b/src/poly/ipa/multiopen.rs deleted file mode 100644 index b78acb5934..0000000000 --- a/src/poly/ipa/multiopen.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! This module contains an optimisation of the polynomial commitment opening -//! scheme described in the [Halo][halo] paper. -//! -//! [halo]: https://eprint.iacr.org/2019/1021 - -use super::*; -use crate::{poly::query::Query, transcript::ChallengeScalar}; -use ff::Field; -use std::collections::{BTreeMap, BTreeSet}; - -mod prover; -mod verifier; - -pub use prover::ProverIPA; -pub use verifier::VerifierIPA; - -#[derive(Clone, Copy, Debug)] -struct X1 {} -/// Challenge for compressing openings at the same point sets together. -type ChallengeX1 = ChallengeScalar; - -#[derive(Clone, Copy, Debug)] -struct X2 {} -/// Challenge for keeping the multi-point quotient polynomial terms linearly independent. -type ChallengeX2 = ChallengeScalar; - -#[derive(Clone, Copy, Debug)] -struct X3 {} -/// Challenge point at which the commitments are opened. -type ChallengeX3 = ChallengeScalar; - -#[derive(Clone, Copy, Debug)] -struct X4 {} -/// Challenge for collapsing the openings of the various remaining polynomials at x_3 -/// together. -type ChallengeX4 = ChallengeScalar; - -#[derive(Debug)] -struct CommitmentData { - pub(crate) commitment: T, - pub(crate) set_index: usize, - pub(crate) point_indices: Vec, - pub(crate) evals: Vec, -} - -impl CommitmentData { - fn new(commitment: T) -> Self { - CommitmentData { - commitment, - set_index: 0, - point_indices: vec![], - evals: vec![], - } - } -} - -type IntermediateSets = ( - Vec>::Eval, >::Commitment>>, - Vec>, -); - -fn construct_intermediate_sets>(queries: I) -> IntermediateSets -where - I: IntoIterator + Clone, -{ - // Construct sets of unique commitments and corresponding information about - // their queries. - let mut commitment_map: Vec> = vec![]; - - // Also construct mapping from a unique point to a point_index. This defines - // an ordering on the points. - let mut point_index_map = BTreeMap::new(); - - // Iterate over all of the queries, computing the ordering of the points - // while also creating new commitment data. - for query in queries.clone() { - let num_points = point_index_map.len(); - let point_idx = point_index_map - .entry(query.get_point()) - .or_insert(num_points); - - if let Some(pos) = commitment_map - .iter() - .position(|comm| comm.commitment == query.get_commitment()) - { - commitment_map[pos].point_indices.push(*point_idx); - } else { - let mut tmp = CommitmentData::new(query.get_commitment()); - tmp.point_indices.push(*point_idx); - commitment_map.push(tmp); - } - } - - // Also construct inverse mapping from point_index to the point - let mut inverse_point_index_map = BTreeMap::new(); - for (&point, &point_index) in point_index_map.iter() { - inverse_point_index_map.insert(point_index, point); - } - - // Construct map of unique ordered point_idx_sets to their set_idx - let mut point_idx_sets = BTreeMap::new(); - // Also construct mapping from commitment to point_idx_set - let mut commitment_set_map = Vec::new(); - - for commitment_data in commitment_map.iter() { - let mut point_index_set = BTreeSet::new(); - // Note that point_index_set is ordered, unlike point_indices - for &point_index in commitment_data.point_indices.iter() { - point_index_set.insert(point_index); - } - - // Push point_index_set to CommitmentData for the relevant commitment - commitment_set_map.push((commitment_data.commitment, point_index_set.clone())); - - let num_sets = point_idx_sets.len(); - point_idx_sets.entry(point_index_set).or_insert(num_sets); - } - - // Initialise empty evals vec for each unique commitment - for commitment_data in commitment_map.iter_mut() { - let len = commitment_data.point_indices.len(); - commitment_data.evals = vec![Q::Eval::default(); len]; - } - - // Populate set_index, evals and points for each commitment using point_idx_sets - for query in queries { - // The index of the point at which the commitment is queried - let point_index = point_index_map.get(&query.get_point()).unwrap(); - - // The point_index_set at which the commitment was queried - let mut point_index_set = BTreeSet::new(); - for (commitment, point_idx_set) in commitment_set_map.iter() { - if query.get_commitment() == *commitment { - point_index_set = point_idx_set.clone(); - } - } - assert!(!point_index_set.is_empty()); - - // The set_index of the point_index_set - let set_index = point_idx_sets.get(&point_index_set).unwrap(); - for commitment_data in commitment_map.iter_mut() { - if query.get_commitment() == commitment_data.commitment { - commitment_data.set_index = *set_index; - } - } - let point_index_set: Vec = point_index_set.iter().cloned().collect(); - - // The offset of the point_index in the point_index_set - let point_index_in_set = point_index_set - .iter() - .position(|i| i == point_index) - .unwrap(); - - for commitment_data in commitment_map.iter_mut() { - if query.get_commitment() == commitment_data.commitment { - // Insert the eval using the ordering of the point_index_set - commitment_data.evals[point_index_in_set] = query.get_eval(); - } - } - } - - // Get actual points in each point set - let mut point_sets: Vec> = vec![Vec::new(); point_idx_sets.len()]; - for (point_idx_set, &set_idx) in point_idx_sets.iter() { - for &point_idx in point_idx_set.iter() { - let point = inverse_point_index_map.get(&point_idx).unwrap(); - point_sets[set_idx].push(*point); - } - } - - (commitment_map, point_sets) -} diff --git a/src/poly/ipa/multiopen/prover.rs b/src/poly/ipa/multiopen/prover.rs deleted file mode 100644 index 2ae745d457..0000000000 --- a/src/poly/ipa/multiopen/prover.rs +++ /dev/null @@ -1,122 +0,0 @@ -use super::{construct_intermediate_sets, ChallengeX1, ChallengeX2, ChallengeX3, ChallengeX4}; -use crate::arithmetic::{eval_polynomial, kate_division, CurveAffine}; -use crate::poly::commitment::ParamsProver; -use crate::poly::commitment::{Blind, Prover}; -use crate::poly::ipa::commitment::{self, IPACommitmentScheme, ParamsIPA}; -use crate::poly::query::ProverQuery; -use crate::poly::{Coeff, Polynomial}; -use crate::transcript::{EncodedChallenge, TranscriptWrite}; - -use ff::Field; -use group::Curve; -use rand_core::RngCore; -use std::io; -use std::marker::PhantomData; - -/// IPA multi-open prover -#[derive(Debug)] -pub struct ProverIPA<'params, C: CurveAffine> { - pub(crate) params: &'params ParamsIPA, -} - -impl<'params, C: CurveAffine> Prover<'params, IPACommitmentScheme> for ProverIPA<'params, C> { - const QUERY_INSTANCE: bool = true; - - fn new(params: &'params ParamsIPA) -> Self { - Self { params } - } - - /// Create a multi-opening proof - fn create_proof<'com, Z: EncodedChallenge, T: TranscriptWrite, R, I>( - &self, - mut rng: R, - transcript: &mut T, - queries: I, - ) -> io::Result<()> - where - I: IntoIterator> + Clone, - R: RngCore, - { - let x_1: ChallengeX1<_> = transcript.squeeze_challenge_scalar(); - let x_2: ChallengeX2<_> = transcript.squeeze_challenge_scalar(); - - let (poly_map, point_sets) = construct_intermediate_sets(queries); - - // Collapse openings at same point sets together into single openings using - // x_1 challenge. - let mut q_polys: Vec>> = vec![None; point_sets.len()]; - let mut q_blinds = vec![Blind(C::Scalar::ZERO); point_sets.len()]; - - { - let mut accumulate = |set_idx: usize, - new_poly: &Polynomial, - blind: Blind| { - if let Some(poly) = &q_polys[set_idx] { - q_polys[set_idx] = Some(poly.clone() * *x_1 + new_poly); - } else { - q_polys[set_idx] = Some(new_poly.clone()); - } - q_blinds[set_idx] *= *x_1; - q_blinds[set_idx] += blind; - }; - - for commitment_data in poly_map.into_iter() { - accumulate( - commitment_data.set_index, // set_idx, - commitment_data.commitment.poly, // poly, - commitment_data.commitment.blind, // blind, - ); - } - } - - let q_prime_poly = point_sets - .iter() - .zip(q_polys.iter()) - .fold(None, |q_prime_poly, (points, poly)| { - let mut poly = points - .iter() - .fold(poly.clone().unwrap().values, |poly, point| { - kate_division(&poly, *point) - }); - poly.resize(self.params.n as usize, C::Scalar::ZERO); - let poly = Polynomial { - values: poly, - _marker: PhantomData, - }; - - if q_prime_poly.is_none() { - Some(poly) - } else { - q_prime_poly.map(|q_prime_poly| q_prime_poly * *x_2 + &poly) - } - }) - .unwrap(); - - let q_prime_blind = Blind(C::Scalar::random(&mut rng)); - let q_prime_commitment = self.params.commit(&q_prime_poly, q_prime_blind).to_affine(); - - transcript.write_point(q_prime_commitment)?; - - let x_3: ChallengeX3<_> = transcript.squeeze_challenge_scalar(); - - // Prover sends u_i for all i, which correspond to the evaluation - // of each Q polynomial commitment at x_3. - for q_i_poly in &q_polys { - transcript.write_scalar(eval_polynomial(q_i_poly.as_ref().unwrap(), *x_3))?; - } - - let x_4: ChallengeX4<_> = transcript.squeeze_challenge_scalar(); - - let (p_poly, p_poly_blind) = q_polys.into_iter().zip(q_blinds).fold( - (q_prime_poly, q_prime_blind), - |(q_prime_poly, q_prime_blind), (poly, blind)| { - ( - q_prime_poly * *x_4 + &poly.unwrap(), - Blind((q_prime_blind.0 * &(*x_4)) + &blind.0), - ) - }, - ); - - commitment::create_proof(self.params, rng, transcript, &p_poly, p_poly_blind, *x_3) - } -} diff --git a/src/poly/ipa/multiopen/verifier.rs b/src/poly/ipa/multiopen/verifier.rs deleted file mode 100644 index d559e33384..0000000000 --- a/src/poly/ipa/multiopen/verifier.rs +++ /dev/null @@ -1,148 +0,0 @@ -use std::fmt::Debug; - -use ff::Field; - -use super::{construct_intermediate_sets, ChallengeX1, ChallengeX2, ChallengeX3, ChallengeX4}; -use crate::arithmetic::{eval_polynomial, lagrange_interpolate, CurveAffine}; -use crate::poly::commitment::{Params, Verifier, MSM}; -use crate::poly::ipa::commitment::{IPACommitmentScheme, ParamsIPA, ParamsVerifierIPA}; -use crate::poly::ipa::msm::MSMIPA; -use crate::poly::ipa::strategy::GuardIPA; -use crate::poly::query::{CommitmentReference, VerifierQuery}; -use crate::poly::Error; -use crate::transcript::{EncodedChallenge, TranscriptRead}; - -/// IPA multi-open verifier -#[derive(Debug)] -pub struct VerifierIPA<'params, C: CurveAffine> { - params: &'params ParamsIPA, -} - -impl<'params, C: CurveAffine> Verifier<'params, IPACommitmentScheme> - for VerifierIPA<'params, C> -{ - type Guard = GuardIPA<'params, C>; - type MSMAccumulator = MSMIPA<'params, C>; - - const QUERY_INSTANCE: bool = true; - - fn new(params: &'params ParamsVerifierIPA) -> Self { - Self { params } - } - - fn verify_proof<'com, E: EncodedChallenge, T: TranscriptRead, I>( - &self, - transcript: &mut T, - queries: I, - mut msm: MSMIPA<'params, C>, - ) -> Result - where - 'params: 'com, - I: IntoIterator>> + Clone, - { - // Sample x_1 for compressing openings at the same point sets together - let x_1: ChallengeX1<_> = transcript.squeeze_challenge_scalar(); - - // Sample a challenge x_2 for keeping the multi-point quotient - // polynomial terms linearly independent. - let x_2: ChallengeX2<_> = transcript.squeeze_challenge_scalar(); - - let (commitment_map, point_sets) = construct_intermediate_sets(queries); - - // Compress the commitments and expected evaluations at x together. - // using the challenge x_1 - let mut q_commitments: Vec<_> = vec![ - (self.params.empty_msm(), C::Scalar::ONE); // (accumulator, next x_1 power). - point_sets.len()]; - - // A vec of vecs of evals. The outer vec corresponds to the point set, - // while the inner vec corresponds to the points in a particular set. - let mut q_eval_sets = Vec::with_capacity(point_sets.len()); - for point_set in point_sets.iter() { - q_eval_sets.push(vec![C::Scalar::ZERO; point_set.len()]); - } - - { - let mut accumulate = |set_idx: usize, - new_commitment: CommitmentReference>, - evals: Vec| { - let (q_commitment, x_1_power) = &mut q_commitments[set_idx]; - match new_commitment { - CommitmentReference::Commitment(c) => { - q_commitment.append_term(*x_1_power, (*c).into()); - } - CommitmentReference::MSM(msm) => { - let mut msm = msm.clone(); - msm.scale(*x_1_power); - q_commitment.add_msm(&msm); - } - } - for (eval, set_eval) in evals.iter().zip(q_eval_sets[set_idx].iter_mut()) { - *set_eval += (*eval) * (*x_1_power); - } - *x_1_power *= *x_1; - }; - - // Each commitment corresponds to evaluations at a set of points. - // For each set, we collapse each commitment's evals pointwise. - // Run in order of increasing x_1 powers. - for commitment_data in commitment_map.into_iter().rev() { - accumulate( - commitment_data.set_index, // set_idx, - commitment_data.commitment, // commitment, - commitment_data.evals, // evals - ); - } - } - - // Obtain the commitment to the multi-point quotient polynomial f(X). - let q_prime_commitment = transcript.read_point().map_err(|_| Error::SamplingError)?; - - // Sample a challenge x_3 for checking that f(X) was committed to - // correctly. - let x_3: ChallengeX3<_> = transcript.squeeze_challenge_scalar(); - - // u is a vector containing the evaluations of the Q polynomial - // commitments at x_3 - let mut u = Vec::with_capacity(q_eval_sets.len()); - for _ in 0..q_eval_sets.len() { - u.push(transcript.read_scalar().map_err(|_| Error::SamplingError)?); - } - - // We can compute the expected msm_eval at x_3 using the u provided - // by the prover and from x_2 - let msm_eval = point_sets - .iter() - .zip(q_eval_sets.iter()) - .zip(u.iter()) - .fold( - C::Scalar::ZERO, - |msm_eval, ((points, evals), proof_eval)| { - let r_poly = lagrange_interpolate(points, evals); - let r_eval = eval_polynomial(&r_poly, *x_3); - let eval = points.iter().fold(*proof_eval - &r_eval, |eval, point| { - eval * &(*x_3 - point).invert().unwrap() - }); - msm_eval * &(*x_2) + &eval - }, - ); - - // Sample a challenge x_4 that we will use to collapse the openings of - // the various remaining polynomials at x_3 together. - let x_4: ChallengeX4<_> = transcript.squeeze_challenge_scalar(); - - // Compute the final commitment that has to be opened - msm.append_term(C::Scalar::ONE, q_prime_commitment.into()); - let (msm, v) = q_commitments.into_iter().zip(u.iter()).fold( - (msm, msm_eval), - |(mut msm, msm_eval), ((q_commitment, _), q_eval)| { - msm.scale(*x_4); - msm.add_msm(&q_commitment); - (msm, msm_eval * &(*x_4) + q_eval) - }, - ); - - // Verify the opening proof - super::commitment::verify_proof(self.params, msm, transcript, *x_3, v) - } -} diff --git a/src/poly/ipa/strategy.rs b/src/poly/ipa/strategy.rs deleted file mode 100644 index d2d1b3d364..0000000000 --- a/src/poly/ipa/strategy.rs +++ /dev/null @@ -1,171 +0,0 @@ -use super::commitment::{IPACommitmentScheme, ParamsIPA}; -use super::msm::MSMIPA; -use super::multiopen::VerifierIPA; -use crate::{ - arithmetic::best_multiexp, - plonk::Error, - poly::{ - commitment::MSM, - strategy::{Guard, VerificationStrategy}, - }, -}; -use ff::Field; -use group::Curve; -use halo2curves::CurveAffine; -use rand_core::OsRng; - -/// Wrapper for verification accumulator -#[derive(Debug, Clone)] -pub struct GuardIPA<'params, C: CurveAffine> { - pub(crate) msm: MSMIPA<'params, C>, - pub(crate) neg_c: C::Scalar, - pub(crate) u: Vec, - pub(crate) u_packed: Vec, -} - -/// An accumulator instance consisting of an evaluation claim and a proof. -#[derive(Debug, Clone)] -pub struct Accumulator { - /// The claimed output of the linear-time polycommit opening protocol - pub g: C, - - /// A vector of challenges u_0, ..., u_{k - 1} sampled by the verifier, to - /// be used in computing G'_0. - pub u_packed: Vec, -} - -/// Define accumulator type as `MSMIPA` -impl<'params, C: CurveAffine> Guard> for GuardIPA<'params, C> { - type MSMAccumulator = MSMIPA<'params, C>; -} - -/// IPA specific operations -impl<'params, C: CurveAffine> GuardIPA<'params, C> { - /// Lets caller supply the challenges and obtain an MSM with updated - /// scalars and points. - pub fn use_challenges(mut self) -> MSMIPA<'params, C> { - let s = compute_s(&self.u, self.neg_c); - self.msm.add_to_g_scalars(&s); - - self.msm - } - - /// Lets caller supply the purported G point and simply appends - /// [-c] G to return an updated MSM. - pub fn use_g(mut self, g: C) -> (MSMIPA<'params, C>, Accumulator) { - self.msm.append_term(self.neg_c, g.into()); - - let accumulator = Accumulator { - g, - u_packed: self.u_packed, - }; - - (self.msm, accumulator) - } - - /// Computes G = ⟨s, params.g⟩ - pub fn compute_g(&self) -> C { - let s = compute_s(&self.u, C::Scalar::ONE); - - best_multiexp(&s, &self.msm.params.g).to_affine() - } -} - -/// A verifier that checks multiple proofs in a batch. -#[derive(Debug)] -pub struct AccumulatorStrategy<'params, C: CurveAffine> { - msm: MSMIPA<'params, C>, -} - -impl<'params, C: CurveAffine> - VerificationStrategy<'params, IPACommitmentScheme, VerifierIPA<'params, C>> - for AccumulatorStrategy<'params, C> -{ - type Output = Self; - - fn new(params: &'params ParamsIPA) -> Self { - AccumulatorStrategy { - msm: MSMIPA::new(params), - } - } - - fn process( - mut self, - f: impl FnOnce(MSMIPA<'params, C>) -> Result, Error>, - ) -> Result { - self.msm.scale(C::Scalar::random(OsRng)); - let guard = f(self.msm)?; - - Ok(Self { - msm: guard.use_challenges(), - }) - } - - /// Finalizes the batch and checks its validity. - /// - /// Returns `false` if *some* proof was invalid. If the caller needs to identify - /// specific failing proofs, it must re-process the proofs separately. - #[must_use] - fn finalize(self) -> bool { - self.msm.check() - } -} - -/// A verifier that checks single proof -#[derive(Debug)] -pub struct SingleStrategy<'params, C: CurveAffine> { - msm: MSMIPA<'params, C>, -} - -impl<'params, C: CurveAffine> - VerificationStrategy<'params, IPACommitmentScheme, VerifierIPA<'params, C>> - for SingleStrategy<'params, C> -{ - type Output = (); - - fn new(params: &'params ParamsIPA) -> Self { - SingleStrategy { - msm: MSMIPA::new(params), - } - } - - fn process( - self, - f: impl FnOnce(MSMIPA<'params, C>) -> Result, Error>, - ) -> Result { - let guard = f(self.msm)?; - let msm = guard.use_challenges(); - if msm.check() { - Ok(()) - } else { - Err(Error::ConstraintSystemFailure) - } - } - - /// Finalizes the batch and checks its validity. - /// - /// Returns `false` if *some* proof was invalid. If the caller needs to identify - /// specific failing proofs, it must re-process the proofs separately. - #[must_use] - fn finalize(self) -> bool { - unreachable!() - } -} - -/// Computes the coefficients of $g(X) = \prod\limits_{i=0}^{k-1} (1 + u_{k - 1 - i} X^{2^i})$. -fn compute_s(u: &[F], init: F) -> Vec { - assert!(!u.is_empty()); - let mut v = vec![F::ZERO; 1 << u.len()]; - v[0] = init; - - for (len, u_j) in u.iter().rev().enumerate().map(|(i, u_j)| (1 << i, u_j)) { - let (left, right) = v.split_at_mut(len); - let right = &mut right[0..len]; - right.copy_from_slice(left); - for v in right { - *v *= u_j; - } - } - - v -} diff --git a/src/poly/multiopen_test.rs b/src/poly/multiopen_test.rs index 47c6731167..b56563841c 100644 --- a/src/poly/multiopen_test.rs +++ b/src/poly/multiopen_test.rs @@ -11,87 +11,13 @@ mod test { EvaluationDomain, }; use crate::transcript::{ - Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge, Keccak256Read, Keccak256Write, - TranscriptReadBuffer, TranscriptWriterBuffer, + Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge, TranscriptReadBuffer, + TranscriptWriterBuffer, }; use ff::WithSmallOrderMulGroup; use group::Curve; use rand_core::OsRng; - #[test] - fn test_roundtrip_ipa() { - use crate::poly::ipa::commitment::{IPACommitmentScheme, ParamsIPA}; - use crate::poly::ipa::multiopen::{ProverIPA, VerifierIPA}; - use crate::poly::ipa::strategy::AccumulatorStrategy; - use halo2curves::pasta::EqAffine; - - const K: u32 = 4; - - let params = ParamsIPA::::new(K); - - let proof = create_proof::< - IPACommitmentScheme, - ProverIPA<_>, - _, - Blake2bWrite<_, _, Challenge255<_>>, - >(¶ms); - - let verifier_params = params.verifier_params(); - - verify::< - IPACommitmentScheme, - VerifierIPA<_>, - _, - Blake2bRead<_, _, Challenge255<_>>, - AccumulatorStrategy<_>, - >(verifier_params, &proof[..], false); - - verify::< - IPACommitmentScheme, - VerifierIPA<_>, - _, - Blake2bRead<_, _, Challenge255<_>>, - AccumulatorStrategy<_>, - >(verifier_params, &proof[..], true); - } - - #[test] - fn test_roundtrip_ipa_keccak() { - use crate::poly::ipa::commitment::{IPACommitmentScheme, ParamsIPA}; - use crate::poly::ipa::multiopen::{ProverIPA, VerifierIPA}; - use crate::poly::ipa::strategy::AccumulatorStrategy; - use halo2curves::pasta::EqAffine; - - const K: u32 = 4; - - let params = ParamsIPA::::new(K); - - let proof = create_proof::< - IPACommitmentScheme, - ProverIPA<_>, - _, - Keccak256Write<_, _, Challenge255<_>>, - >(¶ms); - - let verifier_params = params.verifier_params(); - - verify::< - IPACommitmentScheme, - VerifierIPA<_>, - _, - Keccak256Read<_, _, Challenge255<_>>, - AccumulatorStrategy<_>, - >(verifier_params, &proof[..], false); - - verify::< - IPACommitmentScheme, - VerifierIPA<_>, - _, - Keccak256Read<_, _, Challenge255<_>>, - AccumulatorStrategy<_>, - >(verifier_params, &proof[..], true); - } - #[test] fn test_roundtrip_gwc() { use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; @@ -273,17 +199,14 @@ mod test { ProverQuery { point: x.get_scalar(), poly: &ax, - blind, }, ProverQuery { point: x.get_scalar(), poly: &bx, - blind, }, ProverQuery { point: y.get_scalar(), poly: &cx, - blind, }, ] .to_vec(); diff --git a/src/poly/query.rs b/src/poly/query.rs index bc7a20c240..6fa214fce2 100644 --- a/src/poly/query.rs +++ b/src/poly/query.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use super::commitment::{Blind, MSM}; +use super::commitment::MSM; use crate::{ arithmetic::eval_polynomial, poly::{Coeff, Polynomial}, @@ -23,8 +23,6 @@ pub struct ProverQuery<'com, C: CurveAffine> { pub(crate) point: C::Scalar, /// Coefficients of polynomial pub(crate) poly: &'com Polynomial, - /// Blinding factor of polynomial - pub(crate) blind: Blind, } impl<'com, C> ProverQuery<'com, C> @@ -32,12 +30,8 @@ where C: CurveAffine, { /// Create a new prover query based on a polynomial - pub fn new( - point: C::Scalar, - poly: &'com Polynomial, - blind: Blind, - ) -> Self { - ProverQuery { point, poly, blind } + pub fn new(point: C::Scalar, poly: &'com Polynomial) -> Self { + ProverQuery { point, poly } } } @@ -45,7 +39,6 @@ where #[derive(Copy, Clone)] pub struct PolynomialPointer<'com, C: CurveAffine> { pub(crate) poly: &'com Polynomial, - pub(crate) blind: Blind, } impl<'com, C: CurveAffine> PartialEq for PolynomialPointer<'com, C> { @@ -65,10 +58,7 @@ impl<'com, C: CurveAffine> Query for ProverQuery<'com, C> { eval_polynomial(&self.poly[..], self.get_point()) } fn get_commitment(&self) -> Self::Commitment { - PolynomialPointer { - poly: self.poly, - blind: self.blind, - } + PolynomialPointer { poly: self.poly } } } diff --git a/tests/plonk_api.rs b/tests/plonk_api.rs index 28ffb399ff..dbfb9a6155 100644 --- a/tests/plonk_api.rs +++ b/tests/plonk_api.rs @@ -589,438 +589,6 @@ fn plonk_api() { >(verifier_params, pk.get_vk(), &proof[..]); } - fn test_plonk_api_ipa() { - use halo2_proofs::poly::ipa::commitment::{IPACommitmentScheme, ParamsIPA}; - use halo2_proofs::poly::ipa::multiopen::{ProverIPA, VerifierIPA}; - use halo2_proofs::poly::ipa::strategy::AccumulatorStrategy; - use halo2curves::pasta::EqAffine; - - type Scheme = IPACommitmentScheme; - bad_keys!(Scheme); - - let params = ParamsIPA::::new(K); - let rng = OsRng; - - let pk = keygen::>(¶ms); - - let proof = create_proof::<_, ProverIPA<_>, _, _, Blake2bWrite<_, _, Challenge255<_>>>( - rng, ¶ms, &pk, - ); - - let verifier_params = params.verifier_params(); - - verify_proof::< - _, - VerifierIPA<_>, - _, - Blake2bRead<_, _, Challenge255<_>>, - AccumulatorStrategy<_>, - >(verifier_params, pk.get_vk(), &proof[..]); - - // Check that the verification key has not changed unexpectedly - { - //panic!("{:#?}", pk.get_vk().pinned()); - assert_eq!( - format!("{:#?}", pk.get_vk().pinned()), - r#####"PinnedVerificationKey { - base_modulus: "0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001", - scalar_modulus: "0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001", - domain: PinnedEvaluationDomain { - k: 5, - extended_k: 7, - omega: 0x0cc3380dc616f2e1daf29ad1560833ed3baea3393eceb7bc8fa36376929b78cc, - }, - cs: PinnedConstraintSystem { - num_fixed_columns: 7, - num_advice_columns: 5, - num_instance_columns: 1, - num_selectors: 0, - gates: [ - Sum( - Sum( - Sum( - Sum( - Product( - Advice { - query_index: 0, - column_index: 1, - rotation: Rotation( - 0, - ), - }, - Fixed { - query_index: 2, - column_index: 2, - rotation: Rotation( - 0, - ), - }, - ), - Product( - Advice { - query_index: 1, - column_index: 2, - rotation: Rotation( - 0, - ), - }, - Fixed { - query_index: 3, - column_index: 3, - rotation: Rotation( - 0, - ), - }, - ), - ), - Product( - Product( - Advice { - query_index: 0, - column_index: 1, - rotation: Rotation( - 0, - ), - }, - Advice { - query_index: 1, - column_index: 2, - rotation: Rotation( - 0, - ), - }, - ), - Fixed { - query_index: 5, - column_index: 1, - rotation: Rotation( - 0, - ), - }, - ), - ), - Negated( - Product( - Advice { - query_index: 2, - column_index: 3, - rotation: Rotation( - 0, - ), - }, - Fixed { - query_index: 4, - column_index: 4, - rotation: Rotation( - 0, - ), - }, - ), - ), - ), - Product( - Fixed { - query_index: 1, - column_index: 0, - rotation: Rotation( - 0, - ), - }, - Product( - Advice { - query_index: 3, - column_index: 4, - rotation: Rotation( - 1, - ), - }, - Advice { - query_index: 4, - column_index: 0, - rotation: Rotation( - -1, - ), - }, - ), - ), - ), - Product( - Fixed { - query_index: 6, - column_index: 5, - rotation: Rotation( - 0, - ), - }, - Sum( - Advice { - query_index: 0, - column_index: 1, - rotation: Rotation( - 0, - ), - }, - Negated( - Instance { - query_index: 0, - column_index: 0, - rotation: Rotation( - 0, - ), - }, - ), - ), - ), - ], - advice_queries: [ - ( - Column { - index: 1, - column_type: Advice, - }, - Rotation( - 0, - ), - ), - ( - Column { - index: 2, - column_type: Advice, - }, - Rotation( - 0, - ), - ), - ( - Column { - index: 3, - column_type: Advice, - }, - Rotation( - 0, - ), - ), - ( - Column { - index: 4, - column_type: Advice, - }, - Rotation( - 1, - ), - ), - ( - Column { - index: 0, - column_type: Advice, - }, - Rotation( - -1, - ), - ), - ( - Column { - index: 0, - column_type: Advice, - }, - Rotation( - 0, - ), - ), - ( - Column { - index: 4, - column_type: Advice, - }, - Rotation( - 0, - ), - ), - ], - instance_queries: [ - ( - Column { - index: 0, - column_type: Instance, - }, - Rotation( - 0, - ), - ), - ], - fixed_queries: [ - ( - Column { - index: 6, - column_type: Fixed, - }, - Rotation( - 0, - ), - ), - ( - Column { - index: 0, - column_type: Fixed, - }, - Rotation( - 0, - ), - ), - ( - Column { - index: 2, - column_type: Fixed, - }, - Rotation( - 0, - ), - ), - ( - Column { - index: 3, - column_type: Fixed, - }, - Rotation( - 0, - ), - ), - ( - Column { - index: 4, - column_type: Fixed, - }, - Rotation( - 0, - ), - ), - ( - Column { - index: 1, - column_type: Fixed, - }, - Rotation( - 0, - ), - ), - ( - Column { - index: 5, - column_type: Fixed, - }, - Rotation( - 0, - ), - ), - ], - permutation: Argument { - columns: [ - Column { - index: 1, - column_type: Advice, - }, - Column { - index: 2, - column_type: Advice, - }, - Column { - index: 3, - column_type: Advice, - }, - Column { - index: 0, - column_type: Fixed, - }, - Column { - index: 0, - column_type: Advice, - }, - Column { - index: 4, - column_type: Advice, - }, - Column { - index: 0, - column_type: Instance, - }, - Column { - index: 1, - column_type: Fixed, - }, - Column { - index: 2, - column_type: Fixed, - }, - Column { - index: 3, - column_type: Fixed, - }, - Column { - index: 4, - column_type: Fixed, - }, - Column { - index: 5, - column_type: Fixed, - }, - ], - }, - lookups: [ - Argument { - input_expressions: [ - Advice { - query_index: 0, - column_index: 1, - rotation: Rotation( - 0, - ), - }, - ], - table_expressions: [ - Fixed { - query_index: 0, - column_index: 6, - rotation: Rotation( - 0, - ), - }, - ], - }, - ], - constants: [], - minimum_degree: None, - }, - fixed_commitments: [ - (0x2bbc94ef7b22aebef24f9a4b0cc1831882548b605171366017d45c3e6fd92075, 0x082b801a6e176239943bfb759fb02138f47a5c8cc4aa7fa0af559fde4e3abd97), - (0x2bf5082b105b2156ed0e9c5b8e42bf2a240b058f74a464d080e9585274dd1e84, 0x222ad83cee7777e7a160585e212140e5e770dd8d1df788d869b5ee483a5864fb), - (0x374a656456a0aae7429b23336f825752b575dd5a44290ff614946ee59d6a20c0, 0x054491e187e6e3460e7601fb54ae10836d34d420026f96316f0c5c62f86db9b8), - (0x374a656456a0aae7429b23336f825752b575dd5a44290ff614946ee59d6a20c0, 0x054491e187e6e3460e7601fb54ae10836d34d420026f96316f0c5c62f86db9b8), - (0x02e62cd68370b13711139a08cbcdd889e800a272b9ea10acc90880fff9d89199, 0x1a96c468cb0ce77065d3a58f1e55fea9b72d15e44c01bba1e110bd0cbc6e9bc6), - (0x224ef42758215157d3ee48fb8d769da5bddd35e5929a90a4a89736f5c4b5ae9b, 0x11bc3a1e08eb320cde764f1492ecef956d71e996e2165f7a9a30ad2febb511c1), - (0x2d5415bf917fcac32bfb705f8ca35cb12d9bad52aa33ccca747350f9235d3a18, 0x2b2921f815fad504052512743963ef20ed5b401d20627793b006413e73fe4dd4), - ], - permutation: VerifyingKey { - commitments: [ - (0x1347b4b385837977a96b87f199c6a9a81520015539d1e8fa79429bb4ca229a00, 0x2168e404cabef513654d6ff516cde73f0ba87e3dc84e4b940ed675b5f66f3884), - (0x0e6d69cd2455ec43be640f6397ed65c9e51b1d8c0fd2216339314ff37ade122a, 0x222ed6dc8cfc9ea26dcc10b9d4add791ada60f2b5a63ee1e4635f88aa0c96654), - (0x13c447846f48c41a5e0675ccf88ebc0cdef2c96c51446d037acb866d24255785, 0x1f0b5414fc5e8219dbfab996eed6129d831488b2386a8b1a63663938903bd63a), - (0x1aae6470aa662b8fda003894ddef5fedd03af318b3231683039d2fac9cab05b9, 0x08832d91ae69e99cd07d096c7a4a284a69e6a16227cbb07932a0cdc56914f3a6), - (0x0850521b0f8ac7dd0550fe3e25c840837076e9635067ed623b81d5cbac5944d9, 0x0c25d65d1038d0a92c72e5fccd96c1caf07801c3c8233290bb292e0c38c256fa), - (0x12febcf696badd970750eabf75dd3ced4c2f54f93519bcee23849025177d2014, 0x0a05ab3cd42c9fbcc1bbfcf9269951640cc9920761c87cf8e211ba73c8d9f90f), - (0x053904bdde8cfead3b517bb4f6ded3e699f8b94ca6156a9dd2f92a2a05a7ec5a, 0x16753ff97c0d82ff586bb7a07bf7f27a92df90b3617fa5e75d4f55c3b0ef8711), - (0x3804548f6816452747a5b542fa5656353fc989db40d69e9e27d6f973b5deebb0, 0x389a44d5037866dd83993af75831a5f90a18ad5244255aa5bd2c922cc5853055), - (0x003a9f9ca71c7c0b832c802220915f6fc8d840162bdde6b0ea05d25fb95559e3, 0x091247ca19d6b73887cd7f68908cbf0db0b47459b7c82276bbdb8a1c937e2438), - (0x3eaa38689d9e391c8a8fafab9568f20c45816321d38f309d4cc37f4b1601af72, 0x247f8270a462ea88450221a56aa6b55d2bc352b80b03501e99ea983251ceea13), - (0x394437571f9de32dccdc546fd4737772d8d92593c85438aa3473243997d5acc8, 0x14924ec6e3174f1fab7f0ce7070c22f04bbd0a0ecebdfc5c94be857f25493e95), - (0x3d907e0591343bd285c2c846f3e871a6ac70d80ec29e9500b8cb57f544e60202, 0x1034e48df35830244cabea076be8a16d67d7896e27c6ac22b285d017105da9c3), - ], - }, -}"##### - ); - } - } - - test_plonk_api_ipa(); test_plonk_api_gwc(); test_plonk_api_shplonk(); } From ee29ba059781a242f7c959a622e0a619b553e431 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Fri, 29 Nov 2024 12:58:05 +0100 Subject: [PATCH 04/20] Remove SHPLONK --- benches/plonk.rs | 2 +- examples/serialization.rs | 2 +- examples/shuffle.rs | 2 +- examples/vector-ops-unblinded.rs | 2 +- src/arithmetic.rs | 20 -- src/plonk/prover.rs | 6 +- src/poly/kzg/{multiopen => }/gwc.rs | 0 src/poly/kzg/{multiopen => }/gwc/prover.rs | 0 src/poly/kzg/{multiopen => }/gwc/verifier.rs | 0 src/poly/kzg/mod.rs | 4 +- src/poly/kzg/msm.rs | 39 --- src/poly/kzg/multiopen.rs | 5 - src/poly/kzg/multiopen/shplonk.rs | 247 --------------- src/poly/kzg/multiopen/shplonk/prover.rs | 298 ------------------- src/poly/kzg/multiopen/shplonk/verifier.rs | 140 --------- src/poly/multiopen_test.rs | 39 +-- tests/plonk_api.rs | 72 ++--- 17 files changed, 28 insertions(+), 850 deletions(-) rename src/poly/kzg/{multiopen => }/gwc.rs (100%) rename src/poly/kzg/{multiopen => }/gwc/prover.rs (100%) rename src/poly/kzg/{multiopen => }/gwc/verifier.rs (100%) delete mode 100644 src/poly/kzg/multiopen.rs delete mode 100644 src/poly/kzg/multiopen/shplonk.rs delete mode 100644 src/poly/kzg/multiopen/shplonk/prover.rs delete mode 100644 src/poly/kzg/multiopen/shplonk/verifier.rs diff --git a/benches/plonk.rs b/benches/plonk.rs index 97dccbe1bd..bce3a63db6 100644 --- a/benches/plonk.rs +++ b/benches/plonk.rs @@ -15,7 +15,7 @@ use std::marker::PhantomData; use criterion::{BenchmarkId, Criterion}; use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; -use halo2_proofs::poly::kzg::multiopen::{ProverGWC, VerifierGWC}; +use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; use halo2_proofs::poly::kzg::strategy::SingleStrategy; fn criterion_benchmark(c: &mut Criterion) { diff --git a/examples/serialization.rs b/examples/serialization.rs index 39b6b1192f..c4a84b7281 100644 --- a/examples/serialization.rs +++ b/examples/serialization.rs @@ -13,7 +13,7 @@ use halo2_proofs::{ poly::{ kzg::{ commitment::{KZGCommitmentScheme, ParamsKZG}, - multiopen::{ProverGWC, VerifierGWC}, + gwc::{ProverGWC, VerifierGWC}, strategy::SingleStrategy, }, Rotation, diff --git a/examples/shuffle.rs b/examples/shuffle.rs index 26fbd887f7..13c1cfba93 100644 --- a/examples/shuffle.rs +++ b/examples/shuffle.rs @@ -2,7 +2,7 @@ use ff::{BatchInvert, FromUniformBytes, WithSmallOrderMulGroup}; use halo2_proofs::arithmetic::CurveExt; use halo2_proofs::helpers::SerdeCurveAffine; use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; -use halo2_proofs::poly::kzg::multiopen::{ProverGWC, VerifierGWC}; +use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; use halo2_proofs::{ arithmetic::Field, diff --git a/examples/vector-ops-unblinded.rs b/examples/vector-ops-unblinded.rs index 65d5aaa8d5..b8373c8b1e 100644 --- a/examples/vector-ops-unblinded.rs +++ b/examples/vector-ops-unblinded.rs @@ -7,7 +7,7 @@ use ff::{FromUniformBytes, WithSmallOrderMulGroup}; use halo2_proofs::arithmetic::CurveExt; use halo2_proofs::helpers::SerdeCurveAffine; use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; -use halo2_proofs::poly::kzg::multiopen::{ProverGWC, VerifierGWC}; +use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; use halo2_proofs::{ arithmetic::Field, diff --git a/src/arithmetic.rs b/src/arithmetic.rs index 0163e355eb..9cd65dc4d1 100644 --- a/src/arithmetic.rs +++ b/src/arithmetic.rs @@ -503,26 +503,6 @@ pub fn lagrange_interpolate(points: &[F], evals: &[F]) -> Vec { } } -pub(crate) fn evaluate_vanishing_polynomial(roots: &[F], z: F) -> F { - fn evaluate(roots: &[F], z: F) -> F { - roots.iter().fold(F::ONE, |acc, point| (z - point) * acc) - } - let n = roots.len(); - let num_threads = multicore::current_num_threads(); - if n * 2 < num_threads { - evaluate(roots, z) - } else { - let chunk_size = (n + num_threads - 1) / num_threads; - let mut parts = vec![F::ONE; num_threads]; - multicore::scope(|scope| { - for (out, roots) in parts.chunks_mut(1).zip(roots.chunks(chunk_size)) { - scope.spawn(move |_| out[0] = evaluate(roots, z)); - } - }); - parts.iter().fold(F::ONE, |acc, part| acc * part) - } -} - pub(crate) fn powers(base: F) -> impl Iterator { std::iter::successors(Some(F::ONE), move |power| Some(base * power)) } diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 61c18ea593..50cbd10ab7 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -675,7 +675,7 @@ fn test_create_proof() { plonk::{keygen_pk, keygen_vk}, poly::kzg::{ commitment::{KZGCommitmentScheme, ParamsKZG}, - multiopen::ProverSHPLONK, + gwc::ProverGWC, }, transcript::{Blake2bWrite, Challenge255, TranscriptWriterBuffer}, }; @@ -712,7 +712,7 @@ fn test_create_proof() { let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); // Create proof with wrong number of instances - let proof = create_proof::, ProverSHPLONK<_>, _, _, _, _>( + let proof = create_proof::, ProverGWC<_>, _, _, _, _>( ¶ms, &pk, &[MyCircuit, MyCircuit], @@ -723,7 +723,7 @@ fn test_create_proof() { assert!(matches!(proof.unwrap_err(), Error::InvalidInstances)); // Create proof with correct number of instances - create_proof::, ProverSHPLONK<_>, _, _, _, _>( + create_proof::, ProverGWC<_>, _, _, _, _>( ¶ms, &pk, &[MyCircuit, MyCircuit], diff --git a/src/poly/kzg/multiopen/gwc.rs b/src/poly/kzg/gwc.rs similarity index 100% rename from src/poly/kzg/multiopen/gwc.rs rename to src/poly/kzg/gwc.rs diff --git a/src/poly/kzg/multiopen/gwc/prover.rs b/src/poly/kzg/gwc/prover.rs similarity index 100% rename from src/poly/kzg/multiopen/gwc/prover.rs rename to src/poly/kzg/gwc/prover.rs diff --git a/src/poly/kzg/multiopen/gwc/verifier.rs b/src/poly/kzg/gwc/verifier.rs similarity index 100% rename from src/poly/kzg/multiopen/gwc/verifier.rs rename to src/poly/kzg/gwc/verifier.rs diff --git a/src/poly/kzg/mod.rs b/src/poly/kzg/mod.rs index 0c99a20c34..de86e9b774 100644 --- a/src/poly/kzg/mod.rs +++ b/src/poly/kzg/mod.rs @@ -1,8 +1,8 @@ /// KZG commitment scheme pub mod commitment; +/// KZG multi-open scheme +pub mod gwc; /// Multiscalar multiplication engines pub mod msm; -/// KZG multi-open scheme -pub mod multiopen; /// Strategies used with KZG scheme pub mod strategy; diff --git a/src/poly/kzg/msm.rs b/src/poly/kzg/msm.rs index f9b8c284bd..d7cf75ba13 100644 --- a/src/poly/kzg/msm.rs +++ b/src/poly/kzg/msm.rs @@ -93,45 +93,6 @@ where } } -/// A projective point collector -#[derive(Debug, Clone)] -pub(crate) struct PreMSM -where - E::G1Affine: CurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, -{ - projectives_msms: Vec>, -} - -impl PreMSM -where - E::G1Affine: CurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, -{ - pub(crate) fn new() -> Self { - PreMSM { - projectives_msms: vec![], - } - } - - pub(crate) fn normalize(self) -> MSMKZG { - let (scalars, bases) = self - .projectives_msms - .into_iter() - .map(|msm| (msm.scalars, msm.bases)) - .unzip::<_, _, Vec<_>, Vec<_>>(); - - MSMKZG { - scalars: scalars.into_iter().flatten().collect(), - bases: bases.into_iter().flatten().collect(), - } - } - - pub(crate) fn add_msm(&mut self, other: MSMKZG) { - self.projectives_msms.push(other); - } -} - impl<'params, E: MultiMillerLoop + Debug> From<&'params ParamsKZG> for DualMSM<'params, E> where E::G1Affine: CurveAffine::Fr, CurveExt = ::G1>, diff --git a/src/poly/kzg/multiopen.rs b/src/poly/kzg/multiopen.rs deleted file mode 100644 index 97b7e2b777..0000000000 --- a/src/poly/kzg/multiopen.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod gwc; -mod shplonk; - -pub use gwc::*; -pub use shplonk::*; diff --git a/src/poly/kzg/multiopen/shplonk.rs b/src/poly/kzg/multiopen/shplonk.rs deleted file mode 100644 index d0814e83e3..0000000000 --- a/src/poly/kzg/multiopen/shplonk.rs +++ /dev/null @@ -1,247 +0,0 @@ -mod prover; -mod verifier; - -use crate::multicore::{IntoParallelIterator, ParallelIterator}; -use crate::{poly::query::Query, transcript::ChallengeScalar}; -use ff::Field; -pub use prover::ProverSHPLONK; -use std::collections::BTreeSet; -pub use verifier::VerifierSHPLONK; - -#[derive(Clone, Copy, Debug)] -struct U {} -type ChallengeU = ChallengeScalar; - -#[derive(Clone, Copy, Debug)] -struct V {} -type ChallengeV = ChallengeScalar; - -#[derive(Clone, Copy, Debug)] -struct Y {} -type ChallengeY = ChallengeScalar; - -#[derive(Debug, Clone, PartialEq)] -struct Commitment((T, Vec)); - -impl Commitment { - fn get(&self) -> T { - self.0 .0.clone() - } - - fn evals(&self) -> Vec { - self.0 .1.clone() - } -} - -#[derive(Debug, Clone, PartialEq)] -struct RotationSet { - commitments: Vec>, - points: Vec, -} - -#[derive(Debug, PartialEq)] -struct IntermediateSets> { - rotation_sets: Vec>, - super_point_set: BTreeSet, -} - -fn construct_intermediate_sets>( - queries: I, -) -> IntermediateSets -where - I: IntoIterator + Clone, -{ - let queries = queries.into_iter().collect::>(); - - // Find evaluation of a commitment at a rotation - let get_eval = |commitment: Q::Commitment, rotation: F| -> F { - queries - .iter() - .find(|query| query.get_commitment() == commitment && query.get_point() == rotation) - .unwrap() - .get_eval() - }; - - // All points that appear in queries - let mut super_point_set = BTreeSet::new(); - - // Collect rotation sets for each commitment - // Example elements in the vector: - // (C_0, {r_5}), - // (C_1, {r_1, r_2, r_3}), - // (C_2, {r_2, r_3, r_4}), - // (C_3, {r_2, r_3, r_4}), - // ... - let mut commitment_rotation_set_map: Vec<(Q::Commitment, BTreeSet)> = vec![]; - for query in queries.iter() { - let rotation = query.get_point(); - super_point_set.insert(rotation); - if let Some(commitment_rotation_set) = commitment_rotation_set_map - .iter_mut() - .find(|(commitment, _)| *commitment == query.get_commitment()) - { - let (_, rotation_set) = commitment_rotation_set; - rotation_set.insert(rotation); - } else { - commitment_rotation_set_map.push(( - query.get_commitment(), - BTreeSet::from_iter(std::iter::once(rotation)), - )); - }; - } - - // Flatten rotation sets and collect commitments that opens against each commitment set - // Example elements in the vector: - // {r_5}: [C_0], - // {r_1, r_2, r_3} : [C_1] - // {r_2, r_3, r_4} : [C_2, C_3], - // ... - // NOTE: we want to make the order of the collection of rotation sets independent of the opening points, to ease the verifier computation - let mut rotation_set_commitment_map: Vec<(BTreeSet, Vec)> = vec![]; - for (commitment, rotation_set) in commitment_rotation_set_map.into_iter() { - if let Some(rotation_set_commitment) = rotation_set_commitment_map - .iter_mut() - .find(|(set, _)| set == &rotation_set) - { - let (_, commitments) = rotation_set_commitment; - commitments.push(commitment); - } else { - rotation_set_commitment_map.push((rotation_set, vec![commitment])); - }; - } - - let rotation_sets = rotation_set_commitment_map - .into_par_iter() - .map(|(rotations, commitments)| { - let rotations_vec = rotations.iter().collect::>(); - let commitments: Vec> = commitments - .into_par_iter() - .map(|commitment| { - let evals: Vec = rotations_vec - .as_slice() - .into_par_iter() - .map(|&&rotation| get_eval(commitment, rotation)) - .collect(); - Commitment((commitment, evals)) - }) - .collect(); - - RotationSet { - commitments, - points: rotations.into_iter().collect(), - } - }) - .collect::>>(); - - IntermediateSets { - rotation_sets, - super_point_set, - } -} - -#[cfg(test)] -mod proptests { - use super::{construct_intermediate_sets, Commitment, IntermediateSets}; - use ff::FromUniformBytes; - use halo2curves::pasta::Fp; - use proptest::{collection::vec, prelude::*, sample::select}; - use std::convert::TryFrom; - - #[derive(Debug, Clone)] - struct MyQuery { - point: F, - eval: F, - commitment: usize, - } - - impl super::Query for MyQuery { - type Commitment = usize; - type Eval = Fp; - - fn get_point(&self) -> Fp { - self.point - } - - fn get_eval(&self) -> Self::Eval { - self.eval - } - - fn get_commitment(&self) -> Self::Commitment { - self.commitment - } - } - - prop_compose! { - fn arb_point()( - bytes in vec(any::(), 64) - ) -> Fp { - Fp::from_uniform_bytes(&<[u8; 64]>::try_from(bytes).unwrap()) - } - } - - prop_compose! { - fn arb_query(commitment: usize, point: Fp)( - eval in arb_point() - ) -> MyQuery { - MyQuery { - point, - eval, - commitment - } - } - } - - prop_compose! { - // Mapping from column index to point index. - fn arb_queries_inner(num_points: usize, num_cols: usize, num_queries: usize)( - col_indices in vec(select((0..num_cols).collect::>()), num_queries), - point_indices in vec(select((0..num_points).collect::>()), num_queries) - ) -> Vec<(usize, usize)> { - col_indices.into_iter().zip(point_indices.into_iter()).collect() - } - } - - prop_compose! { - fn compare_queries( - num_points: usize, - num_cols: usize, - num_queries: usize, - )( - points_1 in vec(arb_point(), num_points), - points_2 in vec(arb_point(), num_points), - mapping in arb_queries_inner(num_points, num_cols, num_queries) - )( - queries_1 in mapping.iter().map(|(commitment, point_idx)| arb_query(*commitment, points_1[*point_idx])).collect::>(), - queries_2 in mapping.iter().map(|(commitment, point_idx)| arb_query(*commitment, points_2[*point_idx])).collect::>(), - ) -> ( - Vec>, - Vec> - ) { - ( - queries_1, - queries_2, - ) - } - } - - proptest! { - #[test] - fn test_intermediate_sets( - (queries_1, queries_2) in compare_queries(8, 8, 16) - ) { - let IntermediateSets { rotation_sets, .. } = construct_intermediate_sets(queries_1); - let commitment_sets = rotation_sets.iter().map(|data| - data.commitments.iter().map(Commitment::get).collect::>() - ).collect::>(); - - // It shouldn't matter what the point or eval values are; we should get - // the same exact point set indices and point indices again. - let IntermediateSets { rotation_sets: new_rotation_sets, .. } = construct_intermediate_sets(queries_2); - let new_commitment_sets = new_rotation_sets.iter().map(|data| - data.commitments.iter().map(Commitment::get).collect::>() - ).collect::>(); - - assert_eq!(commitment_sets, new_commitment_sets); - } - } -} diff --git a/src/poly/kzg/multiopen/shplonk/prover.rs b/src/poly/kzg/multiopen/shplonk/prover.rs deleted file mode 100644 index 5001d69094..0000000000 --- a/src/poly/kzg/multiopen/shplonk/prover.rs +++ /dev/null @@ -1,298 +0,0 @@ -use super::{ - construct_intermediate_sets, ChallengeU, ChallengeV, ChallengeY, Commitment, RotationSet, -}; -use crate::arithmetic::{ - eval_polynomial, evaluate_vanishing_polynomial, kate_division, lagrange_interpolate, - parallelize, powers, CurveAffine, -}; -use crate::helpers::SerdeCurveAffine; -use crate::poly::commitment::{Blind, ParamsProver, Prover}; -use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; -use crate::poly::query::{PolynomialPointer, ProverQuery}; -use crate::poly::{Coeff, Polynomial}; -use crate::transcript::{EncodedChallenge, TranscriptWrite}; - -use crate::multicore::{IntoParallelIterator, ParallelIterator}; -use ff::Field; -use group::Curve; -use halo2curves::pairing::Engine; -use halo2curves::CurveExt; -use rand_core::RngCore; -use std::fmt::Debug; -use std::io; -use std::marker::PhantomData; -use std::ops::MulAssign; - -fn div_by_vanishing(poly: Polynomial, roots: &[F]) -> Vec { - let poly = roots - .iter() - .fold(poly.values, |poly, point| kate_division(&poly, *point)); - - poly -} - -struct CommitmentExtension<'a, C: CurveAffine> { - commitment: Commitment>, - low_degree_equivalent: Polynomial, -} - -impl<'a, C: CurveAffine> Commitment> { - fn extend(&self, points: &[C::Scalar]) -> CommitmentExtension<'a, C> { - let poly = lagrange_interpolate(points, &self.evals()[..]); - - let low_degree_equivalent = Polynomial { - values: poly, - _marker: PhantomData, - }; - - CommitmentExtension { - commitment: self.clone(), - low_degree_equivalent, - } - } -} - -impl<'a, C: CurveAffine> CommitmentExtension<'a, C> { - fn linearisation_contribution(&self, u: C::Scalar) -> Polynomial { - let p_x = self.commitment.get().poly; - let r_eval = eval_polynomial(&self.low_degree_equivalent.values[..], u); - p_x - r_eval - } - - fn quotient_contribution(&self) -> Polynomial { - let len = self.low_degree_equivalent.len(); - let mut p_x = self.commitment.get().poly.clone(); - parallelize(&mut p_x.values[0..len], |lhs, start| { - for (lhs, rhs) in lhs - .iter_mut() - .zip(self.low_degree_equivalent.values[start..].iter()) - { - *lhs -= *rhs; - } - }); - p_x - } -} - -struct RotationSetExtension<'a, C: CurveAffine> { - commitments: Vec>, - points: Vec, -} - -impl<'a, C: CurveAffine> RotationSet> { - fn extend(self, commitments: Vec>) -> RotationSetExtension<'a, C> { - RotationSetExtension { - commitments, - points: self.points, - } - } -} - -/// Concrete KZG prover with SHPLONK variant -#[derive(Debug)] -pub struct ProverSHPLONK<'a, E: Engine> { - params: &'a ParamsKZG, -} - -impl<'a, E: Engine> ProverSHPLONK<'a, E> { - /// Given parameters creates new prover instance - pub fn new(params: &'a ParamsKZG) -> Self { - Self { params } - } -} - -/// Create a multi-opening proof -impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> - for ProverSHPLONK<'params, E> -where - E::Fr: Ord, - E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, - E::G2Affine: SerdeCurveAffine, -{ - const QUERY_INSTANCE: bool = false; - - fn new(params: &'params ParamsKZG) -> Self { - Self { params } - } - - /// Create a multi-opening proof - fn create_proof< - 'com, - Ch: EncodedChallenge, - T: TranscriptWrite, - R, - I, - >( - &self, - _: R, - transcript: &mut T, - queries: I, - ) -> io::Result<()> - where - I: IntoIterator> + Clone, - R: RngCore, - { - // TODO: explore if it is safe to use same challenge - // for different sets that are already combined with another challenge - let y: ChallengeY<_> = transcript.squeeze_challenge_scalar(); - - let quotient_contribution = |rotation_set: &RotationSetExtension| { - // [P_i_0(X) - R_i_0(X), P_i_1(X) - R_i_1(X), ... ] - #[allow(clippy::needless_collect)] - let numerators = rotation_set - .commitments - .as_slice() - .into_par_iter() - .map(|commitment| commitment.quotient_contribution()) - .collect::>(); - - // define numerator polynomial as - // N_i_j(X) = (P_i_j(X) - R_i_j(X)) - // and combine polynomials with same evaluation point set - // N_i(X) = linear_combination(y, N_i_j(X)) - // where y is random scalar to combine numerator polynomials - let n_x = numerators - .into_iter() - .zip(powers(*y)) - .map(|(numerator, power_of_y)| numerator * power_of_y) - .reduce(|acc, numerator| acc + &numerator) - .unwrap(); - - let points = &rotation_set.points[..]; - - // quotient contribution of this evaluation set is - // Q_i(X) = N_i(X) / Z_i(X) where - // Z_i(X) = (x - r_i_0) * (x - r_i_1) * ... - let mut poly = div_by_vanishing(n_x, points); - poly.resize(self.params.n as usize, E::Fr::ZERO); - - Polynomial { - values: poly, - _marker: PhantomData, - } - }; - - let intermediate_sets = construct_intermediate_sets(queries); - let (rotation_sets, super_point_set) = ( - intermediate_sets.rotation_sets, - intermediate_sets.super_point_set, - ); - - let rotation_sets: Vec> = rotation_sets - .into_par_iter() - .map(|rotation_set| { - let commitments: Vec> = rotation_set - .commitments - .as_slice() - .into_par_iter() - .map(|commitment_data| commitment_data.extend(&rotation_set.points)) - .collect(); - rotation_set.extend(commitments) - }) - .collect(); - - let v: ChallengeV<_> = transcript.squeeze_challenge_scalar(); - - #[allow(clippy::needless_collect)] - let quotient_polynomials = rotation_sets - .as_slice() - .into_par_iter() - .map(quotient_contribution) - .collect::>(); - - let h_x: Polynomial = quotient_polynomials - .into_iter() - .zip(powers(*v)) - .map(|(poly, power_of_v)| poly * power_of_v) - .reduce(|acc, poly| acc + &poly) - .unwrap(); - - let h = self.params.commit(&h_x, Blind::default()).to_affine(); - transcript.write_point(h)?; - let u: ChallengeU<_> = transcript.squeeze_challenge_scalar(); - - let linearisation_contribution = |rotation_set: RotationSetExtension| { - let mut diffs = super_point_set.clone(); - for point in rotation_set.points.iter() { - diffs.remove(point); - } - let diffs = diffs.into_iter().collect::>(); - - // calculate difference vanishing polynomial evaluation - let z_i = evaluate_vanishing_polynomial(&diffs[..], *u); - - // inner linearisation contributions are - // [P_i_0(X) - r_i_0, P_i_1(X) - r_i_1, ... ] where - // r_i_j = R_i_j(u) is the evaluation of low degree equivalent polynomial - // where u is random evaluation point - #[allow(clippy::needless_collect)] - let inner_contributions = rotation_set - .commitments - .as_slice() - .into_par_iter() - .map(|commitment| commitment.linearisation_contribution(*u)) - .collect::>(); - - // define inner contributor polynomial as - // L_i_j(X) = (P_i_j(X) - r_i_j) - // and combine polynomials with same evaluation point set - // L_i(X) = linear_combination(y, L_i_j(X)) - // where y is random scalar to combine inner contributors - let l_x: Polynomial = inner_contributions - .into_iter() - .zip(powers(*y)) - .map(|(poly, power_of_y)| poly * power_of_y) - .reduce(|acc, poly| acc + &poly) - .unwrap(); - - // finally scale l_x by difference vanishing polynomial evaluation z_i - (l_x * z_i, z_i) - }; - - #[allow(clippy::type_complexity)] - let (linearisation_contributions, z_diffs): ( - Vec>, - Vec, - ) = rotation_sets - .into_par_iter() - .map(linearisation_contribution) - .unzip(); - - let l_x: Polynomial = linearisation_contributions - .into_iter() - .zip(powers(*v)) - .map(|(poly, power_of_v)| poly * power_of_v) - .reduce(|acc, poly| acc + &poly) - .unwrap(); - - let super_point_set = super_point_set.into_iter().collect::>(); - let zt_eval = evaluate_vanishing_polynomial(&super_point_set[..], *u); - let l_x = l_x - &(h_x * zt_eval); - - // sanity check - #[cfg(debug_assertions)] - { - let must_be_zero = eval_polynomial(&l_x.values[..], *u); - assert_eq!(must_be_zero, E::Fr::ZERO); - } - - let mut h_x = div_by_vanishing(l_x, &[*u]); - - // normalize coefficients by the coefficient of the first polynomial - let z_0_diff_inv = z_diffs[0].invert().unwrap(); - for h_i in h_x.iter_mut() { - h_i.mul_assign(z_0_diff_inv) - } - - let h_x = Polynomial { - values: h_x, - _marker: PhantomData, - }; - - let h = self.params.commit(&h_x, Blind::default()).to_affine(); - transcript.write_point(h)?; - - Ok(()) - } -} diff --git a/src/poly/kzg/multiopen/shplonk/verifier.rs b/src/poly/kzg/multiopen/shplonk/verifier.rs deleted file mode 100644 index 5d03940177..0000000000 --- a/src/poly/kzg/multiopen/shplonk/verifier.rs +++ /dev/null @@ -1,140 +0,0 @@ -use std::fmt::Debug; - -use super::ChallengeY; -use super::{construct_intermediate_sets, ChallengeU, ChallengeV}; -use crate::arithmetic::{ - eval_polynomial, evaluate_vanishing_polynomial, lagrange_interpolate, powers, -}; -use crate::helpers::SerdeCurveAffine; -use crate::poly::commitment::Verifier; -use crate::poly::commitment::MSM; -use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; -use crate::poly::kzg::msm::DualMSM; -use crate::poly::kzg::msm::{PreMSM, MSMKZG}; -use crate::poly::kzg::strategy::GuardKZG; -use crate::poly::query::{CommitmentReference, VerifierQuery}; -use crate::poly::Error; -use crate::transcript::{EncodedChallenge, TranscriptRead}; -use ff::Field; -use halo2curves::pairing::{Engine, MultiMillerLoop}; -use halo2curves::CurveExt; -use std::ops::MulAssign; - -/// Concrete KZG multiopen verifier with SHPLONK variant -#[derive(Debug)] -pub struct VerifierSHPLONK<'params, E: Engine> { - params: &'params ParamsKZG, -} - -impl<'params, E> Verifier<'params, KZGCommitmentScheme> for VerifierSHPLONK<'params, E> -where - E: MultiMillerLoop + Debug, - E::Fr: Ord, - E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, - E::G2Affine: SerdeCurveAffine, -{ - type Guard = GuardKZG<'params, E>; - type MSMAccumulator = DualMSM<'params, E>; - - const QUERY_INSTANCE: bool = false; - - fn new(params: &'params ParamsKZG) -> Self { - Self { params } - } - - /// Verify a multi-opening proof - fn verify_proof< - 'com, - Ch: EncodedChallenge, - T: TranscriptRead, - I, - >( - &self, - transcript: &mut T, - queries: I, - mut msm_accumulator: DualMSM<'params, E>, - ) -> Result - where - I: IntoIterator>> + Clone, - { - let intermediate_sets = construct_intermediate_sets(queries); - let (rotation_sets, super_point_set) = ( - intermediate_sets.rotation_sets, - intermediate_sets.super_point_set, - ); - - let y: ChallengeY<_> = transcript.squeeze_challenge_scalar(); - let v: ChallengeV<_> = transcript.squeeze_challenge_scalar(); - - let h1 = transcript.read_point().map_err(|_| Error::SamplingError)?; - let u: ChallengeU<_> = transcript.squeeze_challenge_scalar(); - let h2 = transcript.read_point().map_err(|_| Error::SamplingError)?; - - let (mut z_0_diff_inverse, mut z_0) = (E::Fr::ZERO, E::Fr::ZERO); - let (mut outer_msm, mut r_outer_acc) = (PreMSM::::new(), E::Fr::ZERO); - for (i, (rotation_set, power_of_v)) in rotation_sets.iter().zip(powers(*v)).enumerate() { - let diffs: Vec = super_point_set - .iter() - .filter(|point| !rotation_set.points.contains(point)) - .copied() - .collect(); - let mut z_diff_i = evaluate_vanishing_polynomial(&diffs[..], *u); - - // normalize coefficients by the coefficient of the first commitment - if i == 0 { - z_0 = evaluate_vanishing_polynomial(&rotation_set.points[..], *u); - z_0_diff_inverse = z_diff_i.invert().unwrap(); - z_diff_i = E::Fr::ONE; - } else { - z_diff_i.mul_assign(z_0_diff_inverse); - } - - let (mut inner_msm, r_inner_acc) = rotation_set - .commitments - .iter() - .zip(powers(*y)) - .map(|(commitment_data, power_of_y)| { - // calculate low degree equivalent - let r_x = lagrange_interpolate( - &rotation_set.points[..], - &commitment_data.evals()[..], - ); - let r_eval = power_of_y * eval_polynomial(&r_x[..], *u); - let msm = match commitment_data.get() { - CommitmentReference::Commitment(c) => { - let mut msm = MSMKZG::::new(); - msm.append_term(power_of_y, (*c).into()); - msm - } - CommitmentReference::MSM(msm) => { - let mut msm = msm.clone(); - msm.scale(power_of_y); - msm - } - }; - (msm, r_eval) - }) - .reduce(|(mut msm_acc, r_eval_acc), (msm, r_eval)| { - msm_acc.add_msm(&msm); - (msm_acc, r_eval_acc + r_eval) - }) - .unwrap(); - - inner_msm.scale(power_of_v * z_diff_i); - outer_msm.add_msm(inner_msm); - r_outer_acc += power_of_v * r_inner_acc * z_diff_i; - } - let mut outer_msm = outer_msm.normalize(); - let g1: E::G1 = self.params.g[0].into(); - outer_msm.append_term(-r_outer_acc, g1); - outer_msm.append_term(-z_0, h1.into()); - outer_msm.append_term(*u, h2.into()); - - msm_accumulator.left.append_term(E::Fr::ONE, h2.into()); - - msm_accumulator.right.add_msm(&outer_msm); - - Ok(Self::Guard::new(msm_accumulator)) - } -} diff --git a/src/poly/multiopen_test.rs b/src/poly/multiopen_test.rs index b56563841c..14f1de7f59 100644 --- a/src/poly/multiopen_test.rs +++ b/src/poly/multiopen_test.rs @@ -21,7 +21,7 @@ mod test { #[test] fn test_roundtrip_gwc() { use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; - use crate::poly::kzg::multiopen::{ProverGWC, VerifierGWC}; + use crate::poly::kzg::gwc::{ProverGWC, VerifierGWC}; use crate::poly::kzg::strategy::AccumulatorStrategy; use halo2curves::bn256::Bn256; @@ -49,43 +49,6 @@ mod test { >(verifier_params, &proof[..], true); } - #[test] - fn test_roundtrip_shplonk() { - use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; - use crate::poly::kzg::multiopen::{ProverSHPLONK, VerifierSHPLONK}; - use crate::poly::kzg::strategy::AccumulatorStrategy; - use halo2curves::bn256::Bn256; - - const K: u32 = 4; - - let params = ParamsKZG::::new(K); - - let proof = create_proof::< - KZGCommitmentScheme, - ProverSHPLONK<_>, - _, - Blake2bWrite<_, _, Challenge255<_>>, - >(¶ms); - - let verifier_params = params.verifier_params(); - - verify::< - KZGCommitmentScheme, - VerifierSHPLONK<_>, - _, - Blake2bRead<_, _, Challenge255<_>>, - AccumulatorStrategy<_>, - >(verifier_params, &proof[..], false); - - verify::< - KZGCommitmentScheme, - VerifierSHPLONK<_>, - _, - Blake2bRead<_, _, Challenge255<_>>, - AccumulatorStrategy<_>, - >(verifier_params, &proof[..], true); - } - fn verify< 'a, 'params, diff --git a/tests/plonk_api.rs b/tests/plonk_api.rs index dbfb9a6155..cfae2d4f98 100644 --- a/tests/plonk_api.rs +++ b/tests/plonk_api.rs @@ -531,64 +531,28 @@ fn plonk_api() { assert!(strategy.finalize()); } - fn test_plonk_api_gwc() { - use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; - use halo2_proofs::poly::kzg::multiopen::{ProverGWC, VerifierGWC}; - use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; - use halo2curves::bn256::Bn256; + use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; + use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; + use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; + use halo2curves::bn256::Bn256; - type Scheme = KZGCommitmentScheme; - bad_keys!(Scheme); + type Scheme = KZGCommitmentScheme; + bad_keys!(Scheme); - let params = ParamsKZG::::new(K); - let rng = OsRng; + let params = ParamsKZG::::new(K); + let rng = OsRng; - let pk = keygen::>(¶ms); + let pk = keygen::>(¶ms); - let proof = create_proof::<_, ProverGWC<_>, _, _, Blake2bWrite<_, _, Challenge255<_>>>( - rng, ¶ms, &pk, - ); + let proof = create_proof::<_, ProverGWC<_>, _, _, Blake2bWrite<_, _, Challenge255<_>>>( + rng, ¶ms, &pk, + ); - let verifier_params = params.verifier_params(); + let verifier_params = params.verifier_params(); - verify_proof::< - _, - VerifierGWC<_>, - _, - Blake2bRead<_, _, Challenge255<_>>, - AccumulatorStrategy<_>, - >(verifier_params, pk.get_vk(), &proof[..]); - } - - fn test_plonk_api_shplonk() { - use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; - use halo2_proofs::poly::kzg::multiopen::{ProverSHPLONK, VerifierSHPLONK}; - use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; - use halo2curves::bn256::Bn256; - - type Scheme = KZGCommitmentScheme; - bad_keys!(Scheme); - - let params = ParamsKZG::::new(K); - let rng = OsRng; - - let pk = keygen::>(¶ms); - - let proof = create_proof::<_, ProverSHPLONK<_>, _, _, Blake2bWrite<_, _, Challenge255<_>>>( - rng, ¶ms, &pk, - ); - - let verifier_params = params.verifier_params(); - - verify_proof::< - _, - VerifierSHPLONK<_>, - _, - Blake2bRead<_, _, Challenge255<_>>, - AccumulatorStrategy<_>, - >(verifier_params, pk.get_vk(), &proof[..]); - } - - test_plonk_api_gwc(); - test_plonk_api_shplonk(); + verify_proof::<_, VerifierGWC<_>, _, Blake2bRead<_, _, Challenge255<_>>, AccumulatorStrategy<_>>( + verifier_params, + pk.get_vk(), + &proof[..], + ); } From 3818a52abbca002d056de5dfe2698a73eae8092a Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Fri, 29 Nov 2024 14:18:11 +0100 Subject: [PATCH 05/20] Remove compressed selectors --- src/dev.rs | 4 +- src/dev/cost.rs | 1 - src/plonk.rs | 44 +-- src/plonk/circuit.rs | 109 -------- src/plonk/circuit/compress_selectors.rs | 352 ------------------------ src/plonk/keygen.rs | 20 +- 6 files changed, 13 insertions(+), 517 deletions(-) delete mode 100644 src/plonk/circuit/compress_selectors.rs diff --git a/src/dev.rs b/src/dev.rs index 7dc2cbd597..15575bb8c3 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -709,7 +709,9 @@ impl + Ord> MockProver { )?; } - let (cs, selector_polys) = prover.cs.compress_selectors(prover.selectors.clone()); + let (cs, selector_polys) = prover + .cs + .directly_convert_selectors_to_fixed(prover.selectors.clone()); prover.cs = cs; prover.fixed.extend(selector_polys.into_iter().map(|poly| { let mut v = vec![CellValue::Unassigned; n]; diff --git a/src/dev/cost.rs b/src/dev/cost.rs index 735f1f0dc7..e75aba848e 100644 --- a/src/dev/cost.rs +++ b/src/dev/cost.rs @@ -283,7 +283,6 @@ impl> CircuitCost= cs.minimum_rows()); diff --git a/src/plonk.rs b/src/plonk.rs index 67de4fb163..9a6aa37827 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -55,8 +55,6 @@ pub struct VerifyingKey { /// The representative of this `VerifyingKey` in transcripts. transcript_repr: C::Scalar, selectors: Vec>, - /// Whether selector compression is turned on or not. - compress_selectors: bool, } // Current version of the VK @@ -82,16 +80,12 @@ where assert!(*k <= C::Scalar::S); // k value fits in 1 byte writer.write_all(&[*k as u8])?; - writer.write_all(&[self.compress_selectors as u8])?; writer.write_all(&(self.fixed_commitments.len() as u32).to_le_bytes())?; for commitment in &self.fixed_commitments { commitment.write(writer, format)?; } self.permutation.write(writer, format)?; - if !self.compress_selectors { - assert!(self.selectors.is_empty()); - } // write self.selectors for selector in &self.selectors { // since `selector` is filled with `bool`, we pack them 8 at a time into bytes and then write @@ -139,15 +133,7 @@ where ), )); } - let mut compress_selectors = [0u8; 1]; - reader.read_exact(&mut compress_selectors)?; - if compress_selectors[0] != 0 && compress_selectors[0] != 1 { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "unexpected compress_selectors not boolean", - )); - } - let compress_selectors = compress_selectors[0] == 1; + let (domain, cs, _) = keygen::create_domain::( k as u32, #[cfg(feature = "circuit-params")] @@ -163,27 +149,10 @@ where let permutation = permutation::VerifyingKey::read(reader, &cs.permutation, format)?; - let (cs, selectors) = if compress_selectors { - // read selectors - let selectors: Vec> = vec![vec![false; 1 << k]; cs.num_selectors] - .into_iter() - .map(|mut selector| { - let mut selector_bytes = vec![0u8; (selector.len() + 7) / 8]; - reader.read_exact(&mut selector_bytes)?; - for (bits, byte) in selector.chunks_mut(8).zip(selector_bytes) { - crate::helpers::unpack(byte, bits); - } - Ok(selector) - }) - .collect::>()?; - let (cs, _) = cs.compress_selectors(selectors.clone()); - (cs, selectors) - } else { - // we still need to replace selectors with fixed Expressions in `cs` - let fake_selectors = vec![vec![]; cs.num_selectors]; - let (cs, _) = cs.directly_convert_selectors_to_fixed(fake_selectors); - (cs, vec![]) - }; + let selectors = vec![]; + // we still need to replace selectors with fixed Expressions in `cs` + let fake_selectors = vec![vec![]; cs.num_selectors]; + let (cs, _) = cs.directly_convert_selectors_to_fixed(fake_selectors); Ok(Self::from_parts( domain, @@ -191,7 +160,6 @@ where permutation, cs, selectors, - compress_selectors, )) } @@ -238,7 +206,6 @@ impl VerifyingKey { permutation: permutation::VerifyingKey, cs: ConstraintSystem, selectors: Vec>, - compress_selectors: bool, ) -> Self where C::ScalarExt: FromUniformBytes<64>, @@ -255,7 +222,6 @@ impl VerifyingKey { // Temporary, this is not pinned. transcript_repr: C::Scalar::ZERO, selectors, - compress_selectors, }; let mut hasher = Blake2bParams::new() diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index 85425cc8e4..7b9e1f4cff 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -17,8 +17,6 @@ use std::{ ops::{Neg, Sub}, }; -mod compress_selectors; - /// A column type pub trait ColumnType: 'static + Sized + Copy + std::fmt::Debug + PartialEq + Eq + Into @@ -1223,34 +1221,6 @@ impl Expression { &|a, _| a, ) } - - /// Extracts a simple selector from this gate, if present - fn extract_simple_selector(&self) -> Option { - let op = |a, b| match (a, b) { - (Some(a), None) | (None, Some(a)) => Some(a), - (Some(_), Some(_)) => panic!("two simple selectors cannot be in the same expression"), - _ => None, - }; - - self.evaluate( - &|_| None, - &|selector| { - if selector.is_simple() { - Some(selector) - } else { - None - } - }, - &|_| None, - &|_| None, - &|_| None, - &|_| None, - &|a| a, - &op, - &op, - &|a, _| a, - ) - } } impl std::fmt::Debug for Expression { @@ -1563,11 +1533,6 @@ pub struct ConstraintSystem { /// Contains the phase for each challenge. Should have same length as num_challenges. pub(crate) challenge_phase: Vec, - /// This is a cached vector that maps virtual selectors to the concrete - /// fixed column that they were compressed into. This is just used by dev - /// tooling right now. - pub(crate) selector_map: Vec>, - pub(crate) gates: Vec>, pub(crate) advice_queries: Vec<(Column, Rotation)>, // Contains an integer for each advice column @@ -1664,7 +1629,6 @@ impl Default for ConstraintSystem { unblinded_advice_columns: Vec::new(), advice_column_phase: Vec::new(), challenge_phase: Vec::new(), - selector_map: vec![], gates: vec![], fixed_queries: Vec::new(), advice_queries: Vec::new(), @@ -1932,79 +1896,6 @@ impl ConstraintSystem { }); } - /// This will compress selectors together depending on their provided - /// assignments. This `ConstraintSystem` will then be modified to add new - /// fixed columns (representing the actual selectors) and will return the - /// polynomials for those columns. Finally, an internal map is updated to - /// find which fixed column corresponds with a given `Selector`. - /// - /// Do not call this twice. Yes, this should be a builder pattern instead. - pub fn compress_selectors(mut self, selectors: Vec>) -> (Self, Vec>) { - // The number of provided selector assignments must be the number we - // counted for this constraint system. - assert_eq!(selectors.len(), self.num_selectors); - - // Compute the maximal degree of every selector. We only consider the - // expressions in gates, as lookup arguments cannot support simple - // selectors. Selectors that are complex or do not appear in any gates - // will have degree zero. - let mut degrees = vec![0; selectors.len()]; - for expr in self.gates.iter().flat_map(|gate| gate.polys.iter()) { - if let Some(selector) = expr.extract_simple_selector() { - degrees[selector.0] = max(degrees[selector.0], expr.degree()); - } - } - - // We will not increase the degree of the constraint system, so we limit - // ourselves to the largest existing degree constraint. - let max_degree = self.degree(); - - let mut new_columns = vec![]; - let (polys, selector_assignment) = compress_selectors::process( - selectors - .into_iter() - .zip(degrees) - .enumerate() - .map( - |(i, (activations, max_degree))| compress_selectors::SelectorDescription { - selector: i, - activations, - max_degree, - }, - ) - .collect(), - max_degree, - || { - let column = self.fixed_column(); - new_columns.push(column); - Expression::Fixed(FixedQuery { - index: Some(self.query_fixed_index(column, Rotation::cur())), - column_index: column.index, - rotation: Rotation::cur(), - }) - }, - ); - - let mut selector_map = vec![None; selector_assignment.len()]; - let mut selector_replacements = vec![None; selector_assignment.len()]; - for assignment in selector_assignment { - selector_replacements[assignment.selector] = Some(assignment.expression); - selector_map[assignment.selector] = Some(new_columns[assignment.combination_index]); - } - - self.selector_map = selector_map - .into_iter() - .map(|a| a.unwrap()) - .collect::>(); - let selector_replacements = selector_replacements - .into_iter() - .map(|a| a.unwrap()) - .collect::>(); - self.replace_selectors_with_fixed(&selector_replacements); - - (self, polys) - } - /// Does not combine selectors and directly replaces them everywhere with fixed columns. pub fn directly_convert_selectors_to_fixed( mut self, diff --git a/src/plonk/circuit/compress_selectors.rs b/src/plonk/circuit/compress_selectors.rs deleted file mode 100644 index 053ebe3178..0000000000 --- a/src/plonk/circuit/compress_selectors.rs +++ /dev/null @@ -1,352 +0,0 @@ -use super::Expression; -use ff::Field; - -/// This describes a selector and where it is activated. -#[derive(Debug, Clone)] -pub struct SelectorDescription { - /// The selector that this description references, by index. - pub selector: usize, - - /// The vector of booleans defining which rows are active for this selector. - pub activations: Vec, - - /// The maximum degree of a gate involving this selector, including the - /// virtual selector itself. This means this will be at least 1 for any - /// expression containing a simple selector, even if that selector is not - /// multiplied by anything. - pub max_degree: usize, -} - -/// This describes the assigned combination of a particular selector as well as -/// the expression it should be substituted with. -#[derive(Debug, Clone)] -pub struct SelectorAssignment { - /// The selector that this structure references, by index. - pub selector: usize, - - /// The combination this selector was assigned to - pub combination_index: usize, - - /// The expression we wish to substitute with - pub expression: Expression, -} - -/// This function takes a vector that defines each selector as well as a closure -/// used to allocate new fixed columns, and returns the assignment of each -/// combination as well as details about each selector assignment. -/// -/// This function takes -/// * `selectors`, a vector of `SelectorDescription`s that describe each -/// selector -/// * `max_degree`, the maximum allowed degree of any gate -/// * `allocate_fixed_columns`, a closure that constructs a new fixed column and -/// queries it at Rotation::cur(), returning the expression -/// -/// and returns `Vec>` containing the assignment of each new fixed column -/// (which each correspond to a combination) as well as a vector of -/// `SelectorAssignment` that the caller can use to perform the necessary -/// substitutions to the constraint system. -/// -/// This function is completely deterministic. -pub fn process( - mut selectors: Vec, - max_degree: usize, - mut allocate_fixed_column: E, -) -> (Vec>, Vec>) -where - E: FnMut() -> Expression, -{ - if selectors.is_empty() { - // There is nothing to optimize. - return (vec![], vec![]); - } - - // The length of all provided selectors must be the same. - let n = selectors[0].activations.len(); - assert!(selectors.iter().all(|a| a.activations.len() == n)); - - let mut combination_assignments = vec![]; - let mut selector_assignments = vec![]; - - // All provided selectors of degree 0 are assumed to be either concrete - // selectors or do not appear in a gate. Let's address these first. - selectors.retain(|selector| { - if selector.max_degree == 0 { - // This is a complex selector, or a selector that does not appear in any - // gate constraint. - let expression = allocate_fixed_column(); - - let combination_assignment = selector - .activations - .iter() - .map(|b| if *b { F::ONE } else { F::ZERO }) - .collect::>(); - let combination_index = combination_assignments.len(); - combination_assignments.push(combination_assignment); - selector_assignments.push(SelectorAssignment { - selector: selector.selector, - combination_index, - expression, - }); - - false - } else { - true - } - }); - - // All of the remaining `selectors` are simple. Let's try to combine them. - // First, we compute the exclusion matrix that has (j, k) = true if selector - // j and selector k conflict -- that is, they are both enabled on the same - // row. This matrix is symmetric and the diagonal entries are false, so we - // only need to store the lower triangular entries. - let mut exclusion_matrix = (0..selectors.len()) - .map(|i| vec![false; i]) - .collect::>(); - - for (i, rows) in selectors - .iter() - .map(|selector| &selector.activations) - .enumerate() - { - // Loop over the selectors previous to this one - for (j, other_selector) in selectors.iter().enumerate().take(i) { - // Look at what selectors are active at the same row - if rows - .iter() - .zip(other_selector.activations.iter()) - .any(|(l, r)| l & r) - { - // Mark them as incompatible - exclusion_matrix[i][j] = true; - } - } - } - - // Simple selectors that we've added to combinations already. - let mut added = vec![false; selectors.len()]; - - for (i, selector) in selectors.iter().enumerate() { - if added[i] { - continue; - } - added[i] = true; - assert!(selector.max_degree <= max_degree); - // This is used to keep track of the largest degree gate involved in the - // combination so far. We subtract by one to omit the virtual selector - // which will be substituted by the caller with the expression we give - // them. - let mut d = selector.max_degree - 1; - let mut combination = vec![selector]; - let mut combination_added = vec![i]; - - // Try to find other selectors that can join this one. - 'try_selectors: for (j, selector) in selectors.iter().enumerate().skip(i + 1) { - if d + combination.len() == max_degree { - // Short circuit; nothing can be added to this - // combination. - break 'try_selectors; - } - - // Skip selectors that have been added to previous combinations - if added[j] { - continue 'try_selectors; - } - - // Is this selector excluded from co-existing in the same - // combination with any of the other selectors so far? - for &i in combination_added.iter() { - if exclusion_matrix[j][i] { - continue 'try_selectors; - } - } - - // Can the new selector join the combination? Reminder: we use - // selector.max_degree - 1 to omit the influence of the virtual - // selector on the degree, as it will be substituted. - let new_d = std::cmp::max(d, selector.max_degree - 1); - if new_d + combination.len() + 1 > max_degree { - // Guess not. - continue 'try_selectors; - } - - d = new_d; - combination.push(selector); - combination_added.push(j); - added[j] = true; - } - - // Now, compute the selector and combination assignments. - let mut combination_assignment = vec![F::ZERO; n]; - let combination_len = combination.len(); - let combination_index = combination_assignments.len(); - let query = allocate_fixed_column(); - - let mut assigned_root = F::ONE; - selector_assignments.extend(combination.into_iter().map(|selector| { - // Compute the expression for substitution. This produces an expression of the - // form - // q * Prod[i = 1..=combination_len, i != assigned_root](i - q) - // - // which is non-zero only on rows where `combination_assignment` is set to - // `assigned_root`. In particular, rows set to 0 correspond to all selectors - // being disabled. - let mut expression = query.clone(); - let mut root = F::ONE; - for _ in 0..combination_len { - if root != assigned_root { - expression = expression * (Expression::Constant(root) - query.clone()); - } - root += F::ONE; - } - - // Update the combination assignment - for (combination, selector) in combination_assignment - .iter_mut() - .zip(selector.activations.iter()) - { - // This will not overwrite another selector's activations because - // we have ensured that selectors are disjoint. - if *selector { - *combination = assigned_root; - } - } - - assigned_root += F::ONE; - - SelectorAssignment { - selector: selector.selector, - combination_index, - expression, - } - })); - combination_assignments.push(combination_assignment); - } - - (combination_assignments, selector_assignments) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{plonk::FixedQuery, poly::Rotation}; - use halo2curves::pasta::Fp; - use proptest::collection::{vec, SizeRange}; - use proptest::prelude::*; - - prop_compose! { - fn arb_selector(assignment_size: usize, max_degree: usize) - (degree in 0..max_degree, - assignment in vec(any::(), assignment_size)) - -> (usize, Vec) { - (degree, assignment) - } - } - - prop_compose! { - fn arb_selector_list(assignment_size: usize, max_degree: usize, num_selectors: impl Into) - (list in vec(arb_selector(assignment_size, max_degree), num_selectors)) - -> Vec - { - list.into_iter().enumerate().map(|(i, (max_degree, activations))| { - SelectorDescription { - selector: i, - activations, - max_degree, - } - }).collect() - } - } - - prop_compose! { - fn arb_instance(max_assignment_size: usize, - max_degree: usize, - max_selectors: usize) - (assignment_size in 1..max_assignment_size, - degree in 1..max_degree, - num_selectors in 1..max_selectors) - (list in arb_selector_list(assignment_size, degree, num_selectors), - degree in Just(degree)) - -> (Vec, usize) - { - (list, degree) - } - } - - proptest! { - #![proptest_config(ProptestConfig::with_cases(10000))] - #[test] - fn test_selector_combination((selectors, max_degree) in arb_instance(10, 10, 15)) { - let mut query = 0; - let (combination_assignments, selector_assignments) = - process::(selectors.clone(), max_degree, || { - let tmp = Expression::Fixed(FixedQuery { - index: Some(query), - column_index: query, - rotation: Rotation::cur(), - }); - query += 1; - tmp - }); - - { - let mut selectors_seen = vec![]; - assert_eq!(selectors.len(), selector_assignments.len()); - for selector in &selector_assignments { - // Every selector should be assigned to a combination - assert!(selector.combination_index < combination_assignments.len()); - assert!(!selectors_seen.contains(&selector.selector)); - selectors_seen.push(selector.selector); - } - } - - // Test that, for each selector, the provided expression - // 1. evaluates to zero on rows where the selector's activation is off - // 2. evaluates to nonzero on rows where the selector's activation is on - // 3. is of degree d such that d + (selector.max_degree - 1) <= max_degree - // OR selector.max_degree is zero - for selector in selector_assignments { - assert_eq!( - selectors[selector.selector].activations.len(), - combination_assignments[selector.combination_index].len() - ); - for (&activation, &assignment) in selectors[selector.selector] - .activations - .iter() - .zip(combination_assignments[selector.combination_index].iter()) - { - let eval = selector.expression.evaluate( - &|c| c, - &|_| panic!("should not occur in returned expressions"), - &|query| { - // Should be the correct combination in the expression - assert_eq!(selector.combination_index, query.index.unwrap()); - assignment - }, - &|_| panic!("should not occur in returned expressions"), - &|_| panic!("should not occur in returned expressions"), - &|_| panic!("should not occur in returned expressions"), - &|a| -a, - &|a, b| a + b, - &|a, b| a * b, - &|a, f| a * f, - ); - - if activation { - assert!(!eval.is_zero_vartime()); - } else { - assert!(eval.is_zero_vartime()); - } - } - - let expr_degree = selector.expression.degree(); - assert!(expr_degree <= max_degree); - if selectors[selector.selector].max_degree > 0 { - assert!( - (selectors[selector.selector].max_degree - 1) + expr_degree <= max_degree - ); - } - } - } - } -} diff --git a/src/plonk/keygen.rs b/src/plonk/keygen.rs index 984eecb9e8..eb8bc5ba3d 100644 --- a/src/plonk/keygen.rs +++ b/src/plonk/keygen.rs @@ -214,7 +214,7 @@ where ConcreteCircuit: Circuit, C::Scalar: FromUniformBytes<64>, { - keygen_vk_custom(params, circuit, true) + keygen_vk_custom(params, circuit) } /// Generate a `VerifyingKey` from an instance of `Circuit`. @@ -223,7 +223,6 @@ where pub fn keygen_vk_custom<'params, C, P, ConcreteCircuit>( params: &P, circuit: &ConcreteCircuit, - compress_selectors: bool, ) -> Result, Error> where C: CurveAffine, @@ -259,13 +258,9 @@ where )?; let mut fixed = batch_invert_assigned(assembly.fixed); - let (cs, selector_polys) = if compress_selectors { - cs.compress_selectors(assembly.selectors.clone()) - } else { - // After this, the ConstraintSystem should not have any selectors: `verify` does not need them, and `keygen_pk` regenerates `cs` from scratch anyways. - let selectors = std::mem::take(&mut assembly.selectors); - cs.directly_convert_selectors_to_fixed(selectors) - }; + // After this, the ConstraintSystem should not have any selectors: `verify` does not need them, and `keygen_pk` regenerates `cs` from scratch anyways. + let selectors = std::mem::take(&mut assembly.selectors); + let (cs, selector_polys) = cs.directly_convert_selectors_to_fixed(selectors); fixed.extend( selector_polys .into_iter() @@ -287,7 +282,6 @@ where permutation_vk, cs, assembly.selectors, - compress_selectors, )) } @@ -332,11 +326,7 @@ where )?; let mut fixed = batch_invert_assigned(assembly.fixed); - let (cs, selector_polys) = if vk.compress_selectors { - cs.compress_selectors(assembly.selectors) - } else { - cs.directly_convert_selectors_to_fixed(assembly.selectors) - }; + let (cs, selector_polys) = cs.directly_convert_selectors_to_fixed(assembly.selectors); fixed.extend( selector_polys .into_iter() From a8f0c94b0cb11e5893b1e9cfd0a854c36dcfdfc7 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Wed, 4 Dec 2024 10:59:56 +0100 Subject: [PATCH 06/20] Temporarily remove examples and benches --- Cargo.toml | 38 +- benches/dev_lookup.rs | 232 +++--- benches/plonk.rs | 706 +++++++++---------- examples/circuit-layout.rs | 612 ++++++++-------- examples/proof-size.rs | 202 +++--- examples/serialization.rs | 386 +++++----- examples/shuffle.rs | 718 +++++++++---------- examples/simple-example.rs | 686 +++++++++--------- examples/two-chip.rs | 1076 ++++++++++++++-------------- examples/vector-mul.rs | 633 ++++++++--------- examples/vector-ops-unblinded.rs | 1126 +++++++++++++++--------------- 11 files changed, 3215 insertions(+), 3200 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1e865a63ae..cd05bb6a6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,25 +23,25 @@ keywords = ["halo", "proofs", "zkp", "zkSNARKs"] all-features = true rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] -[[bench]] -name = "commit_zk" -harness = false - -[[bench]] -name = "hashtocurve" -harness = false - -[[bench]] -name = "plonk" -harness = false - -[[bench]] -name = "dev_lookup" -harness = false - -[[bench]] -name = "fft" -harness = false +#[[bench]] +#name = "commit_zk" +#harness = false +# +#[[bench]] +#name = "hashtocurve" +#harness = false +# +#[[bench]] +#name = "plonk" +#harness = false +# +#[[bench]] +#name = "dev_lookup" +#harness = false +# +#[[bench]] +#name = "fft" +#harness = false [dependencies] backtrace = { version = "0.3", optional = true } diff --git a/benches/dev_lookup.rs b/benches/dev_lookup.rs index 569ffd1019..fd1ebb9c51 100644 --- a/benches/dev_lookup.rs +++ b/benches/dev_lookup.rs @@ -1,116 +1,116 @@ -#[macro_use] -extern crate criterion; - -use ff::{Field, PrimeField}; -use halo2_proofs::circuit::{Layouter, SimpleFloorPlanner, Value}; -use halo2_proofs::dev::MockProver; -use halo2_proofs::plonk::*; -use halo2_proofs::poly::Rotation; -use halo2curves::pasta::pallas; - -use std::marker::PhantomData; - -use criterion::{BenchmarkId, Criterion}; - -fn criterion_benchmark(c: &mut Criterion) { - #[derive(Clone, Default)] - struct MyCircuit { - _marker: PhantomData, - } - - #[derive(Clone)] - struct MyConfig { - selector: Selector, - table: TableColumn, - advice: Column, - } - - impl Circuit for MyCircuit { - type Config = MyConfig; - type FloorPlanner = SimpleFloorPlanner; - #[cfg(feature = "circuit-params")] - type Params = (); - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> MyConfig { - let config = MyConfig { - selector: meta.complex_selector(), - table: meta.lookup_table_column(), - advice: meta.advice_column(), - }; - - meta.lookup("lookup", |meta| { - let selector = meta.query_selector(config.selector); - let not_selector = Expression::Constant(F::ONE) - selector.clone(); - let advice = meta.query_advice(config.advice, Rotation::cur()); - vec![(selector * advice + not_selector, config.table)] - }); - - config - } - - fn synthesize( - &self, - config: MyConfig, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - layouter.assign_table( - || "8-bit table", - |mut table| { - for row in 0u64..(1 << 8) { - table.assign_cell( - || format!("row {row}"), - config.table, - row as usize, - || Value::known(F::from(row + 1)), - )?; - } - - Ok(()) - }, - )?; - - layouter.assign_region( - || "assign values", - |mut region| { - for offset in 0u64..(1 << 10) { - config.selector.enable(&mut region, offset as usize)?; - region.assign_advice( - || format!("offset {offset}"), - config.advice, - offset as usize, - || Value::known(F::from((offset % 256) + 1)), - )?; - } - - Ok(()) - }, - ) - } - } - - fn prover(k: u32) { - let circuit = MyCircuit:: { - _marker: PhantomData, - }; - let prover = MockProver::run(k, &circuit, vec![]).unwrap(); - assert_eq!(prover.verify(), Ok(())) - } - - let k_range = 14..=18; - - let mut prover_group = c.benchmark_group("dev-lookup"); - prover_group.sample_size(10); - for k in k_range { - prover_group.bench_with_input(BenchmarkId::from_parameter(k), &k, |b, &k| { - b.iter(|| prover(k)); - }); - } - prover_group.finish(); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); +// #[macro_use] +// extern crate criterion; +// +// use ff::{Field, PrimeField}; +// use halo2_proofs::circuit::{Layouter, SimpleFloorPlanner, Value}; +// use halo2_proofs::dev::MockProver; +// use halo2_proofs::plonk::*; +// use halo2_proofs::poly::Rotation; +// use halo2curves::pasta::pallas; +// +// use std::marker::PhantomData; +// +// use criterion::{BenchmarkId, Criterion}; +// +// fn criterion_benchmark(c: &mut Criterion) { +// #[derive(Clone, Default)] +// struct MyCircuit { +// _marker: PhantomData, +// } +// +// #[derive(Clone)] +// struct MyConfig { +// selector: Selector, +// table: TableColumn, +// advice: Column, +// } +// +// impl Circuit for MyCircuit { +// type Config = MyConfig; +// type FloorPlanner = SimpleFloorPlanner; +// #[cfg(feature = "circuit-params")] +// type Params = (); +// +// fn without_witnesses(&self) -> Self { +// Self::default() +// } +// +// fn configure(meta: &mut ConstraintSystem) -> MyConfig { +// let config = MyConfig { +// selector: meta.complex_selector(), +// table: meta.lookup_table_column(), +// advice: meta.advice_column(), +// }; +// +// meta.lookup("lookup", |meta| { +// let selector = meta.query_selector(config.selector); +// let not_selector = Expression::Constant(F::ONE) - selector.clone(); +// let advice = meta.query_advice(config.advice, Rotation::cur()); +// vec![(selector * advice + not_selector, config.table)] +// }); +// +// config +// } +// +// fn synthesize( +// &self, +// config: MyConfig, +// mut layouter: impl Layouter, +// ) -> Result<(), Error> { +// layouter.assign_table( +// || "8-bit table", +// |mut table| { +// for row in 0u64..(1 << 8) { +// table.assign_cell( +// || format!("row {row}"), +// config.table, +// row as usize, +// || Value::known(F::from(row + 1)), +// )?; +// } +// +// Ok(()) +// }, +// )?; +// +// layouter.assign_region( +// || "assign values", +// |mut region| { +// for offset in 0u64..(1 << 10) { +// config.selector.enable(&mut region, offset as usize)?; +// region.assign_advice( +// || format!("offset {offset}"), +// config.advice, +// offset as usize, +// || Value::known(F::from((offset % 256) + 1)), +// )?; +// } +// +// Ok(()) +// }, +// ) +// } +// } +// +// fn prover(k: u32) { +// let circuit = MyCircuit:: { +// _marker: PhantomData, +// }; +// let prover = MockProver::run(k, &circuit, vec![]).unwrap(); +// assert_eq!(prover.verify(), Ok(())) +// } +// +// let k_range = 14..=18; +// +// let mut prover_group = c.benchmark_group("dev-lookup"); +// prover_group.sample_size(10); +// for k in k_range { +// prover_group.bench_with_input(BenchmarkId::from_parameter(k), &k, |b, &k| { +// b.iter(|| prover(k)); +// }); +// } +// prover_group.finish(); +// } +// +// criterion_group!(benches, criterion_benchmark); +// criterion_main!(benches); diff --git a/benches/plonk.rs b/benches/plonk.rs index bce3a63db6..f68afcf883 100644 --- a/benches/plonk.rs +++ b/benches/plonk.rs @@ -1,353 +1,353 @@ -#[macro_use] -extern crate criterion; - -use group::ff::Field; -use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; -use halo2_proofs::plonk::*; -use halo2_proofs::poly::{commitment::ParamsProver, Rotation}; -use halo2_proofs::transcript::{Blake2bRead, Blake2bWrite, Challenge255}; -use halo2curves::bn256; -use rand_core::OsRng; - -use halo2_proofs::transcript::{TranscriptReadBuffer, TranscriptWriterBuffer}; - -use std::marker::PhantomData; - -use criterion::{BenchmarkId, Criterion}; -use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; -use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; -use halo2_proofs::poly::kzg::strategy::SingleStrategy; - -fn criterion_benchmark(c: &mut Criterion) { - /// This represents an advice column at a certain row in the ConstraintSystem - #[derive(Copy, Clone, Debug)] - pub struct Variable(Column, usize); - - #[derive(Clone)] - struct PlonkConfig { - a: Column, - b: Column, - c: Column, - - sa: Column, - sb: Column, - sc: Column, - sm: Column, - } - - trait StandardCs { - fn raw_multiply( - &self, - layouter: &mut impl Layouter, - f: F, - ) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; - fn raw_add( - &self, - layouter: &mut impl Layouter, - f: F, - ) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; - fn copy(&self, layouter: &mut impl Layouter, a: Cell, b: Cell) -> Result<(), Error>; - } - - #[derive(Clone)] - struct MyCircuit { - a: Value, - k: u32, - } - - struct StandardPlonk { - config: PlonkConfig, - _marker: PhantomData, - } - - impl StandardPlonk { - fn new(config: PlonkConfig) -> Self { - StandardPlonk { - config, - _marker: PhantomData, - } - } - } - - impl StandardCs for StandardPlonk { - fn raw_multiply( - &self, - layouter: &mut impl Layouter, - mut f: F, - ) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, - { - layouter.assign_region( - || "raw_multiply", - |mut region| { - let mut value = None; - let lhs = region.assign_advice( - || "lhs", - self.config.a, - 0, - || { - value = Some(f()); - value.unwrap().map(|v| v.0) - }, - )?; - let rhs = region.assign_advice( - || "rhs", - self.config.b, - 0, - || value.unwrap().map(|v| v.1), - )?; - let out = region.assign_advice( - || "out", - self.config.c, - 0, - || value.unwrap().map(|v| v.2), - )?; - - region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ZERO))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ZERO))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; - region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; - Ok((lhs.cell(), rhs.cell(), out.cell())) - }, - ) - } - fn raw_add( - &self, - layouter: &mut impl Layouter, - mut f: F, - ) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, - { - layouter.assign_region( - || "raw_add", - |mut region| { - let mut value = None; - let lhs = region.assign_advice( - || "lhs", - self.config.a, - 0, - || { - value = Some(f()); - value.unwrap().map(|v| v.0) - }, - )?; - let rhs = region.assign_advice( - || "rhs", - self.config.b, - 0, - || value.unwrap().map(|v| v.1), - )?; - let out = region.assign_advice( - || "out", - self.config.c, - 0, - || value.unwrap().map(|v| v.2), - )?; - - region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ONE))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ONE))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; - region.assign_fixed( - || "a * b", - self.config.sm, - 0, - || Value::known(FF::ZERO), - )?; - Ok((lhs.cell(), rhs.cell(), out.cell())) - }, - ) - } - fn copy( - &self, - layouter: &mut impl Layouter, - left: Cell, - right: Cell, - ) -> Result<(), Error> { - layouter.assign_region(|| "copy", |mut region| region.constrain_equal(left, right)) - } - } - - impl Circuit for MyCircuit { - type Config = PlonkConfig; - type FloorPlanner = SimpleFloorPlanner; - #[cfg(feature = "circuit-params")] - type Params = (); - - fn without_witnesses(&self) -> Self { - Self { - a: Value::unknown(), - k: self.k, - } - } - - fn configure(meta: &mut ConstraintSystem) -> PlonkConfig { - meta.set_minimum_degree(5); - - let a = meta.advice_column(); - let b = meta.advice_column(); - let c = meta.advice_column(); - - meta.enable_equality(a); - meta.enable_equality(b); - meta.enable_equality(c); - - let sm = meta.fixed_column(); - let sa = meta.fixed_column(); - let sb = meta.fixed_column(); - let sc = meta.fixed_column(); - - meta.create_gate("Combined add-mult", |meta| { - let a = meta.query_advice(a, Rotation::cur()); - let b = meta.query_advice(b, Rotation::cur()); - let c = meta.query_advice(c, Rotation::cur()); - - let sa = meta.query_fixed(sa, Rotation::cur()); - let sb = meta.query_fixed(sb, Rotation::cur()); - let sc = meta.query_fixed(sc, Rotation::cur()); - let sm = meta.query_fixed(sm, Rotation::cur()); - - vec![a.clone() * sa + b.clone() * sb + a * b * sm - (c * sc)] - }); - - PlonkConfig { - a, - b, - c, - sa, - sb, - sc, - sm, - } - } - - fn synthesize( - &self, - config: PlonkConfig, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let cs = StandardPlonk::new(config); - - for _ in 0..((1 << (self.k - 1)) - 3) { - let a: Value> = self.a.into(); - let mut a_squared = Value::unknown(); - let (a0, _, c0) = cs.raw_multiply(&mut layouter, || { - a_squared = a.square(); - a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) - })?; - let (a1, b1, _) = cs.raw_add(&mut layouter, || { - let fin = a_squared + a; - a.zip(a_squared) - .zip(fin) - .map(|((a, a_squared), fin)| (a, a_squared, fin)) - })?; - cs.copy(&mut layouter, a0, a1)?; - cs.copy(&mut layouter, b1, c0)?; - } - - Ok(()) - } - } - - fn keygen(k: u32) -> (ParamsKZG, ProvingKey) { - let params: ParamsKZG = ParamsKZG::new(k); - let empty_circuit: MyCircuit = MyCircuit { - a: Value::unknown(), - k, - }; - let vk = keygen_vk(¶ms, &empty_circuit).expect("keygen_vk should not fail"); - let pk = keygen_pk(¶ms, vk, &empty_circuit).expect("keygen_pk should not fail"); - (params, pk) - } - - fn prover( - k: u32, - params: &ParamsKZG, - pk: &ProvingKey, - ) -> Vec { - let rng = OsRng; - - let circuit: MyCircuit = MyCircuit { - a: Value::known(bn256::Fr::random(rng)), - k, - }; - - let mut transcript = Blake2bWrite::<_, _, Challenge255>::init(vec![]); - create_proof::, ProverGWC, _, _, _, _>( - params, - pk, - &[circuit], - &[&[]], - rng, - &mut transcript, - ) - .expect("proof generation should not fail"); - transcript.finalize() - } - - fn verifier( - params: &ParamsKZG, - vk: &VerifyingKey, - proof: &[u8], - ) { - let strategy = SingleStrategy::new(params); - let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(proof); - assert!(verify_proof::<_, VerifierGWC, _, _, _>( - params, - vk, - strategy, - &[&[]], - &mut transcript - ) - .is_ok()); - } - - let k_range = 8..=16; - - let mut keygen_group = c.benchmark_group("plonk-keygen"); - keygen_group.sample_size(10); - for k in k_range.clone() { - keygen_group.bench_with_input(BenchmarkId::from_parameter(k), &k, |b, &k| { - b.iter(|| keygen(k)); - }); - } - keygen_group.finish(); - - let mut prover_group = c.benchmark_group("plonk-prover"); - prover_group.sample_size(10); - for k in k_range.clone() { - let (params, pk) = keygen(k); - - prover_group.bench_with_input( - BenchmarkId::from_parameter(k), - &(k, ¶ms, &pk), - |b, &(k, params, pk)| { - b.iter(|| prover(k, params, pk)); - }, - ); - } - prover_group.finish(); - - let mut verifier_group = c.benchmark_group("plonk-verifier"); - for k in k_range { - let (params, pk) = keygen(k); - let proof = prover(k, ¶ms, &pk); - - verifier_group.bench_with_input( - BenchmarkId::from_parameter(k), - &(¶ms, pk.get_vk(), &proof[..]), - |b, &(params, vk, proof)| { - b.iter(|| verifier(params, vk, proof)); - }, - ); - } - verifier_group.finish(); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); +// #[macro_use] +// extern crate criterion; +// +// use group::ff::Field; +// use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; +// use halo2_proofs::plonk::*; +// use halo2_proofs::poly::{commitment::ParamsProver, Rotation}; +// use halo2_proofs::transcript::{Blake2bRead, Blake2bWrite, Challenge255}; +// use halo2curves::bn256; +// use rand_core::OsRng; +// +// use halo2_proofs::transcript::{TranscriptReadBuffer, TranscriptWriterBuffer}; +// +// use std::marker::PhantomData; +// +// use criterion::{BenchmarkId, Criterion}; +// use halo2_proofs::poly::kzg::params::{KZGCommitmentScheme, ParamsKZG}; +// use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; +// use halo2_proofs::poly::kzg::strategy::SingleStrategy; +// +// fn criterion_benchmark(c: &mut Criterion) { +// /// This represents an advice column at a certain row in the ConstraintSystem +// #[derive(Copy, Clone, Debug)] +// pub struct Variable(Column, usize); +// +// #[derive(Clone)] +// struct PlonkConfig { +// a: Column, +// b: Column, +// c: Column, +// +// sa: Column, +// sb: Column, +// sc: Column, +// sm: Column, +// } +// +// trait StandardCs { +// fn raw_multiply( +// &self, +// layouter: &mut impl Layouter, +// f: F, +// ) -> Result<(Cell, Cell, Cell), Error> +// where +// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; +// fn raw_add( +// &self, +// layouter: &mut impl Layouter, +// f: F, +// ) -> Result<(Cell, Cell, Cell), Error> +// where +// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; +// fn copy(&self, layouter: &mut impl Layouter, a: Cell, b: Cell) -> Result<(), Error>; +// } +// +// #[derive(Clone)] +// struct MyCircuit { +// a: Value, +// k: u32, +// } +// +// struct StandardPlonk { +// config: PlonkConfig, +// _marker: PhantomData, +// } +// +// impl StandardPlonk { +// fn new(config: PlonkConfig) -> Self { +// StandardPlonk { +// config, +// _marker: PhantomData, +// } +// } +// } +// +// impl StandardCs for StandardPlonk { +// fn raw_multiply( +// &self, +// layouter: &mut impl Layouter, +// mut f: F, +// ) -> Result<(Cell, Cell, Cell), Error> +// where +// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, +// { +// layouter.assign_region( +// || "raw_multiply", +// |mut region| { +// let mut value = None; +// let lhs = region.assign_advice( +// || "lhs", +// self.config.a, +// 0, +// || { +// value = Some(f()); +// value.unwrap().map(|v| v.0) +// }, +// )?; +// let rhs = region.assign_advice( +// || "rhs", +// self.config.b, +// 0, +// || value.unwrap().map(|v| v.1), +// )?; +// let out = region.assign_advice( +// || "out", +// self.config.c, +// 0, +// || value.unwrap().map(|v| v.2), +// )?; +// +// region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ZERO))?; +// region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ZERO))?; +// region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; +// region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; +// Ok((lhs.cell(), rhs.cell(), out.cell())) +// }, +// ) +// } +// fn raw_add( +// &self, +// layouter: &mut impl Layouter, +// mut f: F, +// ) -> Result<(Cell, Cell, Cell), Error> +// where +// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, +// { +// layouter.assign_region( +// || "raw_add", +// |mut region| { +// let mut value = None; +// let lhs = region.assign_advice( +// || "lhs", +// self.config.a, +// 0, +// || { +// value = Some(f()); +// value.unwrap().map(|v| v.0) +// }, +// )?; +// let rhs = region.assign_advice( +// || "rhs", +// self.config.b, +// 0, +// || value.unwrap().map(|v| v.1), +// )?; +// let out = region.assign_advice( +// || "out", +// self.config.c, +// 0, +// || value.unwrap().map(|v| v.2), +// )?; +// +// region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ONE))?; +// region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ONE))?; +// region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; +// region.assign_fixed( +// || "a * b", +// self.config.sm, +// 0, +// || Value::known(FF::ZERO), +// )?; +// Ok((lhs.cell(), rhs.cell(), out.cell())) +// }, +// ) +// } +// fn copy( +// &self, +// layouter: &mut impl Layouter, +// left: Cell, +// right: Cell, +// ) -> Result<(), Error> { +// layouter.assign_region(|| "copy", |mut region| region.constrain_equal(left, right)) +// } +// } +// +// impl Circuit for MyCircuit { +// type Config = PlonkConfig; +// type FloorPlanner = SimpleFloorPlanner; +// #[cfg(feature = "circuit-params")] +// type Params = (); +// +// fn without_witnesses(&self) -> Self { +// Self { +// a: Value::unknown(), +// k: self.k, +// } +// } +// +// fn configure(meta: &mut ConstraintSystem) -> PlonkConfig { +// meta.set_minimum_degree(5); +// +// let a = meta.advice_column(); +// let b = meta.advice_column(); +// let c = meta.advice_column(); +// +// meta.enable_equality(a); +// meta.enable_equality(b); +// meta.enable_equality(c); +// +// let sm = meta.fixed_column(); +// let sa = meta.fixed_column(); +// let sb = meta.fixed_column(); +// let sc = meta.fixed_column(); +// +// meta.create_gate("Combined add-mult", |meta| { +// let a = meta.query_advice(a, Rotation::cur()); +// let b = meta.query_advice(b, Rotation::cur()); +// let c = meta.query_advice(c, Rotation::cur()); +// +// let sa = meta.query_fixed(sa, Rotation::cur()); +// let sb = meta.query_fixed(sb, Rotation::cur()); +// let sc = meta.query_fixed(sc, Rotation::cur()); +// let sm = meta.query_fixed(sm, Rotation::cur()); +// +// vec![a.clone() * sa + b.clone() * sb + a * b * sm - (c * sc)] +// }); +// +// PlonkConfig { +// a, +// b, +// c, +// sa, +// sb, +// sc, +// sm, +// } +// } +// +// fn synthesize( +// &self, +// config: PlonkConfig, +// mut layouter: impl Layouter, +// ) -> Result<(), Error> { +// let cs = StandardPlonk::new(config); +// +// for _ in 0..((1 << (self.k - 1)) - 3) { +// let a: Value> = self.a.into(); +// let mut a_squared = Value::unknown(); +// let (a0, _, c0) = cs.raw_multiply(&mut layouter, || { +// a_squared = a.square(); +// a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) +// })?; +// let (a1, b1, _) = cs.raw_add(&mut layouter, || { +// let fin = a_squared + a; +// a.zip(a_squared) +// .zip(fin) +// .map(|((a, a_squared), fin)| (a, a_squared, fin)) +// })?; +// cs.copy(&mut layouter, a0, a1)?; +// cs.copy(&mut layouter, b1, c0)?; +// } +// +// Ok(()) +// } +// } +// +// fn keygen(k: u32) -> (ParamsKZG, ProvingKey) { +// let params: ParamsKZG = ParamsKZG::new(k); +// let empty_circuit: MyCircuit = MyCircuit { +// a: Value::unknown(), +// k, +// }; +// let vk = keygen_vk(¶ms, &empty_circuit).expect("keygen_vk should not fail"); +// let pk = keygen_pk(¶ms, vk, &empty_circuit).expect("keygen_pk should not fail"); +// (params, pk) +// } +// +// fn prover( +// k: u32, +// params: &ParamsKZG, +// pk: &ProvingKey, +// ) -> Vec { +// let rng = OsRng; +// +// let circuit: MyCircuit = MyCircuit { +// a: Value::known(bn256::Fr::random(rng)), +// k, +// }; +// +// let mut transcript = Blake2bWrite::<_, _, Challenge255>::init(vec![]); +// create_proof::, ProverGWC, _, _, _, _>( +// params, +// pk, +// &[circuit], +// &[&[]], +// rng, +// &mut transcript, +// ) +// .expect("proof generation should not fail"); +// transcript.finalize() +// } +// +// fn verifier( +// params: &ParamsKZG, +// vk: &VerifyingKey, +// proof: &[u8], +// ) { +// let strategy = SingleStrategy::new(params); +// let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(proof); +// assert!(verify_proof::<_, VerifierGWC, _, _, _>( +// params, +// vk, +// strategy, +// &[&[]], +// &mut transcript +// ) +// .is_ok()); +// } +// +// let k_range = 8..=16; +// +// let mut keygen_group = c.benchmark_group("plonk-keygen"); +// keygen_group.sample_size(10); +// for k in k_range.clone() { +// keygen_group.bench_with_input(BenchmarkId::from_parameter(k), &k, |b, &k| { +// b.iter(|| keygen(k)); +// }); +// } +// keygen_group.finish(); +// +// let mut prover_group = c.benchmark_group("plonk-prover"); +// prover_group.sample_size(10); +// for k in k_range.clone() { +// let (params, pk) = keygen(k); +// +// prover_group.bench_with_input( +// BenchmarkId::from_parameter(k), +// &(k, ¶ms, &pk), +// |b, &(k, params, pk)| { +// b.iter(|| prover(k, params, pk)); +// }, +// ); +// } +// prover_group.finish(); +// +// let mut verifier_group = c.benchmark_group("plonk-verifier"); +// for k in k_range { +// let (params, pk) = keygen(k); +// let proof = prover(k, ¶ms, &pk); +// +// verifier_group.bench_with_input( +// BenchmarkId::from_parameter(k), +// &(¶ms, pk.get_vk(), &proof[..]), +// |b, &(params, vk, proof)| { +// b.iter(|| verifier(params, vk, proof)); +// }, +// ); +// } +// verifier_group.finish(); +// } +// +// criterion_group!(benches, criterion_benchmark); +// criterion_main!(benches); diff --git a/examples/circuit-layout.rs b/examples/circuit-layout.rs index b65adf5599..0862485ff4 100644 --- a/examples/circuit-layout.rs +++ b/examples/circuit-layout.rs @@ -1,306 +1,308 @@ -use ff::Field; -use halo2_proofs::{ - circuit::{Cell, Layouter, Region, SimpleFloorPlanner, Value}, - plonk::{Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, TableColumn}, - poly::Rotation, -}; -use halo2curves::pasta::Fp; -use rand_core::OsRng; -use std::marker::PhantomData; +fn main() {} -/// This represents an advice column at a certain row in the ConstraintSystem -#[derive(Copy, Clone, Debug)] -pub struct Variable(Column, usize); - -#[derive(Clone)] -struct PlonkConfig { - a: Column, - b: Column, - c: Column, - d: Column, - e: Column, - - sa: Column, - sb: Column, - sc: Column, - sm: Column, - sl: TableColumn, -} - -trait StandardCs { - fn raw_multiply(&self, region: &mut Region, f: F) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; - fn raw_add(&self, region: &mut Region, f: F) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; - fn copy(&self, region: &mut Region, a: Cell, b: Cell) -> Result<(), Error>; - fn lookup_table(&self, layouter: &mut impl Layouter, values: &[FF]) -> Result<(), Error>; -} - -struct MyCircuit { - a: Value, - lookup_table: Vec, -} - -struct StandardPlonk { - config: PlonkConfig, - _marker: PhantomData, -} - -impl StandardPlonk { - fn new(config: PlonkConfig) -> Self { - StandardPlonk { - config, - _marker: PhantomData, - } - } -} - -impl StandardCs for StandardPlonk { - fn raw_multiply( - &self, - region: &mut Region, - mut f: F, - ) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, - { - let mut value = None; - let lhs = region.assign_advice( - || "lhs", - self.config.a, - 0, - || { - value = Some(f()); - value.unwrap().map(|v| v.0) - }, - )?; - region.assign_advice( - || "lhs^4", - self.config.d, - 0, - || value.unwrap().map(|v| v.0).square().square(), - )?; - let rhs = - region.assign_advice(|| "rhs", self.config.b, 0, || value.unwrap().map(|v| v.1))?; - region.assign_advice( - || "rhs^4", - self.config.e, - 0, - || value.unwrap().map(|v| v.1).square().square(), - )?; - let out = - region.assign_advice(|| "out", self.config.c, 0, || value.unwrap().map(|v| v.2))?; - - region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ZERO))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ZERO))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; - region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; - Ok((lhs.cell(), rhs.cell(), out.cell())) - } - fn raw_add(&self, region: &mut Region, mut f: F) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, - { - let mut value = None; - let lhs = region.assign_advice( - || "lhs", - self.config.a, - 0, - || { - value = Some(f()); - value.unwrap().map(|v| v.0) - }, - )?; - region.assign_advice( - || "lhs^4", - self.config.d, - 0, - || value.unwrap().map(|v| v.0.square().square()), - )?; - let rhs = - region.assign_advice(|| "rhs", self.config.b, 0, || value.unwrap().map(|v| v.1))?; - region.assign_advice( - || "rhs^4", - self.config.e, - 0, - || value.unwrap().map(|v| v.1.square().square()), - )?; - let out = - region.assign_advice(|| "out", self.config.c, 0, || value.unwrap().map(|v| v.2))?; - - region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ONE))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ONE))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; - region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ZERO))?; - Ok((lhs.cell(), rhs.cell(), out.cell())) - } - fn copy(&self, region: &mut Region, left: Cell, right: Cell) -> Result<(), Error> { - region.constrain_equal(left, right) - } - fn lookup_table(&self, layouter: &mut impl Layouter, values: &[FF]) -> Result<(), Error> { - layouter.assign_table( - || "", - |mut table| { - for (index, &value) in values.iter().enumerate() { - table.assign_cell( - || "table col", - self.config.sl, - index, - || Value::known(value), - )?; - } - Ok(()) - }, - )?; - Ok(()) - } -} - -impl Circuit for MyCircuit { - type Config = PlonkConfig; - type FloorPlanner = SimpleFloorPlanner; - #[cfg(feature = "circuit-params")] - type Params = (); - - fn without_witnesses(&self) -> Self { - Self { - a: Value::unknown(), - lookup_table: self.lookup_table.clone(), - } - } - - #[allow(clippy::many_single_char_names)] - fn configure(meta: &mut ConstraintSystem) -> PlonkConfig { - let e = meta.advice_column(); - let a = meta.advice_column(); - let b = meta.advice_column(); - let sf = meta.fixed_column(); - let c = meta.advice_column(); - let d = meta.advice_column(); - - meta.enable_equality(a); - meta.enable_equality(b); - meta.enable_equality(c); - - let sm = meta.fixed_column(); - let sa = meta.fixed_column(); - let sb = meta.fixed_column(); - let sc = meta.fixed_column(); - let sl = meta.lookup_table_column(); - - /* - * A B ... sl - * [ - * instance 0 ... 0 - * a a ... 0 - * a a^2 ... 0 - * a a ... 0 - * a a^2 ... 0 - * ... ... ... ... - * ... ... ... instance - * ... ... ... a - * ... ... ... a - * ... ... ... 0 - * ] - */ - meta.lookup("lookup", |meta| { - let a_ = meta.query_any(a, Rotation::cur()); - vec![(a_, sl)] - }); - - meta.create_gate("Combined add-mult", |meta| { - let d = meta.query_advice(d, Rotation::next()); - let a = meta.query_advice(a, Rotation::cur()); - let sf = meta.query_fixed(sf, Rotation::cur()); - let e = meta.query_advice(e, Rotation::prev()); - let b = meta.query_advice(b, Rotation::cur()); - let c = meta.query_advice(c, Rotation::cur()); - - let sa = meta.query_fixed(sa, Rotation::cur()); - let sb = meta.query_fixed(sb, Rotation::cur()); - let sc = meta.query_fixed(sc, Rotation::cur()); - let sm = meta.query_fixed(sm, Rotation::cur()); - - vec![a.clone() * sa + b.clone() * sb + a * b * sm - (c * sc) + sf * (d * e)] - }); - - PlonkConfig { - a, - b, - c, - d, - e, - sa, - sb, - sc, - sm, - sl, - } - } - - fn synthesize(&self, config: PlonkConfig, mut layouter: impl Layouter) -> Result<(), Error> { - let cs = StandardPlonk::new(config); - - for i in 0..10 { - layouter.assign_region( - || format!("region_{i}"), - |mut region| { - let a: Value> = self.a.into(); - let mut a_squared = Value::unknown(); - let (a0, _, c0) = cs.raw_multiply(&mut region, || { - a_squared = a.square(); - a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) - })?; - let (a1, b1, _) = cs.raw_add(&mut region, || { - let fin = a_squared + a; - a.zip(a_squared) - .zip(fin) - .map(|((a, a_squared), fin)| (a, a_squared, fin)) - })?; - cs.copy(&mut region, a0, a1)?; - cs.copy(&mut region, b1, c0) - }, - )?; - } - - cs.lookup_table(&mut layouter, &self.lookup_table)?; - - Ok(()) - } -} - -// ANCHOR: dev-graph -fn main() { - // Prepare the circuit you want to render. - // You don't need to include any witness variables. - let a = Fp::random(OsRng); - let instance = Fp::one() + Fp::one(); - let lookup_table = vec![instance, a, a, Fp::zero()]; - let circuit: MyCircuit = MyCircuit { - a: Value::unknown(), - lookup_table, - }; - - // Create the area you want to draw on. - // Use SVGBackend if you want to render to .svg instead. - use plotters::prelude::*; - let root = BitMapBackend::new("layout.png", (1024, 768)).into_drawing_area(); - root.fill(&WHITE).unwrap(); - let root = root - .titled("Example Circuit Layout", ("sans-serif", 60)) - .unwrap(); - - halo2_proofs::dev::CircuitLayout::default() - // You can optionally render only a section of the circuit. - .view_width(0..2) - .view_height(0..16) - // You can hide labels, which can be useful with smaller areas. - .show_labels(false) - // Render the circuit onto your area! - // The first argument is the size parameter for the circuit. - .render(5, &circuit, &root) - .unwrap(); -} -// ANCHOR_END: dev-graph +// use ff::Field; +// use halo2_proofs::{ +// circuit::{Cell, Layouter, Region, SimpleFloorPlanner, Value}, +// plonk::{Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, TableColumn}, +// poly::Rotation, +// }; +// use halo2curves::pasta::Fp; +// use rand_core::OsRng; +// use std::marker::PhantomData; +// +// /// This represents an advice column at a certain row in the ConstraintSystem +// #[derive(Copy, Clone, Debug)] +// pub struct Variable(Column, usize); +// +// #[derive(Clone)] +// struct PlonkConfig { +// a: Column, +// b: Column, +// c: Column, +// d: Column, +// e: Column, +// +// sa: Column, +// sb: Column, +// sc: Column, +// sm: Column, +// sl: TableColumn, +// } +// +// trait StandardCs { +// fn raw_multiply(&self, region: &mut Region, f: F) -> Result<(Cell, Cell, Cell), Error> +// where +// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; +// fn raw_add(&self, region: &mut Region, f: F) -> Result<(Cell, Cell, Cell), Error> +// where +// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; +// fn copy(&self, region: &mut Region, a: Cell, b: Cell) -> Result<(), Error>; +// fn lookup_table(&self, layouter: &mut impl Layouter, values: &[FF]) -> Result<(), Error>; +// } +// +// struct MyCircuit { +// a: Value, +// lookup_table: Vec, +// } +// +// struct StandardPlonk { +// config: PlonkConfig, +// _marker: PhantomData, +// } +// +// impl StandardPlonk { +// fn new(config: PlonkConfig) -> Self { +// StandardPlonk { +// config, +// _marker: PhantomData, +// } +// } +// } +// +// impl StandardCs for StandardPlonk { +// fn raw_multiply( +// &self, +// region: &mut Region, +// mut f: F, +// ) -> Result<(Cell, Cell, Cell), Error> +// where +// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, +// { +// let mut value = None; +// let lhs = region.assign_advice( +// || "lhs", +// self.config.a, +// 0, +// || { +// value = Some(f()); +// value.unwrap().map(|v| v.0) +// }, +// )?; +// region.assign_advice( +// || "lhs^4", +// self.config.d, +// 0, +// || value.unwrap().map(|v| v.0).square().square(), +// )?; +// let rhs = +// region.assign_advice(|| "rhs", self.config.b, 0, || value.unwrap().map(|v| v.1))?; +// region.assign_advice( +// || "rhs^4", +// self.config.e, +// 0, +// || value.unwrap().map(|v| v.1).square().square(), +// )?; +// let out = +// region.assign_advice(|| "out", self.config.c, 0, || value.unwrap().map(|v| v.2))?; +// +// region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ZERO))?; +// region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ZERO))?; +// region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; +// region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; +// Ok((lhs.cell(), rhs.cell(), out.cell())) +// } +// fn raw_add(&self, region: &mut Region, mut f: F) -> Result<(Cell, Cell, Cell), Error> +// where +// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, +// { +// let mut value = None; +// let lhs = region.assign_advice( +// || "lhs", +// self.config.a, +// 0, +// || { +// value = Some(f()); +// value.unwrap().map(|v| v.0) +// }, +// )?; +// region.assign_advice( +// || "lhs^4", +// self.config.d, +// 0, +// || value.unwrap().map(|v| v.0.square().square()), +// )?; +// let rhs = +// region.assign_advice(|| "rhs", self.config.b, 0, || value.unwrap().map(|v| v.1))?; +// region.assign_advice( +// || "rhs^4", +// self.config.e, +// 0, +// || value.unwrap().map(|v| v.1.square().square()), +// )?; +// let out = +// region.assign_advice(|| "out", self.config.c, 0, || value.unwrap().map(|v| v.2))?; +// +// region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ONE))?; +// region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ONE))?; +// region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; +// region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ZERO))?; +// Ok((lhs.cell(), rhs.cell(), out.cell())) +// } +// fn copy(&self, region: &mut Region, left: Cell, right: Cell) -> Result<(), Error> { +// region.constrain_equal(left, right) +// } +// fn lookup_table(&self, layouter: &mut impl Layouter, values: &[FF]) -> Result<(), Error> { +// layouter.assign_table( +// || "", +// |mut table| { +// for (index, &value) in values.iter().enumerate() { +// table.assign_cell( +// || "table col", +// self.config.sl, +// index, +// || Value::known(value), +// )?; +// } +// Ok(()) +// }, +// )?; +// Ok(()) +// } +// } +// +// impl Circuit for MyCircuit { +// type Config = PlonkConfig; +// type FloorPlanner = SimpleFloorPlanner; +// #[cfg(feature = "circuit-params")] +// type Params = (); +// +// fn without_witnesses(&self) -> Self { +// Self { +// a: Value::unknown(), +// lookup_table: self.lookup_table.clone(), +// } +// } +// +// #[allow(clippy::many_single_char_names)] +// fn configure(meta: &mut ConstraintSystem) -> PlonkConfig { +// let e = meta.advice_column(); +// let a = meta.advice_column(); +// let b = meta.advice_column(); +// let sf = meta.fixed_column(); +// let c = meta.advice_column(); +// let d = meta.advice_column(); +// +// meta.enable_equality(a); +// meta.enable_equality(b); +// meta.enable_equality(c); +// +// let sm = meta.fixed_column(); +// let sa = meta.fixed_column(); +// let sb = meta.fixed_column(); +// let sc = meta.fixed_column(); +// let sl = meta.lookup_table_column(); +// +// /* +// * A B ... sl +// * [ +// * instance 0 ... 0 +// * a a ... 0 +// * a a^2 ... 0 +// * a a ... 0 +// * a a^2 ... 0 +// * ... ... ... ... +// * ... ... ... instance +// * ... ... ... a +// * ... ... ... a +// * ... ... ... 0 +// * ] +// */ +// meta.lookup("lookup", |meta| { +// let a_ = meta.query_any(a, Rotation::cur()); +// vec![(a_, sl)] +// }); +// +// meta.create_gate("Combined add-mult", |meta| { +// let d = meta.query_advice(d, Rotation::next()); +// let a = meta.query_advice(a, Rotation::cur()); +// let sf = meta.query_fixed(sf, Rotation::cur()); +// let e = meta.query_advice(e, Rotation::prev()); +// let b = meta.query_advice(b, Rotation::cur()); +// let c = meta.query_advice(c, Rotation::cur()); +// +// let sa = meta.query_fixed(sa, Rotation::cur()); +// let sb = meta.query_fixed(sb, Rotation::cur()); +// let sc = meta.query_fixed(sc, Rotation::cur()); +// let sm = meta.query_fixed(sm, Rotation::cur()); +// +// vec![a.clone() * sa + b.clone() * sb + a * b * sm - (c * sc) + sf * (d * e)] +// }); +// +// PlonkConfig { +// a, +// b, +// c, +// d, +// e, +// sa, +// sb, +// sc, +// sm, +// sl, +// } +// } +// +// fn synthesize(&self, config: PlonkConfig, mut layouter: impl Layouter) -> Result<(), Error> { +// let cs = StandardPlonk::new(config); +// +// for i in 0..10 { +// layouter.assign_region( +// || format!("region_{i}"), +// |mut region| { +// let a: Value> = self.a.into(); +// let mut a_squared = Value::unknown(); +// let (a0, _, c0) = cs.raw_multiply(&mut region, || { +// a_squared = a.square(); +// a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) +// })?; +// let (a1, b1, _) = cs.raw_add(&mut region, || { +// let fin = a_squared + a; +// a.zip(a_squared) +// .zip(fin) +// .map(|((a, a_squared), fin)| (a, a_squared, fin)) +// })?; +// cs.copy(&mut region, a0, a1)?; +// cs.copy(&mut region, b1, c0) +// }, +// )?; +// } +// +// cs.lookup_table(&mut layouter, &self.lookup_table)?; +// +// Ok(()) +// } +// } +// +// // ANCHOR: dev-graph +// fn main() { +// // Prepare the circuit you want to render. +// // You don't need to include any witness variables. +// let a = Fp::random(OsRng); +// let instance = Fp::one() + Fp::one(); +// let lookup_table = vec![instance, a, a, Fp::zero()]; +// let circuit: MyCircuit = MyCircuit { +// a: Value::unknown(), +// lookup_table, +// }; +// +// // Create the area you want to draw on. +// // Use SVGBackend if you want to render to .svg instead. +// use plotters::prelude::*; +// let root = BitMapBackend::new("layout.png", (1024, 768)).into_drawing_area(); +// root.fill(&WHITE).unwrap(); +// let root = root +// .titled("Example Circuit Layout", ("sans-serif", 60)) +// .unwrap(); +// +// halo2_proofs::dev::CircuitLayout::default() +// // You can optionally render only a section of the circuit. +// .view_width(0..2) +// .view_height(0..16) +// // You can hide labels, which can be useful with smaller areas. +// .show_labels(false) +// // Render the circuit onto your area! +// // The first argument is the size parameter for the circuit. +// .render(5, &circuit, &root) +// .unwrap(); +// } +// // ANCHOR_END: dev-graph diff --git a/examples/proof-size.rs b/examples/proof-size.rs index 3d5b242fb0..139baaab19 100644 --- a/examples/proof-size.rs +++ b/examples/proof-size.rs @@ -1,101 +1,103 @@ -use ff::Field; -use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner, Value}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, -}; -use halo2curves::pasta::Fp; +fn main() {} -use halo2_proofs::dev::cost_model::{from_circuit_to_model_circuit, CommitmentScheme}; -use halo2_proofs::plonk::{Expression, Selector, TableColumn}; -use halo2_proofs::poly::Rotation; - -// We use a lookup example -#[derive(Clone, Copy)] -struct TestCircuit {} - -#[derive(Debug, Clone)] -struct MyConfig { - selector: Selector, - table: TableColumn, - advice: Column, -} - -impl Circuit for TestCircuit { - type Config = MyConfig; - type FloorPlanner = SimpleFloorPlanner; - #[cfg(feature = "circuit-params")] - type Params = (); - - fn without_witnesses(&self) -> Self { - Self {} - } - - fn configure(meta: &mut ConstraintSystem) -> MyConfig { - let config = MyConfig { - selector: meta.complex_selector(), - table: meta.lookup_table_column(), - advice: meta.advice_column(), - }; - - meta.lookup("lookup", |meta| { - let selector = meta.query_selector(config.selector); - let not_selector = Expression::Constant(Fp::ONE) - selector.clone(); - let advice = meta.query_advice(config.advice, Rotation::cur()); - vec![(selector * advice + not_selector, config.table)] - }); - - config - } - - fn synthesize(&self, config: MyConfig, mut layouter: impl Layouter) -> Result<(), Error> { - layouter.assign_table( - || "8-bit table", - |mut table| { - for row in 0u64..(1 << 8) { - table.assign_cell( - || format!("row {row}"), - config.table, - row as usize, - || Value::known(Fp::from(row + 1)), - )?; - } - - Ok(()) - }, - )?; - - layouter.assign_region( - || "assign values", - |mut region| { - for offset in 0u64..(1 << 10) { - config.selector.enable(&mut region, offset as usize)?; - region.assign_advice( - || format!("offset {offset}"), - config.advice, - offset as usize, - || Value::known(Fp::from((offset % 256) + 1)), - )?; - } - - Ok(()) - }, - ) - } -} - -const K: u32 = 11; - -fn main() { - let circuit = TestCircuit {}; - - let model = from_circuit_to_model_circuit::<_, _, 56, 56>( - K, - &circuit, - vec![], - CommitmentScheme::KZGGWC, - ); - println!( - "Cost of circuit with 8 bit lookup table: \n{}", - serde_json::to_string_pretty(&model).unwrap() - ); -} +// use ff::Field; +// use halo2_proofs::{ +// circuit::{Layouter, SimpleFloorPlanner, Value}, +// plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, +// }; +// use halo2curves::pasta::Fp; +// +// use halo2_proofs::dev::cost_model::{from_circuit_to_model_circuit, CommitmentScheme}; +// use halo2_proofs::plonk::{Expression, Selector, TableColumn}; +// use halo2_proofs::poly::Rotation; +// +// // We use a lookup example +// #[derive(Clone, Copy)] +// struct TestCircuit {} +// +// #[derive(Debug, Clone)] +// struct MyConfig { +// selector: Selector, +// table: TableColumn, +// advice: Column, +// } +// +// impl Circuit for TestCircuit { +// type Config = MyConfig; +// type FloorPlanner = SimpleFloorPlanner; +// #[cfg(feature = "circuit-params")] +// type Params = (); +// +// fn without_witnesses(&self) -> Self { +// Self {} +// } +// +// fn configure(meta: &mut ConstraintSystem) -> MyConfig { +// let config = MyConfig { +// selector: meta.complex_selector(), +// table: meta.lookup_table_column(), +// advice: meta.advice_column(), +// }; +// +// meta.lookup("lookup", |meta| { +// let selector = meta.query_selector(config.selector); +// let not_selector = Expression::Constant(Fp::ONE) - selector.clone(); +// let advice = meta.query_advice(config.advice, Rotation::cur()); +// vec![(selector * advice + not_selector, config.table)] +// }); +// +// config +// } +// +// fn synthesize(&self, config: MyConfig, mut layouter: impl Layouter) -> Result<(), Error> { +// layouter.assign_table( +// || "8-bit table", +// |mut table| { +// for row in 0u64..(1 << 8) { +// table.assign_cell( +// || format!("row {row}"), +// config.table, +// row as usize, +// || Value::known(Fp::from(row + 1)), +// )?; +// } +// +// Ok(()) +// }, +// )?; +// +// layouter.assign_region( +// || "assign values", +// |mut region| { +// for offset in 0u64..(1 << 10) { +// config.selector.enable(&mut region, offset as usize)?; +// region.assign_advice( +// || format!("offset {offset}"), +// config.advice, +// offset as usize, +// || Value::known(Fp::from((offset % 256) + 1)), +// )?; +// } +// +// Ok(()) +// }, +// ) +// } +// } +// +// const K: u32 = 11; +// +// fn main() { +// let circuit = TestCircuit {}; +// +// let model = from_circuit_to_model_circuit::<_, _, 56, 56>( +// K, +// &circuit, +// vec![], +// CommitmentScheme::KZGGWC, +// ); +// println!( +// "Cost of circuit with 8 bit lookup table: \n{}", +// serde_json::to_string_pretty(&model).unwrap() +// ); +// } diff --git a/examples/serialization.rs b/examples/serialization.rs index c4a84b7281..278101f0d6 100644 --- a/examples/serialization.rs +++ b/examples/serialization.rs @@ -1,192 +1,194 @@ -use std::{ - fs::File, - io::{BufReader, BufWriter, Write}, -}; - -use ff::Field; -use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner, Value}, - plonk::{ - create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, - ConstraintSystem, Error, Fixed, Instance, ProvingKey, - }, - poly::{ - kzg::{ - commitment::{KZGCommitmentScheme, ParamsKZG}, - gwc::{ProverGWC, VerifierGWC}, - strategy::SingleStrategy, - }, - Rotation, - }, - transcript::{ - Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, - }, - SerdeFormat, -}; -use halo2curves::bn256::{Bn256, Fr, G1Affine}; -use rand_core::OsRng; - -#[derive(Clone, Copy)] -struct StandardPlonkConfig { - a: Column, - b: Column, - c: Column, - q_a: Column, - q_b: Column, - q_c: Column, - q_ab: Column, - constant: Column, - #[allow(dead_code)] - instance: Column, -} - -impl StandardPlonkConfig { - fn configure(meta: &mut ConstraintSystem) -> Self { - let [a, b, c] = [(); 3].map(|_| meta.advice_column()); - let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); - let instance = meta.instance_column(); - - [a, b, c].map(|column| meta.enable_equality(column)); - - meta.create_gate( - "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", - |meta| { - 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())); - let instance = meta.query_instance(instance, Rotation::cur()); - Some( - q_a * a.clone() - + q_b * b.clone() - + q_c * c - + q_ab * a * b - + constant - + instance, - ) - }, - ); - - StandardPlonkConfig { - a, - b, - c, - q_a, - q_b, - q_c, - q_ab, - constant, - instance, - } - } -} - -#[derive(Clone, Default)] -struct StandardPlonk(Fr); - -impl Circuit for StandardPlonk { - type Config = StandardPlonkConfig; - type FloorPlanner = SimpleFloorPlanner; - #[cfg(feature = "circuit-params")] - type Params = (); - - 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| { - region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; - region.assign_fixed(|| "", config.q_a, 0, || Value::known(-Fr::one()))?; - - region.assign_advice(|| "", config.a, 1, || Value::known(-Fr::from(5u64)))?; - for (idx, column) in (1..).zip([ - config.q_a, - config.q_b, - config.q_c, - config.q_ab, - config.constant, - ]) { - region.assign_fixed(|| "", column, 1, || Value::known(Fr::from(idx as u64)))?; - } - - let a = region.assign_advice(|| "", config.a, 2, || Value::known(Fr::one()))?; - a.copy_advice(|| "", &mut region, config.b, 3)?; - a.copy_advice(|| "", &mut region, config.c, 4)?; - Ok(()) - }, - ) - } -} - -fn main() { - let k = 4; - let circuit = StandardPlonk(Fr::random(OsRng)); - let params = ParamsKZG::::setup(k, OsRng); - let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail"); - let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail"); - - let f = File::create("serialization-test.pk").unwrap(); - let mut writer = BufWriter::new(f); - pk.write(&mut writer, SerdeFormat::RawBytes).unwrap(); - writer.flush().unwrap(); - - let f = File::open("serialization-test.pk").unwrap(); - let mut reader = BufReader::new(f); - #[allow(clippy::unit_arg)] - let pk = ProvingKey::::read::<_, StandardPlonk>( - &mut reader, - SerdeFormat::RawBytes, - #[cfg(feature = "circuit-params")] - circuit.params(), - ) - .unwrap(); - - std::fs::remove_file("serialization-test.pk").unwrap(); - - let instances: &[&[Fr]] = &[&[circuit.0]]; - let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - create_proof::< - KZGCommitmentScheme, - ProverGWC<'_, Bn256>, - Challenge255, - _, - Blake2bWrite, G1Affine, Challenge255<_>>, - _, - >( - ¶ms, - &pk, - &[circuit], - &[instances], - OsRng, - &mut transcript, - ) - .expect("prover should not fail"); - let proof = transcript.finalize(); - - let strategy = SingleStrategy::new(¶ms); - let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - assert!(verify_proof::< - KZGCommitmentScheme, - VerifierGWC<'_, Bn256>, - Challenge255, - Blake2bRead<&[u8], G1Affine, Challenge255>, - SingleStrategy<'_, Bn256>, - >( - ¶ms, - pk.get_vk(), - strategy, - &[instances], - &mut transcript - ) - .is_ok()); -} +fn main() {} + +// use std::{ +// fs::File, +// io::{BufReader, BufWriter, Write}, +// }; +// +// use ff::Field; +// use halo2_proofs::{ +// circuit::{Layouter, SimpleFloorPlanner, Value}, +// plonk::{ +// create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, +// ConstraintSystem, Error, Fixed, Instance, ProvingKey, +// }, +// poly::{ +// kzg::{ +// params::{KZGCommitmentScheme, ParamsKZG}, +// gwc::{ProverGWC, VerifierGWC}, +// strategy::SingleStrategy, +// }, +// Rotation, +// }, +// transcript::{ +// Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, +// }, +// SerdeFormat, +// }; +// use halo2curves::bn256::{Bn256, Fr, G1Affine}; +// use rand_core::OsRng; +// +// #[derive(Clone, Copy)] +// struct StandardPlonkConfig { +// a: Column, +// b: Column, +// c: Column, +// q_a: Column, +// q_b: Column, +// q_c: Column, +// q_ab: Column, +// constant: Column, +// #[allow(dead_code)] +// instance: Column, +// } +// +// impl StandardPlonkConfig { +// fn configure(meta: &mut ConstraintSystem) -> Self { +// let [a, b, c] = [(); 3].map(|_| meta.advice_column()); +// let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); +// let instance = meta.instance_column(); +// +// [a, b, c].map(|column| meta.enable_equality(column)); +// +// meta.create_gate( +// "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", +// |meta| { +// 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())); +// let instance = meta.query_instance(instance, Rotation::cur()); +// Some( +// q_a * a.clone() +// + q_b * b.clone() +// + q_c * c +// + q_ab * a * b +// + constant +// + instance, +// ) +// }, +// ); +// +// StandardPlonkConfig { +// a, +// b, +// c, +// q_a, +// q_b, +// q_c, +// q_ab, +// constant, +// instance, +// } +// } +// } +// +// #[derive(Clone, Default)] +// struct StandardPlonk(Fr); +// +// impl Circuit for StandardPlonk { +// type Config = StandardPlonkConfig; +// type FloorPlanner = SimpleFloorPlanner; +// #[cfg(feature = "circuit-params")] +// type Params = (); +// +// 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| { +// region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; +// region.assign_fixed(|| "", config.q_a, 0, || Value::known(-Fr::one()))?; +// +// region.assign_advice(|| "", config.a, 1, || Value::known(-Fr::from(5u64)))?; +// for (idx, column) in (1..).zip([ +// config.q_a, +// config.q_b, +// config.q_c, +// config.q_ab, +// config.constant, +// ]) { +// region.assign_fixed(|| "", column, 1, || Value::known(Fr::from(idx as u64)))?; +// } +// +// let a = region.assign_advice(|| "", config.a, 2, || Value::known(Fr::one()))?; +// a.copy_advice(|| "", &mut region, config.b, 3)?; +// a.copy_advice(|| "", &mut region, config.c, 4)?; +// Ok(()) +// }, +// ) +// } +// } +// +// fn main() { +// let k = 4; +// let circuit = StandardPlonk(Fr::random(OsRng)); +// let params = ParamsKZG::::setup(k, OsRng); +// let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail"); +// let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail"); +// +// let f = File::create("serialization-test.pk").unwrap(); +// let mut writer = BufWriter::new(f); +// pk.write(&mut writer, SerdeFormat::RawBytes).unwrap(); +// writer.flush().unwrap(); +// +// let f = File::open("serialization-test.pk").unwrap(); +// let mut reader = BufReader::new(f); +// #[allow(clippy::unit_arg)] +// let pk = ProvingKey::::read::<_, StandardPlonk>( +// &mut reader, +// SerdeFormat::RawBytes, +// #[cfg(feature = "circuit-params")] +// circuit.params(), +// ) +// .unwrap(); +// +// std::fs::remove_file("serialization-test.pk").unwrap(); +// +// let instances: &[&[Fr]] = &[&[circuit.0]]; +// let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); +// create_proof::< +// KZGCommitmentScheme, +// ProverGWC<'_, Bn256>, +// Challenge255, +// _, +// Blake2bWrite, G1Affine, Challenge255<_>>, +// _, +// >( +// ¶ms, +// &pk, +// &[circuit], +// &[instances], +// OsRng, +// &mut transcript, +// ) +// .expect("prover should not fail"); +// let proof = transcript.finalize(); +// +// let strategy = SingleStrategy::new(¶ms); +// let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); +// assert!(verify_proof::< +// KZGCommitmentScheme, +// VerifierGWC<'_, Bn256>, +// Challenge255, +// Blake2bRead<&[u8], G1Affine, Challenge255>, +// SingleStrategy<'_, Bn256>, +// >( +// ¶ms, +// pk.get_vk(), +// strategy, +// &[instances], +// &mut transcript +// ) +// .is_ok()); +// } diff --git a/examples/shuffle.rs b/examples/shuffle.rs index 13c1cfba93..90c3241747 100644 --- a/examples/shuffle.rs +++ b/examples/shuffle.rs @@ -1,358 +1,360 @@ -use ff::{BatchInvert, FromUniformBytes, WithSmallOrderMulGroup}; -use halo2_proofs::arithmetic::CurveExt; -use halo2_proofs::helpers::SerdeCurveAffine; -use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; -use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; -use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; -use halo2_proofs::{ - arithmetic::Field, - circuit::{floor_planner::V1, Layouter, Value}, - dev::{metadata, FailureLocation, MockProver, VerifyFailure}, - plonk::*, - poly::{commitment::ParamsProver, VerificationStrategy}, - transcript::{ - Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, - }, -}; -use halo2curves::pairing::MultiMillerLoop; -use halo2curves::{bn256, pairing::Engine}; -use rand_core::{OsRng, RngCore}; -use std::iter; - -fn rand_2d_array(rng: &mut R) -> [[F; H]; W] { - [(); W].map(|_| [(); H].map(|_| F::random(&mut *rng))) -} - -fn shuffled( - original: [[F; H]; W], - rng: &mut R, -) -> [[F; H]; W] { - let mut shuffled = original; - - for row in (1..H).rev() { - let rand_row = (rng.next_u32() as usize) % row; - for column in shuffled.iter_mut() { - column.swap(row, rand_row); - } - } - - shuffled -} - -#[derive(Clone)] -struct MyConfig { - q_shuffle: Selector, - q_first: Selector, - q_last: Selector, - original: [Column; W], - shuffled: [Column; W], - theta: Challenge, - gamma: Challenge, - z: Column, -} - -impl MyConfig { - fn configure(meta: &mut ConstraintSystem) -> Self { - let [q_shuffle, q_first, q_last] = [(); 3].map(|_| meta.selector()); - // First phase - let original = [(); W].map(|_| meta.advice_column_in(FirstPhase)); - let shuffled = [(); W].map(|_| meta.advice_column_in(FirstPhase)); - let [theta, gamma] = [(); 2].map(|_| meta.challenge_usable_after(FirstPhase)); - // Second phase - let z = meta.advice_column_in(SecondPhase); - - meta.create_gate("z should start with 1", |_| { - let one = Expression::Constant(F::ONE); - - vec![q_first.expr() * (one - z.cur())] - }); - - meta.create_gate("z should end with 1", |_| { - let one = Expression::Constant(F::ONE); - - vec![q_last.expr() * (one - z.cur())] - }); - - meta.create_gate("z should have valid transition", |_| { - let q_shuffle = q_shuffle.expr(); - let original = original.map(|advice| advice.cur()); - let shuffled = shuffled.map(|advice| advice.cur()); - let [theta, gamma] = [theta, gamma].map(|challenge| challenge.expr()); - - // Compress - let original = original - .iter() - .cloned() - .reduce(|acc, a| acc * theta.clone() + a) - .unwrap(); - let shuffled = shuffled - .iter() - .cloned() - .reduce(|acc, a| acc * theta.clone() + a) - .unwrap(); - - vec![q_shuffle * (z.cur() * (original + gamma.clone()) - z.next() * (shuffled + gamma))] - }); - - Self { - q_shuffle, - q_first, - q_last, - original, - shuffled, - theta, - gamma, - z, - } - } -} - -#[derive(Clone, Default)] -struct MyCircuit { - original: Value<[[F; H]; W]>, - shuffled: Value<[[F; H]; W]>, -} - -impl MyCircuit { - fn rand(rng: &mut R) -> Self { - let original = rand_2d_array::(rng); - let shuffled = shuffled(original, rng); - - Self { - original: Value::known(original), - shuffled: Value::known(shuffled), - } - } -} - -impl Circuit for MyCircuit { - type Config = MyConfig; - type FloorPlanner = V1; - #[cfg(feature = "circuit-params")] - type Params = (); - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - MyConfig::configure(meta) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let theta = layouter.get_challenge(config.theta); - let gamma = layouter.get_challenge(config.gamma); - - layouter.assign_region( - || "Shuffle original into shuffled", - |mut region| { - // Keygen - config.q_first.enable(&mut region, 0)?; - config.q_last.enable(&mut region, H)?; - for offset in 0..H { - config.q_shuffle.enable(&mut region, offset)?; - } - - // First phase - for (idx, (&column, values)) in config - .original - .iter() - .zip(self.original.transpose_array().iter()) - .enumerate() - { - for (offset, &value) in values.transpose_array().iter().enumerate() { - region.assign_advice( - || format!("original[{idx}][{offset}]"), - column, - offset, - || value, - )?; - } - } - for (idx, (&column, values)) in config - .shuffled - .iter() - .zip(self.shuffled.transpose_array().iter()) - .enumerate() - { - for (offset, &value) in values.transpose_array().iter().enumerate() { - region.assign_advice( - || format!("shuffled[{idx}][{offset}]"), - column, - offset, - || value, - )?; - } - } - - // Second phase - let z = self.original.zip(self.shuffled).zip(theta).zip(gamma).map( - |(((original, shuffled), theta), gamma)| { - let mut product = vec![F::ZERO; H]; - for (idx, product) in product.iter_mut().enumerate() { - let mut compressed = F::ZERO; - for value in shuffled.iter() { - compressed *= theta; - compressed += value[idx]; - } - - *product = compressed + gamma - } - - product.iter_mut().batch_invert(); - - for (idx, product) in product.iter_mut().enumerate() { - let mut compressed = F::ZERO; - for value in original.iter() { - compressed *= theta; - compressed += value[idx]; - } - - *product *= compressed + gamma - } - - #[allow(clippy::let_and_return)] - let z = iter::once(F::ONE) - .chain(product) - .scan(F::ONE, |state, cur| { - *state *= &cur; - Some(*state) - }) - .collect::>(); - - #[cfg(feature = "sanity-checks")] - assert_eq!(F::ONE, *z.last().unwrap()); - - z - }, - ); - for (offset, value) in z.transpose_vec(H + 1).into_iter().enumerate() { - region.assign_advice(|| format!("z[{offset}]"), config.z, offset, || value)?; - } - - Ok(()) - }, - ) - } -} - -fn test_mock_prover, const W: usize, const H: usize>( - k: u32, - circuit: MyCircuit, - expected: Result<(), Vec<(metadata::Constraint, FailureLocation)>>, -) { - let prover = MockProver::run(k, &circuit, vec![]).unwrap(); - match (prover.verify(), expected) { - (Ok(_), Ok(_)) => {} - (Err(err), Err(expected)) => { - assert_eq!( - err.into_iter() - .map(|failure| match failure { - VerifyFailure::ConstraintNotSatisfied { - constraint, - location, - .. - } => (constraint, location), - _ => panic!("MockProver::verify has result unmatching expected"), - }) - .collect::>(), - expected - ) - } - (_, _) => panic!("MockProver::verify has result unmatching expected"), - }; -} - -fn test_prover( - k: u32, - circuit: MyCircuit, - expected: bool, -) where - E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, - E::G2Affine: SerdeCurveAffine, - E::Fr: FromUniformBytes<64> + WithSmallOrderMulGroup<3>, -{ - let params = ParamsKZG::::new(k); - let vk = keygen_vk(¶ms, &circuit).unwrap(); - let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); - - let proof = { - let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - - create_proof::, ProverGWC, _, _, _, _>( - ¶ms, - &pk, - &[circuit], - &[&[]], - OsRng, - &mut transcript, - ) - .expect("proof generation should not fail"); - - transcript.finalize() - }; - - let accepted = { - let strategy = AccumulatorStrategy::new(¶ms); - let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - - verify_proof::, VerifierGWC, _, _, _>( - ¶ms, - pk.get_vk(), - strategy, - &[&[]], - &mut transcript, - ) - .map(|strategy| { - as VerificationStrategy< - KZGCommitmentScheme, - VerifierGWC, - >>::finalize(strategy) - }) - .unwrap_or_default() - }; - - assert_eq!(accepted, expected); -} - -fn main() { - const W: usize = 4; - const H: usize = 32; - const K: u32 = 8; - - let circuit = &MyCircuit::<_, W, H>::rand(&mut OsRng); - - { - test_mock_prover(K, circuit.clone(), Ok(())); - test_prover::(K, circuit.clone(), true); - } - - #[cfg(not(feature = "sanity-checks"))] - { - use std::ops::IndexMut; - - let mut circuit = circuit.clone(); - circuit.shuffled = circuit.shuffled.map(|mut shuffled| { - shuffled.index_mut(0).swap(0, 1); - shuffled - }); - - test_mock_prover( - K, - circuit.clone(), - Err(vec![( - ((1, "z should end with 1").into(), 0, "").into(), - FailureLocation::InRegion { - region: (0, "Shuffle original into shuffled").into(), - offset: 32, - }, - )]), - ); - test_prover::(K, circuit, false); - } -} +fn main() {} + +// use ff::{BatchInvert, FromUniformBytes, WithSmallOrderMulGroup}; +// use halo2_proofs::arithmetic::CurveExt; +// use halo2_proofs::helpers::SerdeCurveAffine; +// use halo2_proofs::poly::kzg::params::{KZGCommitmentScheme, ParamsKZG}; +// use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; +// use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; +// use halo2_proofs::{ +// arithmetic::Field, +// circuit::{floor_planner::V1, Layouter, Value}, +// dev::{metadata, FailureLocation, MockProver, VerifyFailure}, +// plonk::*, +// poly::{commitment::ParamsProver, VerificationStrategy}, +// transcript::{ +// Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, +// }, +// }; +// use halo2curves::pairing::MultiMillerLoop; +// use halo2curves::{bn256, pairing::Engine}; +// use rand_core::{OsRng, RngCore}; +// use std::iter; +// +// fn rand_2d_array(rng: &mut R) -> [[F; H]; W] { +// [(); W].map(|_| [(); H].map(|_| F::random(&mut *rng))) +// } +// +// fn shuffled( +// original: [[F; H]; W], +// rng: &mut R, +// ) -> [[F; H]; W] { +// let mut shuffled = original; +// +// for row in (1..H).rev() { +// let rand_row = (rng.next_u32() as usize) % row; +// for column in shuffled.iter_mut() { +// column.swap(row, rand_row); +// } +// } +// +// shuffled +// } +// +// #[derive(Clone)] +// struct MyConfig { +// q_shuffle: Selector, +// q_first: Selector, +// q_last: Selector, +// original: [Column; W], +// shuffled: [Column; W], +// theta: Challenge, +// gamma: Challenge, +// z: Column, +// } +// +// impl MyConfig { +// fn configure(meta: &mut ConstraintSystem) -> Self { +// let [q_shuffle, q_first, q_last] = [(); 3].map(|_| meta.selector()); +// // First phase +// let original = [(); W].map(|_| meta.advice_column_in(FirstPhase)); +// let shuffled = [(); W].map(|_| meta.advice_column_in(FirstPhase)); +// let [theta, gamma] = [(); 2].map(|_| meta.challenge_usable_after(FirstPhase)); +// // Second phase +// let z = meta.advice_column_in(SecondPhase); +// +// meta.create_gate("z should start with 1", |_| { +// let one = Expression::Constant(F::ONE); +// +// vec![q_first.expr() * (one - z.cur())] +// }); +// +// meta.create_gate("z should end with 1", |_| { +// let one = Expression::Constant(F::ONE); +// +// vec![q_last.expr() * (one - z.cur())] +// }); +// +// meta.create_gate("z should have valid transition", |_| { +// let q_shuffle = q_shuffle.expr(); +// let original = original.map(|advice| advice.cur()); +// let shuffled = shuffled.map(|advice| advice.cur()); +// let [theta, gamma] = [theta, gamma].map(|challenge| challenge.expr()); +// +// // Compress +// let original = original +// .iter() +// .cloned() +// .reduce(|acc, a| acc * theta.clone() + a) +// .unwrap(); +// let shuffled = shuffled +// .iter() +// .cloned() +// .reduce(|acc, a| acc * theta.clone() + a) +// .unwrap(); +// +// vec![q_shuffle * (z.cur() * (original + gamma.clone()) - z.next() * (shuffled + gamma))] +// }); +// +// Self { +// q_shuffle, +// q_first, +// q_last, +// original, +// shuffled, +// theta, +// gamma, +// z, +// } +// } +// } +// +// #[derive(Clone, Default)] +// struct MyCircuit { +// original: Value<[[F; H]; W]>, +// shuffled: Value<[[F; H]; W]>, +// } +// +// impl MyCircuit { +// fn rand(rng: &mut R) -> Self { +// let original = rand_2d_array::(rng); +// let shuffled = shuffled(original, rng); +// +// Self { +// original: Value::known(original), +// shuffled: Value::known(shuffled), +// } +// } +// } +// +// impl Circuit for MyCircuit { +// type Config = MyConfig; +// type FloorPlanner = V1; +// #[cfg(feature = "circuit-params")] +// type Params = (); +// +// fn without_witnesses(&self) -> Self { +// Self::default() +// } +// +// fn configure(meta: &mut ConstraintSystem) -> Self::Config { +// MyConfig::configure(meta) +// } +// +// fn synthesize( +// &self, +// config: Self::Config, +// mut layouter: impl Layouter, +// ) -> Result<(), Error> { +// let theta = layouter.get_challenge(config.theta); +// let gamma = layouter.get_challenge(config.gamma); +// +// layouter.assign_region( +// || "Shuffle original into shuffled", +// |mut region| { +// // Keygen +// config.q_first.enable(&mut region, 0)?; +// config.q_last.enable(&mut region, H)?; +// for offset in 0..H { +// config.q_shuffle.enable(&mut region, offset)?; +// } +// +// // First phase +// for (idx, (&column, values)) in config +// .original +// .iter() +// .zip(self.original.transpose_array().iter()) +// .enumerate() +// { +// for (offset, &value) in values.transpose_array().iter().enumerate() { +// region.assign_advice( +// || format!("original[{idx}][{offset}]"), +// column, +// offset, +// || value, +// )?; +// } +// } +// for (idx, (&column, values)) in config +// .shuffled +// .iter() +// .zip(self.shuffled.transpose_array().iter()) +// .enumerate() +// { +// for (offset, &value) in values.transpose_array().iter().enumerate() { +// region.assign_advice( +// || format!("shuffled[{idx}][{offset}]"), +// column, +// offset, +// || value, +// )?; +// } +// } +// +// // Second phase +// let z = self.original.zip(self.shuffled).zip(theta).zip(gamma).map( +// |(((original, shuffled), theta), gamma)| { +// let mut product = vec![F::ZERO; H]; +// for (idx, product) in product.iter_mut().enumerate() { +// let mut compressed = F::ZERO; +// for value in shuffled.iter() { +// compressed *= theta; +// compressed += value[idx]; +// } +// +// *product = compressed + gamma +// } +// +// product.iter_mut().batch_invert(); +// +// for (idx, product) in product.iter_mut().enumerate() { +// let mut compressed = F::ZERO; +// for value in original.iter() { +// compressed *= theta; +// compressed += value[idx]; +// } +// +// *product *= compressed + gamma +// } +// +// #[allow(clippy::let_and_return)] +// let z = iter::once(F::ONE) +// .chain(product) +// .scan(F::ONE, |state, cur| { +// *state *= &cur; +// Some(*state) +// }) +// .collect::>(); +// +// #[cfg(feature = "sanity-checks")] +// assert_eq!(F::ONE, *z.last().unwrap()); +// +// z +// }, +// ); +// for (offset, value) in z.transpose_vec(H + 1).into_iter().enumerate() { +// region.assign_advice(|| format!("z[{offset}]"), config.z, offset, || value)?; +// } +// +// Ok(()) +// }, +// ) +// } +// } +// +// fn test_mock_prover, const W: usize, const H: usize>( +// k: u32, +// circuit: MyCircuit, +// expected: Result<(), Vec<(metadata::Constraint, FailureLocation)>>, +// ) { +// let prover = MockProver::run(k, &circuit, vec![]).unwrap(); +// match (prover.verify(), expected) { +// (Ok(_), Ok(_)) => {} +// (Err(err), Err(expected)) => { +// assert_eq!( +// err.into_iter() +// .map(|failure| match failure { +// VerifyFailure::ConstraintNotSatisfied { +// constraint, +// location, +// .. +// } => (constraint, location), +// _ => panic!("MockProver::verify has result unmatching expected"), +// }) +// .collect::>(), +// expected +// ) +// } +// (_, _) => panic!("MockProver::verify has result unmatching expected"), +// }; +// } +// +// fn test_prover( +// k: u32, +// circuit: MyCircuit, +// expected: bool, +// ) where +// E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, +// E::G1: CurveExt, +// E::G2Affine: SerdeCurveAffine, +// E::Fr: FromUniformBytes<64> + WithSmallOrderMulGroup<3>, +// { +// let params = ParamsKZG::::new(k); +// let vk = keygen_vk(¶ms, &circuit).unwrap(); +// let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); +// +// let proof = { +// let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); +// +// create_proof::, ProverGWC, _, _, _, _>( +// ¶ms, +// &pk, +// &[circuit], +// &[&[]], +// OsRng, +// &mut transcript, +// ) +// .expect("proof generation should not fail"); +// +// transcript.finalize() +// }; +// +// let accepted = { +// let strategy = AccumulatorStrategy::new(¶ms); +// let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); +// +// verify_proof::, VerifierGWC, _, _, _>( +// ¶ms, +// pk.get_vk(), +// strategy, +// &[&[]], +// &mut transcript, +// ) +// .map(|strategy| { +// as VerificationStrategy< +// KZGCommitmentScheme, +// VerifierGWC, +// >>::finalize(strategy) +// }) +// .unwrap_or_default() +// }; +// +// assert_eq!(accepted, expected); +// } +// +// fn main() { +// const W: usize = 4; +// const H: usize = 32; +// const K: u32 = 8; +// +// let circuit = &MyCircuit::<_, W, H>::rand(&mut OsRng); +// +// { +// test_mock_prover(K, circuit.clone(), Ok(())); +// test_prover::(K, circuit.clone(), true); +// } +// +// #[cfg(not(feature = "sanity-checks"))] +// { +// use std::ops::IndexMut; +// +// let mut circuit = circuit.clone(); +// circuit.shuffled = circuit.shuffled.map(|mut shuffled| { +// shuffled.index_mut(0).swap(0, 1); +// shuffled +// }); +// +// test_mock_prover( +// K, +// circuit.clone(), +// Err(vec![( +// ((1, "z should end with 1").into(), 0, "").into(), +// FailureLocation::InRegion { +// region: (0, "Shuffle original into shuffled").into(), +// offset: 32, +// }, +// )]), +// ); +// test_prover::(K, circuit, false); +// } +// } diff --git a/examples/simple-example.rs b/examples/simple-example.rs index 242257a692..a4ca7c3757 100644 --- a/examples/simple-example.rs +++ b/examples/simple-example.rs @@ -1,342 +1,344 @@ -use std::marker::PhantomData; - -use halo2_proofs::{ - arithmetic::Field, - circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector}, - poly::Rotation, -}; - -// ANCHOR: instructions -trait NumericInstructions: Chip { - /// Variable representing a number. - type Num; - - /// Loads a number into the circuit as a private input. - fn load_private(&self, layouter: impl Layouter, a: Value) -> Result; - - /// Loads a number into the circuit as a fixed constant. - fn load_constant(&self, layouter: impl Layouter, constant: F) -> Result; - - /// Returns `c = a * b`. - fn mul( - &self, - layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result; - - /// Exposes a number as a public input to the circuit. - fn expose_public( - &self, - layouter: impl Layouter, - num: Self::Num, - row: usize, - ) -> Result<(), Error>; -} -// ANCHOR_END: instructions - -// ANCHOR: chip -/// The chip that will implement our instructions! Chips store their own -/// config, as well as type markers if necessary. -struct FieldChip { - config: FieldConfig, - _marker: PhantomData, -} -// ANCHOR_END: chip - -// ANCHOR: chip-config -/// Chip state is stored in a config struct. This is generated by the chip -/// during configuration, and then stored inside the chip. -#[derive(Clone, Debug)] -struct FieldConfig { - /// For this chip, we will use two advice columns to implement our instructions. - /// These are also the columns through which we communicate with other parts of - /// the circuit. - advice: [Column; 2], - - /// This is the public input (instance) column. - instance: Column, - - // We need a selector to enable the multiplication gate, so that we aren't placing - // any constraints on cells where `NumericInstructions::mul` is not being used. - // This is important when building larger circuits, where columns are used by - // multiple sets of instructions. - s_mul: Selector, -} - -impl FieldChip { - fn construct(config: >::Config) -> Self { - Self { - config, - _marker: PhantomData, - } - } - - fn configure( - meta: &mut ConstraintSystem, - advice: [Column; 2], - instance: Column, - constant: Column, - ) -> >::Config { - meta.enable_equality(instance); - meta.enable_constant(constant); - for column in &advice { - meta.enable_equality(*column); - } - let s_mul = meta.selector(); - - // Define our multiplication gate! - meta.create_gate("mul", |meta| { - // To implement multiplication, we need three advice cells and a selector - // cell. We arrange them like so: - // - // | a0 | a1 | s_mul | - // |-----|-----|-------| - // | lhs | rhs | s_mul | - // | out | | | - // - // Gates may refer to any relative offsets we want, but each distinct - // offset adds a cost to the proof. The most common offsets are 0 (the - // current row), 1 (the next row), and -1 (the previous row), for which - // `Rotation` has specific constructors. - let lhs = meta.query_advice(advice[0], Rotation::cur()); - let rhs = meta.query_advice(advice[1], Rotation::cur()); - let out = meta.query_advice(advice[0], Rotation::next()); - let s_mul = meta.query_selector(s_mul); - - // Finally, we return the polynomial expressions that constrain this gate. - // For our multiplication gate, we only need a single polynomial constraint. - // - // The polynomial expressions returned from `create_gate` will be - // constrained by the proving system to equal zero. Our expression - // has the following properties: - // - When s_mul = 0, any value is allowed in lhs, rhs, and out. - // - When s_mul != 0, this constrains lhs * rhs = out. - vec![s_mul * (lhs * rhs - out)] - }); - - FieldConfig { - advice, - instance, - s_mul, - } - } -} -// ANCHOR_END: chip-config - -// ANCHOR: chip-impl -impl Chip for FieldChip { - type Config = FieldConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} -// ANCHOR_END: chip-impl - -// ANCHOR: instructions-impl -/// A variable representing a number. -#[derive(Clone)] -struct Number(AssignedCell); - -impl NumericInstructions for FieldChip { - type Num = Number; - - fn load_private( - &self, - mut layouter: impl Layouter, - value: Value, - ) -> Result { - let config = self.config(); - - layouter.assign_region( - || "load private", - |mut region| { - region - .assign_advice(|| "private input", config.advice[0], 0, || value) - .map(Number) - }, - ) - } - - fn load_constant( - &self, - mut layouter: impl Layouter, - constant: F, - ) -> Result { - let config = self.config(); - - layouter.assign_region( - || "load constant", - |mut region| { - region - .assign_advice_from_constant(|| "constant value", config.advice[0], 0, constant) - .map(Number) - }, - ) - } - - fn mul( - &self, - mut layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result { - let config = self.config(); - - layouter.assign_region( - || "mul", - |mut region: Region<'_, F>| { - // We only want to use a single multiplication gate in this region, - // so we enable it at region offset 0; this means it will constrain - // cells at offsets 0 and 1. - config.s_mul.enable(&mut region, 0)?; - - // The inputs we've been given could be located anywhere in the circuit, - // but we can only rely on relative offsets inside this region. So we - // assign new cells inside the region and constrain them to have the - // same values as the inputs. - a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; - b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; - - // Now we can assign the multiplication result, which is to be assigned - // into the output position. - let value = a.0.value().copied() * b.0.value(); - - // Finally, we do the assignment to the output, returning a - // variable to be used in another part of the circuit. - region - .assign_advice(|| "lhs * rhs", config.advice[0], 1, || value) - .map(Number) - }, - ) - } - - fn expose_public( - &self, - mut layouter: impl Layouter, - num: Self::Num, - row: usize, - ) -> Result<(), Error> { - let config = self.config(); - - layouter.constrain_instance(num.0.cell(), config.instance, row) - } -} -// ANCHOR_END: instructions-impl - -// ANCHOR: circuit -/// The full circuit implementation. -/// -/// In this struct we store the private input variables. We use `Option` because -/// they won't have any value during key generation. During proving, if any of these -/// were `None` we would get an error. -#[derive(Default)] -struct MyCircuit { - constant: F, - a: Value, - b: Value, -} - -impl Circuit for MyCircuit { - // Since we are using a single chip for everything, we can just reuse its config. - type Config = FieldConfig; - type FloorPlanner = SimpleFloorPlanner; - #[cfg(feature = "circuit-params")] - type Params = (); - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - // We create the two advice columns that FieldChip uses for I/O. - let advice = [meta.advice_column(), meta.advice_column()]; - - // We also need an instance column to store public inputs. - let instance = meta.instance_column(); - - // Create a fixed column to load constants. - let constant = meta.fixed_column(); - - FieldChip::configure(meta, advice, instance, constant) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let field_chip = FieldChip::::construct(config); - - // Load our private values into the circuit. - let a = field_chip.load_private(layouter.namespace(|| "load a"), self.a)?; - let b = field_chip.load_private(layouter.namespace(|| "load b"), self.b)?; - - // Load the constant factor into the circuit. - let constant = - field_chip.load_constant(layouter.namespace(|| "load constant"), self.constant)?; - - // We only have access to plain multiplication. - // We could implement our circuit as: - // asq = a*a - // bsq = b*b - // absq = asq*bsq - // c = constant*asq*bsq - // - // but it's more efficient to implement it as: - // ab = a*b - // absq = ab^2 - // c = constant*absq - let ab = field_chip.mul(layouter.namespace(|| "a * b"), a, b)?; - let absq = field_chip.mul(layouter.namespace(|| "ab * ab"), ab.clone(), ab)?; - let c = field_chip.mul(layouter.namespace(|| "constant * absq"), constant, absq)?; - - // Expose the result as a public input to the circuit. - field_chip.expose_public(layouter.namespace(|| "expose c"), c, 0) - } -} -// ANCHOR_END: circuit - -fn main() { - use halo2_proofs::dev::MockProver; - use halo2curves::pasta::Fp; - - // ANCHOR: test-circuit - // The number of rows in our circuit cannot exceed 2^k. Since our example - // circuit is very small, we can pick a very small value here. - let k = 4; - - // Prepare the private and public inputs to the circuit! - let constant = Fp::from(7); - let a = Fp::from(2); - let b = Fp::from(3); - let c = constant * a.square() * b.square(); - - // Instantiate the circuit with the private inputs. - let circuit = MyCircuit { - constant, - a: Value::known(a), - b: Value::known(b), - }; - - // Arrange the public input. We expose the multiplication result in row 0 - // of the instance column, so we position it there in our public inputs. - let mut public_inputs = vec![c]; - - // Given the correct public input, our circuit will verify. - let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); - assert_eq!(prover.verify(), Ok(())); - - // If we try some other public input, the proof will fail! - public_inputs[0] += Fp::one(); - let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); - assert!(prover.verify().is_err()); - // ANCHOR_END: test-circuit -} +fn main() {} + +// use std::marker::PhantomData; +// +// use halo2_proofs::{ +// arithmetic::Field, +// circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, +// plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector}, +// poly::Rotation, +// }; +// +// // ANCHOR: instructions +// trait NumericInstructions: Chip { +// /// Variable representing a number. +// type Num; +// +// /// Loads a number into the circuit as a private input. +// fn load_private(&self, layouter: impl Layouter, a: Value) -> Result; +// +// /// Loads a number into the circuit as a fixed constant. +// fn load_constant(&self, layouter: impl Layouter, constant: F) -> Result; +// +// /// Returns `c = a * b`. +// fn mul( +// &self, +// layouter: impl Layouter, +// a: Self::Num, +// b: Self::Num, +// ) -> Result; +// +// /// Exposes a number as a public input to the circuit. +// fn expose_public( +// &self, +// layouter: impl Layouter, +// num: Self::Num, +// row: usize, +// ) -> Result<(), Error>; +// } +// // ANCHOR_END: instructions +// +// // ANCHOR: chip +// /// The chip that will implement our instructions! Chips store their own +// /// config, as well as type markers if necessary. +// struct FieldChip { +// config: FieldConfig, +// _marker: PhantomData, +// } +// // ANCHOR_END: chip +// +// // ANCHOR: chip-config +// /// Chip state is stored in a config struct. This is generated by the chip +// /// during configuration, and then stored inside the chip. +// #[derive(Clone, Debug)] +// struct FieldConfig { +// /// For this chip, we will use two advice columns to implement our instructions. +// /// These are also the columns through which we communicate with other parts of +// /// the circuit. +// advice: [Column; 2], +// +// /// This is the public input (instance) column. +// instance: Column, +// +// // We need a selector to enable the multiplication gate, so that we aren't placing +// // any constraints on cells where `NumericInstructions::mul` is not being used. +// // This is important when building larger circuits, where columns are used by +// // multiple sets of instructions. +// s_mul: Selector, +// } +// +// impl FieldChip { +// fn construct(config: >::Config) -> Self { +// Self { +// config, +// _marker: PhantomData, +// } +// } +// +// fn configure( +// meta: &mut ConstraintSystem, +// advice: [Column; 2], +// instance: Column, +// constant: Column, +// ) -> >::Config { +// meta.enable_equality(instance); +// meta.enable_constant(constant); +// for column in &advice { +// meta.enable_equality(*column); +// } +// let s_mul = meta.selector(); +// +// // Define our multiplication gate! +// meta.create_gate("mul", |meta| { +// // To implement multiplication, we need three advice cells and a selector +// // cell. We arrange them like so: +// // +// // | a0 | a1 | s_mul | +// // |-----|-----|-------| +// // | lhs | rhs | s_mul | +// // | out | | | +// // +// // Gates may refer to any relative offsets we want, but each distinct +// // offset adds a cost to the proof. The most common offsets are 0 (the +// // current row), 1 (the next row), and -1 (the previous row), for which +// // `Rotation` has specific constructors. +// let lhs = meta.query_advice(advice[0], Rotation::cur()); +// let rhs = meta.query_advice(advice[1], Rotation::cur()); +// let out = meta.query_advice(advice[0], Rotation::next()); +// let s_mul = meta.query_selector(s_mul); +// +// // Finally, we return the polynomial expressions that constrain this gate. +// // For our multiplication gate, we only need a single polynomial constraint. +// // +// // The polynomial expressions returned from `create_gate` will be +// // constrained by the proving system to equal zero. Our expression +// // has the following properties: +// // - When s_mul = 0, any value is allowed in lhs, rhs, and out. +// // - When s_mul != 0, this constrains lhs * rhs = out. +// vec![s_mul * (lhs * rhs - out)] +// }); +// +// FieldConfig { +// advice, +// instance, +// s_mul, +// } +// } +// } +// // ANCHOR_END: chip-config +// +// // ANCHOR: chip-impl +// impl Chip for FieldChip { +// type Config = FieldConfig; +// type Loaded = (); +// +// fn config(&self) -> &Self::Config { +// &self.config +// } +// +// fn loaded(&self) -> &Self::Loaded { +// &() +// } +// } +// // ANCHOR_END: chip-impl +// +// // ANCHOR: instructions-impl +// /// A variable representing a number. +// #[derive(Clone)] +// struct Number(AssignedCell); +// +// impl NumericInstructions for FieldChip { +// type Num = Number; +// +// fn load_private( +// &self, +// mut layouter: impl Layouter, +// value: Value, +// ) -> Result { +// let config = self.config(); +// +// layouter.assign_region( +// || "load private", +// |mut region| { +// region +// .assign_advice(|| "private input", config.advice[0], 0, || value) +// .map(Number) +// }, +// ) +// } +// +// fn load_constant( +// &self, +// mut layouter: impl Layouter, +// constant: F, +// ) -> Result { +// let config = self.config(); +// +// layouter.assign_region( +// || "load constant", +// |mut region| { +// region +// .assign_advice_from_constant(|| "constant value", config.advice[0], 0, constant) +// .map(Number) +// }, +// ) +// } +// +// fn mul( +// &self, +// mut layouter: impl Layouter, +// a: Self::Num, +// b: Self::Num, +// ) -> Result { +// let config = self.config(); +// +// layouter.assign_region( +// || "mul", +// |mut region: Region<'_, F>| { +// // We only want to use a single multiplication gate in this region, +// // so we enable it at region offset 0; this means it will constrain +// // cells at offsets 0 and 1. +// config.s_mul.enable(&mut region, 0)?; +// +// // The inputs we've been given could be located anywhere in the circuit, +// // but we can only rely on relative offsets inside this region. So we +// // assign new cells inside the region and constrain them to have the +// // same values as the inputs. +// a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; +// b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; +// +// // Now we can assign the multiplication result, which is to be assigned +// // into the output position. +// let value = a.0.value().copied() * b.0.value(); +// +// // Finally, we do the assignment to the output, returning a +// // variable to be used in another part of the circuit. +// region +// .assign_advice(|| "lhs * rhs", config.advice[0], 1, || value) +// .map(Number) +// }, +// ) +// } +// +// fn expose_public( +// &self, +// mut layouter: impl Layouter, +// num: Self::Num, +// row: usize, +// ) -> Result<(), Error> { +// let config = self.config(); +// +// layouter.constrain_instance(num.0.cell(), config.instance, row) +// } +// } +// // ANCHOR_END: instructions-impl +// +// // ANCHOR: circuit +// /// The full circuit implementation. +// /// +// /// In this struct we store the private input variables. We use `Option` because +// /// they won't have any value during key generation. During proving, if any of these +// /// were `None` we would get an error. +// #[derive(Default)] +// struct MyCircuit { +// constant: F, +// a: Value, +// b: Value, +// } +// +// impl Circuit for MyCircuit { +// // Since we are using a single chip for everything, we can just reuse its config. +// type Config = FieldConfig; +// type FloorPlanner = SimpleFloorPlanner; +// #[cfg(feature = "circuit-params")] +// type Params = (); +// +// fn without_witnesses(&self) -> Self { +// Self::default() +// } +// +// fn configure(meta: &mut ConstraintSystem) -> Self::Config { +// // We create the two advice columns that FieldChip uses for I/O. +// let advice = [meta.advice_column(), meta.advice_column()]; +// +// // We also need an instance column to store public inputs. +// let instance = meta.instance_column(); +// +// // Create a fixed column to load constants. +// let constant = meta.fixed_column(); +// +// FieldChip::configure(meta, advice, instance, constant) +// } +// +// fn synthesize( +// &self, +// config: Self::Config, +// mut layouter: impl Layouter, +// ) -> Result<(), Error> { +// let field_chip = FieldChip::::construct(config); +// +// // Load our private values into the circuit. +// let a = field_chip.load_private(layouter.namespace(|| "load a"), self.a)?; +// let b = field_chip.load_private(layouter.namespace(|| "load b"), self.b)?; +// +// // Load the constant factor into the circuit. +// let constant = +// field_chip.load_constant(layouter.namespace(|| "load constant"), self.constant)?; +// +// // We only have access to plain multiplication. +// // We could implement our circuit as: +// // asq = a*a +// // bsq = b*b +// // absq = asq*bsq +// // c = constant*asq*bsq +// // +// // but it's more efficient to implement it as: +// // ab = a*b +// // absq = ab^2 +// // c = constant*absq +// let ab = field_chip.mul(layouter.namespace(|| "a * b"), a, b)?; +// let absq = field_chip.mul(layouter.namespace(|| "ab * ab"), ab.clone(), ab)?; +// let c = field_chip.mul(layouter.namespace(|| "constant * absq"), constant, absq)?; +// +// // Expose the result as a public input to the circuit. +// field_chip.expose_public(layouter.namespace(|| "expose c"), c, 0) +// } +// } +// // ANCHOR_END: circuit +// +// fn main() { +// use halo2_proofs::dev::MockProver; +// use halo2curves::pasta::Fp; +// +// // ANCHOR: test-circuit +// // The number of rows in our circuit cannot exceed 2^k. Since our example +// // circuit is very small, we can pick a very small value here. +// let k = 4; +// +// // Prepare the private and public inputs to the circuit! +// let constant = Fp::from(7); +// let a = Fp::from(2); +// let b = Fp::from(3); +// let c = constant * a.square() * b.square(); +// +// // Instantiate the circuit with the private inputs. +// let circuit = MyCircuit { +// constant, +// a: Value::known(a), +// b: Value::known(b), +// }; +// +// // Arrange the public input. We expose the multiplication result in row 0 +// // of the instance column, so we position it there in our public inputs. +// let mut public_inputs = vec![c]; +// +// // Given the correct public input, our circuit will verify. +// let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); +// assert_eq!(prover.verify(), Ok(())); +// +// // If we try some other public input, the proof will fail! +// public_inputs[0] += Fp::one(); +// let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); +// assert!(prover.verify().is_err()); +// // ANCHOR_END: test-circuit +// } diff --git a/examples/two-chip.rs b/examples/two-chip.rs index 336f9c4957..cc320d8e20 100644 --- a/examples/two-chip.rs +++ b/examples/two-chip.rs @@ -1,537 +1,539 @@ -use std::marker::PhantomData; - -use halo2_proofs::{ - arithmetic::Field, - circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, - poly::Rotation, -}; - -// ANCHOR: field-instructions -/// A variable representing a number. -#[derive(Clone)] -struct Number(AssignedCell); - -trait FieldInstructions: AddInstructions + MulInstructions { - /// Variable representing a number. - type Num; - - /// Loads a number into the circuit as a private input. - fn load_private( - &self, - layouter: impl Layouter, - a: Value, - ) -> Result<>::Num, Error>; - - /// Returns `d = (a + b) * c`. - fn add_and_mul( - &self, - layouter: &mut impl Layouter, - a: >::Num, - b: >::Num, - c: >::Num, - ) -> Result<>::Num, Error>; - - /// Exposes a number as a public input to the circuit. - fn expose_public( - &self, - layouter: impl Layouter, - num: >::Num, - row: usize, - ) -> Result<(), Error>; -} -// ANCHOR_END: field-instructions - -// ANCHOR: add-instructions -trait AddInstructions: Chip { - /// Variable representing a number. - type Num; - - /// Returns `c = a + b`. - fn add( - &self, - layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result; -} -// ANCHOR_END: add-instructions - -// ANCHOR: mul-instructions -trait MulInstructions: Chip { - /// Variable representing a number. - type Num; - - /// Returns `c = a * b`. - fn mul( - &self, - layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result; -} -// ANCHOR_END: mul-instructions - -// ANCHOR: field-config -// The top-level config that provides all necessary columns and permutations -// for the other configs. -#[derive(Clone, Debug)] -struct FieldConfig { - /// For this chip, we will use two advice columns to implement our instructions. - /// These are also the columns through which we communicate with other parts of - /// the circuit. - advice: [Column; 2], - - /// Public inputs - instance: Column, - - add_config: AddConfig, - mul_config: MulConfig, -} -// ANCHOR END: field-config - -// ANCHOR: add-config -#[derive(Clone, Debug)] -struct AddConfig { - advice: [Column; 2], - s_add: Selector, -} -// ANCHOR_END: add-config - -// ANCHOR: mul-config -#[derive(Clone, Debug)] -struct MulConfig { - advice: [Column; 2], - s_mul: Selector, -} -// ANCHOR END: mul-config - -// ANCHOR: field-chip -/// The top-level chip that will implement the `FieldInstructions`. -struct FieldChip { - config: FieldConfig, - _marker: PhantomData, -} -// ANCHOR_END: field-chip - -// ANCHOR: add-chip -struct AddChip { - config: AddConfig, - _marker: PhantomData, -} -// ANCHOR END: add-chip - -// ANCHOR: mul-chip -struct MulChip { - config: MulConfig, - _marker: PhantomData, -} -// ANCHOR_END: mul-chip - -// ANCHOR: add-chip-trait-impl -impl Chip for AddChip { - type Config = AddConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} -// ANCHOR END: add-chip-trait-impl - -// ANCHOR: add-chip-impl -impl AddChip { - fn construct(config: >::Config, _loaded: >::Loaded) -> Self { - Self { - config, - _marker: PhantomData, - } - } - - fn configure( - meta: &mut ConstraintSystem, - advice: [Column; 2], - ) -> >::Config { - let s_add = meta.selector(); - - // Define our addition gate! - meta.create_gate("add", |meta| { - let lhs = meta.query_advice(advice[0], Rotation::cur()); - let rhs = meta.query_advice(advice[1], Rotation::cur()); - let out = meta.query_advice(advice[0], Rotation::next()); - let s_add = meta.query_selector(s_add); - - vec![s_add * (lhs + rhs - out)] - }); - - AddConfig { advice, s_add } - } -} -// ANCHOR END: add-chip-impl - -// ANCHOR: add-instructions-impl -impl AddInstructions for FieldChip { - type Num = Number; - fn add( - &self, - layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result { - let config = self.config().add_config.clone(); - - let add_chip = AddChip::::construct(config, ()); - add_chip.add(layouter, a, b) - } -} - -impl AddInstructions for AddChip { - type Num = Number; - - fn add( - &self, - mut layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result { - let config = self.config(); - - layouter.assign_region( - || "add", - |mut region: Region<'_, F>| { - // We only want to use a single addition gate in this region, - // so we enable it at region offset 0; this means it will constrain - // cells at offsets 0 and 1. - config.s_add.enable(&mut region, 0)?; - - // The inputs we've been given could be located anywhere in the circuit, - // but we can only rely on relative offsets inside this region. So we - // assign new cells inside the region and constrain them to have the - // same values as the inputs. - a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; - b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; - - // Now we can compute the addition result, which is to be assigned - // into the output position. - let value = a.0.value().copied() + b.0.value(); - - // Finally, we do the assignment to the output, returning a - // variable to be used in another part of the circuit. - region - .assign_advice(|| "lhs + rhs", config.advice[0], 1, || value) - .map(Number) - }, - ) - } -} -// ANCHOR END: add-instructions-impl - -// ANCHOR: mul-chip-trait-impl -impl Chip for MulChip { - type Config = MulConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} -// ANCHOR END: mul-chip-trait-impl - -// ANCHOR: mul-chip-impl -impl MulChip { - fn construct(config: >::Config, _loaded: >::Loaded) -> Self { - Self { - config, - _marker: PhantomData, - } - } - - fn configure( - meta: &mut ConstraintSystem, - advice: [Column; 2], - ) -> >::Config { - for column in &advice { - meta.enable_equality(*column); - } - let s_mul = meta.selector(); - - // Define our multiplication gate! - meta.create_gate("mul", |meta| { - // To implement multiplication, we need three advice cells and a selector - // cell. We arrange them like so: - // - // | a0 | a1 | s_mul | - // |-----|-----|-------| - // | lhs | rhs | s_mul | - // | out | | | - // - // Gates may refer to any relative offsets we want, but each distinct - // offset adds a cost to the proof. The most common offsets are 0 (the - // current row), 1 (the next row), and -1 (the previous row), for which - // `Rotation` has specific constructors. - let lhs = meta.query_advice(advice[0], Rotation::cur()); - let rhs = meta.query_advice(advice[1], Rotation::cur()); - let out = meta.query_advice(advice[0], Rotation::next()); - let s_mul = meta.query_selector(s_mul); - - // The polynomial expression returned from `create_gate` will be - // constrained by the proving system to equal zero. Our expression - // has the following properties: - // - When s_mul = 0, any value is allowed in lhs, rhs, and out. - // - When s_mul != 0, this constrains lhs * rhs = out. - vec![s_mul * (lhs * rhs - out)] - }); - - MulConfig { advice, s_mul } - } -} -// ANCHOR_END: mul-chip-impl - -// ANCHOR: mul-instructions-impl -impl MulInstructions for FieldChip { - type Num = Number; - fn mul( - &self, - layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result { - let config = self.config().mul_config.clone(); - let mul_chip = MulChip::::construct(config, ()); - mul_chip.mul(layouter, a, b) - } -} - -impl MulInstructions for MulChip { - type Num = Number; - - fn mul( - &self, - mut layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result { - let config = self.config(); - - layouter.assign_region( - || "mul", - |mut region: Region<'_, F>| { - // We only want to use a single multiplication gate in this region, - // so we enable it at region offset 0; this means it will constrain - // cells at offsets 0 and 1. - config.s_mul.enable(&mut region, 0)?; - - // The inputs we've been given could be located anywhere in the circuit, - // but we can only rely on relative offsets inside this region. So we - // assign new cells inside the region and constrain them to have the - // same values as the inputs. - a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; - b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; - - // Now we can compute the multiplication result, which is to be assigned - // into the output position. - let value = a.0.value().copied() * b.0.value(); - - // Finally, we do the assignment to the output, returning a - // variable to be used in another part of the circuit. - region - .assign_advice(|| "lhs * rhs", config.advice[0], 1, || value) - .map(Number) - }, - ) - } -} -// ANCHOR END: mul-instructions-impl - -// ANCHOR: field-chip-trait-impl -impl Chip for FieldChip { - type Config = FieldConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} -// ANCHOR_END: field-chip-trait-impl - -// ANCHOR: field-chip-impl -impl FieldChip { - fn construct(config: >::Config, _loaded: >::Loaded) -> Self { - Self { - config, - _marker: PhantomData, - } - } - - fn configure( - meta: &mut ConstraintSystem, - advice: [Column; 2], - instance: Column, - ) -> >::Config { - let add_config = AddChip::configure(meta, advice); - let mul_config = MulChip::configure(meta, advice); - - meta.enable_equality(instance); - - FieldConfig { - advice, - instance, - add_config, - mul_config, - } - } -} -// ANCHOR_END: field-chip-impl - -// ANCHOR: field-instructions-impl -impl FieldInstructions for FieldChip { - type Num = Number; - - fn load_private( - &self, - mut layouter: impl Layouter, - value: Value, - ) -> Result<>::Num, Error> { - let config = self.config(); - - layouter.assign_region( - || "load private", - |mut region| { - region - .assign_advice(|| "private input", config.advice[0], 0, || value) - .map(Number) - }, - ) - } - - /// Returns `d = (a + b) * c`. - fn add_and_mul( - &self, - layouter: &mut impl Layouter, - a: >::Num, - b: >::Num, - c: >::Num, - ) -> Result<>::Num, Error> { - let ab = self.add(layouter.namespace(|| "a + b"), a, b)?; - self.mul(layouter.namespace(|| "(a + b) * c"), ab, c) - } - - fn expose_public( - &self, - mut layouter: impl Layouter, - num: >::Num, - row: usize, - ) -> Result<(), Error> { - let config = self.config(); - - layouter.constrain_instance(num.0.cell(), config.instance, row) - } -} -// ANCHOR_END: field-instructions-impl - -// ANCHOR: circuit -/// The full circuit implementation. -/// -/// In this struct we store the private input variables. We use `Value` because -/// they won't have any value during key generation. During proving, if any of these -/// were `Value::unknown()` we would get an error. -#[derive(Default)] -struct MyCircuit { - a: Value, - b: Value, - c: Value, -} - -impl Circuit for MyCircuit { - // Since we are using a single chip for everything, we can just reuse its config. - type Config = FieldConfig; - type FloorPlanner = SimpleFloorPlanner; - #[cfg(feature = "circuit-params")] - type Params = (); - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - // We create the two advice columns that FieldChip uses for I/O. - let advice = [meta.advice_column(), meta.advice_column()]; - - // We also need an instance column to store public inputs. - let instance = meta.instance_column(); - - FieldChip::configure(meta, advice, instance) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let field_chip = FieldChip::::construct(config, ()); - - // Load our private values into the circuit. - let a = field_chip.load_private(layouter.namespace(|| "load a"), self.a)?; - let b = field_chip.load_private(layouter.namespace(|| "load b"), self.b)?; - let c = field_chip.load_private(layouter.namespace(|| "load c"), self.c)?; - - // Use `add_and_mul` to get `d = (a + b) * c`. - let d = field_chip.add_and_mul(&mut layouter, a, b, c)?; - - // Expose the result as a public input to the circuit. - field_chip.expose_public(layouter.namespace(|| "expose d"), d, 0) - } -} -// ANCHOR_END: circuit - -#[allow(clippy::many_single_char_names)] -fn main() { - use halo2_proofs::dev::MockProver; - use halo2curves::pasta::Fp; - use rand_core::OsRng; - - // ANCHOR: test-circuit - // The number of rows in our circuit cannot exceed 2^k. Since our example - // circuit is very small, we can pick a very small value here. - let k = 4; - - // Prepare the private and public inputs to the circuit! - let rng = OsRng; - let a = Fp::random(rng); - let b = Fp::random(rng); - let c = Fp::random(rng); - let d = (a + b) * c; - - // Instantiate the circuit with the private inputs. - let circuit = MyCircuit { - a: Value::known(a), - b: Value::known(b), - c: Value::known(c), - }; - - // Arrange the public input. We expose the multiplication result in row 0 - // of the instance column, so we position it there in our public inputs. - let mut public_inputs = vec![d]; - - // Given the correct public input, our circuit will verify. - let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); - assert_eq!(prover.verify(), Ok(())); - - // If we try some other public input, the proof will fail! - public_inputs[0] += Fp::one(); - let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); - assert!(prover.verify().is_err()); - // ANCHOR_END: test-circuit -} +fn main() {} + +// use std::marker::PhantomData; +// +// use halo2_proofs::{ +// arithmetic::Field, +// circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, +// plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, +// poly::Rotation, +// }; +// +// // ANCHOR: field-instructions +// /// A variable representing a number. +// #[derive(Clone)] +// struct Number(AssignedCell); +// +// trait FieldInstructions: AddInstructions + MulInstructions { +// /// Variable representing a number. +// type Num; +// +// /// Loads a number into the circuit as a private input. +// fn load_private( +// &self, +// layouter: impl Layouter, +// a: Value, +// ) -> Result<>::Num, Error>; +// +// /// Returns `d = (a + b) * c`. +// fn add_and_mul( +// &self, +// layouter: &mut impl Layouter, +// a: >::Num, +// b: >::Num, +// c: >::Num, +// ) -> Result<>::Num, Error>; +// +// /// Exposes a number as a public input to the circuit. +// fn expose_public( +// &self, +// layouter: impl Layouter, +// num: >::Num, +// row: usize, +// ) -> Result<(), Error>; +// } +// // ANCHOR_END: field-instructions +// +// // ANCHOR: add-instructions +// trait AddInstructions: Chip { +// /// Variable representing a number. +// type Num; +// +// /// Returns `c = a + b`. +// fn add( +// &self, +// layouter: impl Layouter, +// a: Self::Num, +// b: Self::Num, +// ) -> Result; +// } +// // ANCHOR_END: add-instructions +// +// // ANCHOR: mul-instructions +// trait MulInstructions: Chip { +// /// Variable representing a number. +// type Num; +// +// /// Returns `c = a * b`. +// fn mul( +// &self, +// layouter: impl Layouter, +// a: Self::Num, +// b: Self::Num, +// ) -> Result; +// } +// // ANCHOR_END: mul-instructions +// +// // ANCHOR: field-config +// // The top-level config that provides all necessary columns and permutations +// // for the other configs. +// #[derive(Clone, Debug)] +// struct FieldConfig { +// /// For this chip, we will use two advice columns to implement our instructions. +// /// These are also the columns through which we communicate with other parts of +// /// the circuit. +// advice: [Column; 2], +// +// /// Public inputs +// instance: Column, +// +// add_config: AddConfig, +// mul_config: MulConfig, +// } +// // ANCHOR END: field-config +// +// // ANCHOR: add-config +// #[derive(Clone, Debug)] +// struct AddConfig { +// advice: [Column; 2], +// s_add: Selector, +// } +// // ANCHOR_END: add-config +// +// // ANCHOR: mul-config +// #[derive(Clone, Debug)] +// struct MulConfig { +// advice: [Column; 2], +// s_mul: Selector, +// } +// // ANCHOR END: mul-config +// +// // ANCHOR: field-chip +// /// The top-level chip that will implement the `FieldInstructions`. +// struct FieldChip { +// config: FieldConfig, +// _marker: PhantomData, +// } +// // ANCHOR_END: field-chip +// +// // ANCHOR: add-chip +// struct AddChip { +// config: AddConfig, +// _marker: PhantomData, +// } +// // ANCHOR END: add-chip +// +// // ANCHOR: mul-chip +// struct MulChip { +// config: MulConfig, +// _marker: PhantomData, +// } +// // ANCHOR_END: mul-chip +// +// // ANCHOR: add-chip-trait-impl +// impl Chip for AddChip { +// type Config = AddConfig; +// type Loaded = (); +// +// fn config(&self) -> &Self::Config { +// &self.config +// } +// +// fn loaded(&self) -> &Self::Loaded { +// &() +// } +// } +// // ANCHOR END: add-chip-trait-impl +// +// // ANCHOR: add-chip-impl +// impl AddChip { +// fn construct(config: >::Config, _loaded: >::Loaded) -> Self { +// Self { +// config, +// _marker: PhantomData, +// } +// } +// +// fn configure( +// meta: &mut ConstraintSystem, +// advice: [Column; 2], +// ) -> >::Config { +// let s_add = meta.selector(); +// +// // Define our addition gate! +// meta.create_gate("add", |meta| { +// let lhs = meta.query_advice(advice[0], Rotation::cur()); +// let rhs = meta.query_advice(advice[1], Rotation::cur()); +// let out = meta.query_advice(advice[0], Rotation::next()); +// let s_add = meta.query_selector(s_add); +// +// vec![s_add * (lhs + rhs - out)] +// }); +// +// AddConfig { advice, s_add } +// } +// } +// // ANCHOR END: add-chip-impl +// +// // ANCHOR: add-instructions-impl +// impl AddInstructions for FieldChip { +// type Num = Number; +// fn add( +// &self, +// layouter: impl Layouter, +// a: Self::Num, +// b: Self::Num, +// ) -> Result { +// let config = self.config().add_config.clone(); +// +// let add_chip = AddChip::::construct(config, ()); +// add_chip.add(layouter, a, b) +// } +// } +// +// impl AddInstructions for AddChip { +// type Num = Number; +// +// fn add( +// &self, +// mut layouter: impl Layouter, +// a: Self::Num, +// b: Self::Num, +// ) -> Result { +// let config = self.config(); +// +// layouter.assign_region( +// || "add", +// |mut region: Region<'_, F>| { +// // We only want to use a single addition gate in this region, +// // so we enable it at region offset 0; this means it will constrain +// // cells at offsets 0 and 1. +// config.s_add.enable(&mut region, 0)?; +// +// // The inputs we've been given could be located anywhere in the circuit, +// // but we can only rely on relative offsets inside this region. So we +// // assign new cells inside the region and constrain them to have the +// // same values as the inputs. +// a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; +// b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; +// +// // Now we can compute the addition result, which is to be assigned +// // into the output position. +// let value = a.0.value().copied() + b.0.value(); +// +// // Finally, we do the assignment to the output, returning a +// // variable to be used in another part of the circuit. +// region +// .assign_advice(|| "lhs + rhs", config.advice[0], 1, || value) +// .map(Number) +// }, +// ) +// } +// } +// // ANCHOR END: add-instructions-impl +// +// // ANCHOR: mul-chip-trait-impl +// impl Chip for MulChip { +// type Config = MulConfig; +// type Loaded = (); +// +// fn config(&self) -> &Self::Config { +// &self.config +// } +// +// fn loaded(&self) -> &Self::Loaded { +// &() +// } +// } +// // ANCHOR END: mul-chip-trait-impl +// +// // ANCHOR: mul-chip-impl +// impl MulChip { +// fn construct(config: >::Config, _loaded: >::Loaded) -> Self { +// Self { +// config, +// _marker: PhantomData, +// } +// } +// +// fn configure( +// meta: &mut ConstraintSystem, +// advice: [Column; 2], +// ) -> >::Config { +// for column in &advice { +// meta.enable_equality(*column); +// } +// let s_mul = meta.selector(); +// +// // Define our multiplication gate! +// meta.create_gate("mul", |meta| { +// // To implement multiplication, we need three advice cells and a selector +// // cell. We arrange them like so: +// // +// // | a0 | a1 | s_mul | +// // |-----|-----|-------| +// // | lhs | rhs | s_mul | +// // | out | | | +// // +// // Gates may refer to any relative offsets we want, but each distinct +// // offset adds a cost to the proof. The most common offsets are 0 (the +// // current row), 1 (the next row), and -1 (the previous row), for which +// // `Rotation` has specific constructors. +// let lhs = meta.query_advice(advice[0], Rotation::cur()); +// let rhs = meta.query_advice(advice[1], Rotation::cur()); +// let out = meta.query_advice(advice[0], Rotation::next()); +// let s_mul = meta.query_selector(s_mul); +// +// // The polynomial expression returned from `create_gate` will be +// // constrained by the proving system to equal zero. Our expression +// // has the following properties: +// // - When s_mul = 0, any value is allowed in lhs, rhs, and out. +// // - When s_mul != 0, this constrains lhs * rhs = out. +// vec![s_mul * (lhs * rhs - out)] +// }); +// +// MulConfig { advice, s_mul } +// } +// } +// // ANCHOR_END: mul-chip-impl +// +// // ANCHOR: mul-instructions-impl +// impl MulInstructions for FieldChip { +// type Num = Number; +// fn mul( +// &self, +// layouter: impl Layouter, +// a: Self::Num, +// b: Self::Num, +// ) -> Result { +// let config = self.config().mul_config.clone(); +// let mul_chip = MulChip::::construct(config, ()); +// mul_chip.mul(layouter, a, b) +// } +// } +// +// impl MulInstructions for MulChip { +// type Num = Number; +// +// fn mul( +// &self, +// mut layouter: impl Layouter, +// a: Self::Num, +// b: Self::Num, +// ) -> Result { +// let config = self.config(); +// +// layouter.assign_region( +// || "mul", +// |mut region: Region<'_, F>| { +// // We only want to use a single multiplication gate in this region, +// // so we enable it at region offset 0; this means it will constrain +// // cells at offsets 0 and 1. +// config.s_mul.enable(&mut region, 0)?; +// +// // The inputs we've been given could be located anywhere in the circuit, +// // but we can only rely on relative offsets inside this region. So we +// // assign new cells inside the region and constrain them to have the +// // same values as the inputs. +// a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; +// b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; +// +// // Now we can compute the multiplication result, which is to be assigned +// // into the output position. +// let value = a.0.value().copied() * b.0.value(); +// +// // Finally, we do the assignment to the output, returning a +// // variable to be used in another part of the circuit. +// region +// .assign_advice(|| "lhs * rhs", config.advice[0], 1, || value) +// .map(Number) +// }, +// ) +// } +// } +// // ANCHOR END: mul-instructions-impl +// +// // ANCHOR: field-chip-trait-impl +// impl Chip for FieldChip { +// type Config = FieldConfig; +// type Loaded = (); +// +// fn config(&self) -> &Self::Config { +// &self.config +// } +// +// fn loaded(&self) -> &Self::Loaded { +// &() +// } +// } +// // ANCHOR_END: field-chip-trait-impl +// +// // ANCHOR: field-chip-impl +// impl FieldChip { +// fn construct(config: >::Config, _loaded: >::Loaded) -> Self { +// Self { +// config, +// _marker: PhantomData, +// } +// } +// +// fn configure( +// meta: &mut ConstraintSystem, +// advice: [Column; 2], +// instance: Column, +// ) -> >::Config { +// let add_config = AddChip::configure(meta, advice); +// let mul_config = MulChip::configure(meta, advice); +// +// meta.enable_equality(instance); +// +// FieldConfig { +// advice, +// instance, +// add_config, +// mul_config, +// } +// } +// } +// // ANCHOR_END: field-chip-impl +// +// // ANCHOR: field-instructions-impl +// impl FieldInstructions for FieldChip { +// type Num = Number; +// +// fn load_private( +// &self, +// mut layouter: impl Layouter, +// value: Value, +// ) -> Result<>::Num, Error> { +// let config = self.config(); +// +// layouter.assign_region( +// || "load private", +// |mut region| { +// region +// .assign_advice(|| "private input", config.advice[0], 0, || value) +// .map(Number) +// }, +// ) +// } +// +// /// Returns `d = (a + b) * c`. +// fn add_and_mul( +// &self, +// layouter: &mut impl Layouter, +// a: >::Num, +// b: >::Num, +// c: >::Num, +// ) -> Result<>::Num, Error> { +// let ab = self.add(layouter.namespace(|| "a + b"), a, b)?; +// self.mul(layouter.namespace(|| "(a + b) * c"), ab, c) +// } +// +// fn expose_public( +// &self, +// mut layouter: impl Layouter, +// num: >::Num, +// row: usize, +// ) -> Result<(), Error> { +// let config = self.config(); +// +// layouter.constrain_instance(num.0.cell(), config.instance, row) +// } +// } +// // ANCHOR_END: field-instructions-impl +// +// // ANCHOR: circuit +// /// The full circuit implementation. +// /// +// /// In this struct we store the private input variables. We use `Value` because +// /// they won't have any value during key generation. During proving, if any of these +// /// were `Value::unknown()` we would get an error. +// #[derive(Default)] +// struct MyCircuit { +// a: Value, +// b: Value, +// c: Value, +// } +// +// impl Circuit for MyCircuit { +// // Since we are using a single chip for everything, we can just reuse its config. +// type Config = FieldConfig; +// type FloorPlanner = SimpleFloorPlanner; +// #[cfg(feature = "circuit-params")] +// type Params = (); +// +// fn without_witnesses(&self) -> Self { +// Self::default() +// } +// +// fn configure(meta: &mut ConstraintSystem) -> Self::Config { +// // We create the two advice columns that FieldChip uses for I/O. +// let advice = [meta.advice_column(), meta.advice_column()]; +// +// // We also need an instance column to store public inputs. +// let instance = meta.instance_column(); +// +// FieldChip::configure(meta, advice, instance) +// } +// +// fn synthesize( +// &self, +// config: Self::Config, +// mut layouter: impl Layouter, +// ) -> Result<(), Error> { +// let field_chip = FieldChip::::construct(config, ()); +// +// // Load our private values into the circuit. +// let a = field_chip.load_private(layouter.namespace(|| "load a"), self.a)?; +// let b = field_chip.load_private(layouter.namespace(|| "load b"), self.b)?; +// let c = field_chip.load_private(layouter.namespace(|| "load c"), self.c)?; +// +// // Use `add_and_mul` to get `d = (a + b) * c`. +// let d = field_chip.add_and_mul(&mut layouter, a, b, c)?; +// +// // Expose the result as a public input to the circuit. +// field_chip.expose_public(layouter.namespace(|| "expose d"), d, 0) +// } +// } +// // ANCHOR_END: circuit +// +// #[allow(clippy::many_single_char_names)] +// fn main() { +// use halo2_proofs::dev::MockProver; +// use halo2curves::pasta::Fp; +// use rand_core::OsRng; +// +// // ANCHOR: test-circuit +// // The number of rows in our circuit cannot exceed 2^k. Since our example +// // circuit is very small, we can pick a very small value here. +// let k = 4; +// +// // Prepare the private and public inputs to the circuit! +// let rng = OsRng; +// let a = Fp::random(rng); +// let b = Fp::random(rng); +// let c = Fp::random(rng); +// let d = (a + b) * c; +// +// // Instantiate the circuit with the private inputs. +// let circuit = MyCircuit { +// a: Value::known(a), +// b: Value::known(b), +// c: Value::known(c), +// }; +// +// // Arrange the public input. We expose the multiplication result in row 0 +// // of the instance column, so we position it there in our public inputs. +// let mut public_inputs = vec![d]; +// +// // Given the correct public input, our circuit will verify. +// let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); +// assert_eq!(prover.verify(), Ok(())); +// +// // If we try some other public input, the proof will fail! +// public_inputs[0] += Fp::one(); +// let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); +// assert!(prover.verify().is_err()); +// // ANCHOR_END: test-circuit +// } diff --git a/examples/vector-mul.rs b/examples/vector-mul.rs index 01728fdf36..e35927cc54 100644 --- a/examples/vector-mul.rs +++ b/examples/vector-mul.rs @@ -1,316 +1,317 @@ -use std::marker::PhantomData; - -use halo2_proofs::{ - arithmetic::Field, - circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, - poly::Rotation, -}; - -// ANCHOR: instructions -trait NumericInstructions: Chip { - /// Variable representing a number. - type Num; - - /// Loads a number into the circuit as a private input. - fn load_private( - &self, - layouter: impl Layouter, - a: &[Value], - ) -> Result, Error>; - - /// Returns `c = a * b`. The caller is responsible for ensuring that `a.len() == b.len()`. - fn mul( - &self, - layouter: impl Layouter, - a: &[Self::Num], - b: &[Self::Num], - ) -> Result, Error>; - - /// Exposes a number as a public input to the circuit. - fn expose_public( - &self, - layouter: impl Layouter, - num: &Self::Num, - row: usize, - ) -> Result<(), Error>; -} -// ANCHOR_END: instructions - -// ANCHOR: chip -/// The chip that will implement our instructions! Chips store their own -/// config, as well as type markers if necessary. -struct FieldChip { - config: FieldConfig, - _marker: PhantomData, -} -// ANCHOR_END: chip - -// ANCHOR: chip-config -/// Chip state is stored in a config struct. This is generated by the chip -/// during configuration, and then stored inside the chip. -#[derive(Clone, Debug)] -struct FieldConfig { - /// For this chip, we will use two advice columns to implement our instructions. - /// These are also the columns through which we communicate with other parts of - /// the circuit. - advice: [Column; 3], - - /// This is the public input (instance) column. - instance: Column, - - // We need a selector to enable the multiplication gate, so that we aren't placing - // any constraints on cells where `NumericInstructions::mul` is not being used. - // This is important when building larger circuits, where columns are used by - // multiple sets of instructions. - s_mul: Selector, -} - -impl FieldChip { - fn construct(config: >::Config) -> Self { - Self { - config, - _marker: PhantomData, - } - } - - fn configure( - meta: &mut ConstraintSystem, - advice: [Column; 3], - instance: Column, - ) -> >::Config { - meta.enable_equality(instance); - for column in &advice { - meta.enable_equality(*column); - } - let s_mul = meta.selector(); - - // Define our multiplication gate! - meta.create_gate("mul", |meta| { - // To implement multiplication, we need three advice cells and a selector - // cell. We arrange them like so: - // - // | a0 | a1 | a2 | s_mul | - // |-----|-----|-----|-------| - // | lhs | rhs | out | s_mul | - // - // Gates may refer to any relative offsets we want, but each distinct - // offset adds a cost to the proof. The most common offsets are 0 (the - // current row), 1 (the next row), and -1 (the previous row), for which - // `Rotation` has specific constructors. - let lhs = meta.query_advice(advice[0], Rotation::cur()); - let rhs = meta.query_advice(advice[1], Rotation::cur()); - let out = meta.query_advice(advice[2], Rotation::cur()); - let s_mul = meta.query_selector(s_mul); - - // Finally, we return the polynomial expressions that constrain this gate. - // For our multiplication gate, we only need a single polynomial constraint. - // - // The polynomial expressions returned from `create_gate` will be - // constrained by the proving system to equal zero. Our expression - // has the following properties: - // - When s_mul = 0, any value is allowed in lhs, rhs, and out. - // - When s_mul != 0, this constrains lhs * rhs = out. - vec![s_mul * (lhs * rhs - out)] - }); - - FieldConfig { - advice, - instance, - s_mul, - } - } -} -// ANCHOR_END: chip-config - -// ANCHOR: chip-impl -impl Chip for FieldChip { - type Config = FieldConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} -// ANCHOR_END: chip-impl - -// ANCHOR: instructions-impl -/// A variable representing a number. -#[derive(Clone, Debug)] -struct Number(AssignedCell); - -impl NumericInstructions for FieldChip { - type Num = Number; - - fn load_private( - &self, - mut layouter: impl Layouter, - values: &[Value], - ) -> Result, Error> { - let config = self.config(); - - layouter.assign_region( - || "load private", - |mut region| { - values - .iter() - .enumerate() - .map(|(i, value)| { - region - .assign_advice(|| "private input", config.advice[0], i, || *value) - .map(Number) - }) - .collect() - }, - ) - } - - fn mul( - &self, - mut layouter: impl Layouter, - a: &[Self::Num], - b: &[Self::Num], - ) -> Result, Error> { - let config = self.config(); - assert_eq!(a.len(), b.len()); - - layouter.assign_region( - || "mul", - |mut region: Region<'_, F>| { - a.iter() - .zip(b.iter()) - .enumerate() - .map(|(i, (a, b))| { - config.s_mul.enable(&mut region, i)?; - - a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?; - b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?; - - let value = a.0.value().copied() * b.0.value(); - - // Finally, we do the assignment to the output, returning a - // variable to be used in another part of the circuit. - region - .assign_advice(|| "lhs * rhs", config.advice[2], i, || value) - .map(Number) - }) - .collect() - }, - ) - } - - fn expose_public( - &self, - mut layouter: impl Layouter, - num: &Self::Num, - row: usize, - ) -> Result<(), Error> { - let config = self.config(); - - layouter.constrain_instance(num.0.cell(), config.instance, row) - } -} -// ANCHOR_END: instructions-impl - -// ANCHOR: circuit -/// The full circuit implementation. -/// -/// In this struct we store the private input variables. We use `Option` because -/// they won't have any value during key generation. During proving, if any of these -/// were `None` we would get an error. -#[derive(Default)] -struct MyCircuit { - a: Vec>, - b: Vec>, -} - -impl Circuit for MyCircuit { - // Since we are using a single chip for everything, we can just reuse its config. - type Config = FieldConfig; - type FloorPlanner = SimpleFloorPlanner; - #[cfg(feature = "circuit-params")] - type Params = (); - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - // We create the three advice columns that FieldChip uses for I/O. - let advice = [ - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - ]; - - // We also need an instance column to store public inputs. - let instance = meta.instance_column(); - - FieldChip::configure(meta, advice, instance) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let field_chip = FieldChip::::construct(config); - - // Load our private values into the circuit. - let a = field_chip.load_private(layouter.namespace(|| "load a"), &self.a)?; - let b = field_chip.load_private(layouter.namespace(|| "load b"), &self.b)?; - - let ab = field_chip.mul(layouter.namespace(|| "a * b"), &a, &b)?; - - for (i, c) in ab.iter().enumerate() { - // Expose the result as a public input to the circuit. - field_chip.expose_public(layouter.namespace(|| "expose c"), c, i)?; - } - Ok(()) - } -} -// ANCHOR_END: circuit - -fn main() { - use halo2_proofs::dev::MockProver; - use halo2curves::pasta::Fp; - - const N: usize = 20000; - // ANCHOR: test-circuit - // The number of rows in our circuit cannot exceed 2^k. Since our example - // circuit is very small, we can pick a very small value here. - let k = 16; - - // Prepare the private and public inputs to the circuit! - let a = [Fp::from(2); N]; - let b = [Fp::from(3); N]; - let c: Vec = a.iter().zip(b).map(|(&a, b)| a * b).collect(); - - // Instantiate the circuit with the private inputs. - let circuit = MyCircuit { - a: a.iter().map(|&x| Value::known(x)).collect(), - b: b.iter().map(|&x| Value::known(x)).collect(), - }; - - // Arrange the public input. We expose the multiplication result in row 0 - // of the instance column, so we position it there in our public inputs. - let mut public_inputs = c; - - let start = std::time::Instant::now(); - // Given the correct public input, our circuit will verify. - let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); - assert_eq!(prover.verify(), Ok(())); - println!("positive test took {:?}", start.elapsed()); - - // If we try some other public input, the proof will fail! - let start = std::time::Instant::now(); - public_inputs[0] += Fp::one(); - let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); - assert!(prover.verify().is_err()); - println!("negative test took {:?}", start.elapsed()); - // ANCHOR_END: test-circuit -} +fn main() {} +// use std::marker::PhantomData; +// +// use halo2_proofs::{ +// arithmetic::Field, +// circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, +// plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, +// poly::Rotation, +// }; +// +// // ANCHOR: instructions +// trait NumericInstructions: Chip { +// /// Variable representing a number. +// type Num; +// +// /// Loads a number into the circuit as a private input. +// fn load_private( +// &self, +// layouter: impl Layouter, +// a: &[Value], +// ) -> Result, Error>; +// +// /// Returns `c = a * b`. The caller is responsible for ensuring that `a.len() == b.len()`. +// fn mul( +// &self, +// layouter: impl Layouter, +// a: &[Self::Num], +// b: &[Self::Num], +// ) -> Result, Error>; +// +// /// Exposes a number as a public input to the circuit. +// fn expose_public( +// &self, +// layouter: impl Layouter, +// num: &Self::Num, +// row: usize, +// ) -> Result<(), Error>; +// } +// // ANCHOR_END: instructions +// +// // ANCHOR: chip +// /// The chip that will implement our instructions! Chips store their own +// /// config, as well as type markers if necessary. +// struct FieldChip { +// config: FieldConfig, +// _marker: PhantomData, +// } +// // ANCHOR_END: chip +// +// // ANCHOR: chip-config +// /// Chip state is stored in a config struct. This is generated by the chip +// /// during configuration, and then stored inside the chip. +// #[derive(Clone, Debug)] +// struct FieldConfig { +// /// For this chip, we will use two advice columns to implement our instructions. +// /// These are also the columns through which we communicate with other parts of +// /// the circuit. +// advice: [Column; 3], +// +// /// This is the public input (instance) column. +// instance: Column, +// +// // We need a selector to enable the multiplication gate, so that we aren't placing +// // any constraints on cells where `NumericInstructions::mul` is not being used. +// // This is important when building larger circuits, where columns are used by +// // multiple sets of instructions. +// s_mul: Selector, +// } +// +// impl FieldChip { +// fn construct(config: >::Config) -> Self { +// Self { +// config, +// _marker: PhantomData, +// } +// } +// +// fn configure( +// meta: &mut ConstraintSystem, +// advice: [Column; 3], +// instance: Column, +// ) -> >::Config { +// meta.enable_equality(instance); +// for column in &advice { +// meta.enable_equality(*column); +// } +// let s_mul = meta.selector(); +// +// // Define our multiplication gate! +// meta.create_gate("mul", |meta| { +// // To implement multiplication, we need three advice cells and a selector +// // cell. We arrange them like so: +// // +// // | a0 | a1 | a2 | s_mul | +// // |-----|-----|-----|-------| +// // | lhs | rhs | out | s_mul | +// // +// // Gates may refer to any relative offsets we want, but each distinct +// // offset adds a cost to the proof. The most common offsets are 0 (the +// // current row), 1 (the next row), and -1 (the previous row), for which +// // `Rotation` has specific constructors. +// let lhs = meta.query_advice(advice[0], Rotation::cur()); +// let rhs = meta.query_advice(advice[1], Rotation::cur()); +// let out = meta.query_advice(advice[2], Rotation::cur()); +// let s_mul = meta.query_selector(s_mul); +// +// // Finally, we return the polynomial expressions that constrain this gate. +// // For our multiplication gate, we only need a single polynomial constraint. +// // +// // The polynomial expressions returned from `create_gate` will be +// // constrained by the proving system to equal zero. Our expression +// // has the following properties: +// // - When s_mul = 0, any value is allowed in lhs, rhs, and out. +// // - When s_mul != 0, this constrains lhs * rhs = out. +// vec![s_mul * (lhs * rhs - out)] +// }); +// +// FieldConfig { +// advice, +// instance, +// s_mul, +// } +// } +// } +// // ANCHOR_END: chip-config +// +// // ANCHOR: chip-impl +// impl Chip for FieldChip { +// type Config = FieldConfig; +// type Loaded = (); +// +// fn config(&self) -> &Self::Config { +// &self.config +// } +// +// fn loaded(&self) -> &Self::Loaded { +// &() +// } +// } +// // ANCHOR_END: chip-impl +// +// // ANCHOR: instructions-impl +// /// A variable representing a number. +// #[derive(Clone, Debug)] +// struct Number(AssignedCell); +// +// impl NumericInstructions for FieldChip { +// type Num = Number; +// +// fn load_private( +// &self, +// mut layouter: impl Layouter, +// values: &[Value], +// ) -> Result, Error> { +// let config = self.config(); +// +// layouter.assign_region( +// || "load private", +// |mut region| { +// values +// .iter() +// .enumerate() +// .map(|(i, value)| { +// region +// .assign_advice(|| "private input", config.advice[0], i, || *value) +// .map(Number) +// }) +// .collect() +// }, +// ) +// } +// +// fn mul( +// &self, +// mut layouter: impl Layouter, +// a: &[Self::Num], +// b: &[Self::Num], +// ) -> Result, Error> { +// let config = self.config(); +// assert_eq!(a.len(), b.len()); +// +// layouter.assign_region( +// || "mul", +// |mut region: Region<'_, F>| { +// a.iter() +// .zip(b.iter()) +// .enumerate() +// .map(|(i, (a, b))| { +// config.s_mul.enable(&mut region, i)?; +// +// a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?; +// b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?; +// +// let value = a.0.value().copied() * b.0.value(); +// +// // Finally, we do the assignment to the output, returning a +// // variable to be used in another part of the circuit. +// region +// .assign_advice(|| "lhs * rhs", config.advice[2], i, || value) +// .map(Number) +// }) +// .collect() +// }, +// ) +// } +// +// fn expose_public( +// &self, +// mut layouter: impl Layouter, +// num: &Self::Num, +// row: usize, +// ) -> Result<(), Error> { +// let config = self.config(); +// +// layouter.constrain_instance(num.0.cell(), config.instance, row) +// } +// } +// // ANCHOR_END: instructions-impl +// +// // ANCHOR: circuit +// /// The full circuit implementation. +// /// +// /// In this struct we store the private input variables. We use `Option` because +// /// they won't have any value during key generation. During proving, if any of these +// /// were `None` we would get an error. +// #[derive(Default)] +// struct MyCircuit { +// a: Vec>, +// b: Vec>, +// } +// +// impl Circuit for MyCircuit { +// // Since we are using a single chip for everything, we can just reuse its config. +// type Config = FieldConfig; +// type FloorPlanner = SimpleFloorPlanner; +// #[cfg(feature = "circuit-params")] +// type Params = (); +// +// fn without_witnesses(&self) -> Self { +// Self::default() +// } +// +// fn configure(meta: &mut ConstraintSystem) -> Self::Config { +// // We create the three advice columns that FieldChip uses for I/O. +// let advice = [ +// meta.advice_column(), +// meta.advice_column(), +// meta.advice_column(), +// ]; +// +// // We also need an instance column to store public inputs. +// let instance = meta.instance_column(); +// +// FieldChip::configure(meta, advice, instance) +// } +// +// fn synthesize( +// &self, +// config: Self::Config, +// mut layouter: impl Layouter, +// ) -> Result<(), Error> { +// let field_chip = FieldChip::::construct(config); +// +// // Load our private values into the circuit. +// let a = field_chip.load_private(layouter.namespace(|| "load a"), &self.a)?; +// let b = field_chip.load_private(layouter.namespace(|| "load b"), &self.b)?; +// +// let ab = field_chip.mul(layouter.namespace(|| "a * b"), &a, &b)?; +// +// for (i, c) in ab.iter().enumerate() { +// // Expose the result as a public input to the circuit. +// field_chip.expose_public(layouter.namespace(|| "expose c"), c, i)?; +// } +// Ok(()) +// } +// } +// // ANCHOR_END: circuit +// +// fn main() { +// use halo2_proofs::dev::MockProver; +// use halo2curves::pasta::Fp; +// +// const N: usize = 20000; +// // ANCHOR: test-circuit +// // The number of rows in our circuit cannot exceed 2^k. Since our example +// // circuit is very small, we can pick a very small value here. +// let k = 16; +// +// // Prepare the private and public inputs to the circuit! +// let a = [Fp::from(2); N]; +// let b = [Fp::from(3); N]; +// let c: Vec = a.iter().zip(b).map(|(&a, b)| a * b).collect(); +// +// // Instantiate the circuit with the private inputs. +// let circuit = MyCircuit { +// a: a.iter().map(|&x| Value::known(x)).collect(), +// b: b.iter().map(|&x| Value::known(x)).collect(), +// }; +// +// // Arrange the public input. We expose the multiplication result in row 0 +// // of the instance column, so we position it there in our public inputs. +// let mut public_inputs = c; +// +// let start = std::time::Instant::now(); +// // Given the correct public input, our circuit will verify. +// let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); +// assert_eq!(prover.verify(), Ok(())); +// println!("positive test took {:?}", start.elapsed()); +// +// // If we try some other public input, the proof will fail! +// let start = std::time::Instant::now(); +// public_inputs[0] += Fp::one(); +// let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); +// assert!(prover.verify().is_err()); +// println!("negative test took {:?}", start.elapsed()); +// // ANCHOR_END: test-circuit +// } diff --git a/examples/vector-ops-unblinded.rs b/examples/vector-ops-unblinded.rs index b8373c8b1e..501982560c 100644 --- a/examples/vector-ops-unblinded.rs +++ b/examples/vector-ops-unblinded.rs @@ -1,562 +1,564 @@ -/// Here we construct two circuits one for adding two vectors and one for multiplying and we check that their transcripts have the same inputs -/// by way of the unblinded inputs. -/// This is a simple example of how to use unblinded inputs to match up circuits that might be proved on different host machines. -use std::marker::PhantomData; - -use ff::{FromUniformBytes, WithSmallOrderMulGroup}; -use halo2_proofs::arithmetic::CurveExt; -use halo2_proofs::helpers::SerdeCurveAffine; -use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; -use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; -use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; -use halo2_proofs::{ - arithmetic::Field, - circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, - plonk::*, - poly::{commitment::ParamsProver, Rotation, VerificationStrategy}, - transcript::{ - Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, - }, -}; -use halo2curves::pairing::{Engine, MultiMillerLoop}; -use rand_core::OsRng; - -// ANCHOR: instructions -trait NumericInstructions: Chip { - /// Variable representing a number. - type Num; - - /// Loads a number into the circuit as an unblinded input this can be matched to inputs in other circuits ! - fn load_unblinded( - &self, - layouter: impl Layouter, - a: &[Value], - ) -> Result, Error>; - - /// Returns `c = a * b`. The caller is responsible for ensuring that `a.len() == b.len()`. - fn mul( - &self, - layouter: impl Layouter, - a: &[Self::Num], - b: &[Self::Num], - ) -> Result, Error>; - - /// Returns `c = a + b`. The caller is responsible for ensuring that `a.len() == b.len()`. - fn add( - &self, - layouter: impl Layouter, - a: &[Self::Num], - b: &[Self::Num], - ) -> Result, Error>; - - /// Exposes a number as a public input to the circuit. - fn expose_public( - &self, - layouter: impl Layouter, - num: &Self::Num, - row: usize, - ) -> Result<(), Error>; -} -// ANCHOR_END: instructions - -// ANCHOR: chip -/// The chip that will implement our instructions! Chips store their own -/// config, as well as type markers if necessary. -struct MultChip { - config: FieldConfig, - _marker: PhantomData, -} - -// ANCHOR: chip -/// The chip that will implement our instructions! Chips store their own -/// config, as well as type markers if necessary. -struct AddChip { - config: FieldConfig, - _marker: PhantomData, -} -// ANCHOR_END: chip - -// ANCHOR: chip-config -/// Chip state is stored in a config struct. This is generated by the chip -/// during configuration, and then stored inside the chip. -#[derive(Clone, Debug)] -struct FieldConfig { - advice: [Column; 3], - instance: Column, - s: Selector, -} - -impl MultChip { - fn construct(config: >::Config) -> Self { - Self { - config, - _marker: PhantomData, - } - } - - fn configure( - meta: &mut ConstraintSystem, - advice: [Column; 3], - instance: Column, - ) -> >::Config { - meta.enable_equality(instance); - for column in &advice { - meta.enable_equality(*column); - } - let s = meta.selector(); - - // Define our multiplication gate! - meta.create_gate("mul", |meta| { - let lhs = meta.query_advice(advice[0], Rotation::cur()); - let rhs = meta.query_advice(advice[1], Rotation::cur()); - let out = meta.query_advice(advice[2], Rotation::cur()); - let s_mul = meta.query_selector(s); - - vec![s_mul * (lhs * rhs - out)] - }); - - FieldConfig { - advice, - instance, - s, - } - } -} - -impl AddChip { - fn construct(config: >::Config) -> Self { - Self { - config, - _marker: PhantomData, - } - } - - fn configure( - meta: &mut ConstraintSystem, - advice: [Column; 3], - instance: Column, - ) -> >::Config { - meta.enable_equality(instance); - for column in &advice { - meta.enable_equality(*column); - } - let s = meta.selector(); - - // Define our multiplication gate! - meta.create_gate("add", |meta| { - let lhs = meta.query_advice(advice[0], Rotation::cur()); - let rhs = meta.query_advice(advice[1], Rotation::cur()); - let out = meta.query_advice(advice[2], Rotation::cur()); - let s_add = meta.query_selector(s); - - vec![s_add * (lhs + rhs - out)] - }); - - FieldConfig { - advice, - instance, - s, - } - } -} -// ANCHOR_END: chip-config - -// ANCHOR: chip-impl -impl Chip for MultChip { - type Config = FieldConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} -// ANCHOR_END: chip-impl - -// ANCHOR: chip-impl -impl Chip for AddChip { - type Config = FieldConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} - -// ANCHOR: instructions-impl -/// A variable representing a number. -#[derive(Clone, Debug)] -struct Number(AssignedCell); - -impl NumericInstructions for MultChip { - type Num = Number; - - fn load_unblinded( - &self, - mut layouter: impl Layouter, - values: &[Value], - ) -> Result, Error> { - let config = self.config(); - - layouter.assign_region( - || "load unblinded", - |mut region| { - values - .iter() - .enumerate() - .map(|(i, value)| { - region - .assign_advice(|| "unblinded input", config.advice[0], i, || *value) - .map(Number) - }) - .collect() - }, - ) - } - - fn add( - &self, - _: impl Layouter, - _: &[Self::Num], - _: &[Self::Num], - ) -> Result, Error> { - panic!("Not implemented") - } - - fn mul( - &self, - mut layouter: impl Layouter, - a: &[Self::Num], - b: &[Self::Num], - ) -> Result, Error> { - let config = self.config(); - assert_eq!(a.len(), b.len()); - - layouter.assign_region( - || "mul", - |mut region: Region<'_, F>| { - a.iter() - .zip(b.iter()) - .enumerate() - .map(|(i, (a, b))| { - config.s.enable(&mut region, i)?; - - a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?; - b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?; - - let value = a.0.value().copied() * b.0.value(); - - // Finally, we do the assignment to the output, returning a - // variable to be used in another part of the circuit. - region - .assign_advice(|| "lhs * rhs", config.advice[2], i, || value) - .map(Number) - }) - .collect() - }, - ) - } - - fn expose_public( - &self, - mut layouter: impl Layouter, - num: &Self::Num, - row: usize, - ) -> Result<(), Error> { - let config = self.config(); - - layouter.constrain_instance(num.0.cell(), config.instance, row) - } -} -// ANCHOR_END: instructions-impl - -impl NumericInstructions for AddChip { - type Num = Number; - - fn load_unblinded( - &self, - mut layouter: impl Layouter, - values: &[Value], - ) -> Result, Error> { - let config = self.config(); - - layouter.assign_region( - || "load unblinded", - |mut region| { - values - .iter() - .enumerate() - .map(|(i, value)| { - region - .assign_advice(|| "unblinded input", config.advice[0], i, || *value) - .map(Number) - }) - .collect() - }, - ) - } - - fn mul( - &self, - _: impl Layouter, - _: &[Self::Num], - _: &[Self::Num], - ) -> Result, Error> { - panic!("Not implemented") - } - - fn add( - &self, - mut layouter: impl Layouter, - a: &[Self::Num], - b: &[Self::Num], - ) -> Result, Error> { - let config = self.config(); - assert_eq!(a.len(), b.len()); - - layouter.assign_region( - || "add", - |mut region: Region<'_, F>| { - a.iter() - .zip(b.iter()) - .enumerate() - .map(|(i, (a, b))| { - config.s.enable(&mut region, i)?; - - a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?; - b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?; - - let value = a.0.value().copied() + b.0.value(); - - // Finally, we do the assignment to the output, returning a - // variable to be used in another part of the circuit. - region - .assign_advice(|| "lhs + rhs", config.advice[2], i, || value) - .map(Number) - }) - .collect() - }, - ) - } - - fn expose_public( - &self, - mut layouter: impl Layouter, - num: &Self::Num, - row: usize, - ) -> Result<(), Error> { - let config = self.config(); - - layouter.constrain_instance(num.0.cell(), config.instance, row) - } -} - -#[derive(Default)] -struct MulCircuit { - a: Vec>, - b: Vec>, -} - -impl Circuit for MulCircuit { - // Since we are using a single chip for everything, we can just reuse its config. - type Config = FieldConfig; - type FloorPlanner = SimpleFloorPlanner; - #[cfg(feature = "circuit-params")] - type Params = (); - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - // We create the three advice columns that FieldChip uses for I/O. - let advice = [ - meta.unblinded_advice_column(), - meta.unblinded_advice_column(), - meta.unblinded_advice_column(), - ]; - - // We also need an instance column to store public inputs. - let instance = meta.instance_column(); - - MultChip::configure(meta, advice, instance) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let field_chip = MultChip::::construct(config); - - // Load our unblinded values into the circuit. - let a = field_chip.load_unblinded(layouter.namespace(|| "load a"), &self.a)?; - let b = field_chip.load_unblinded(layouter.namespace(|| "load b"), &self.b)?; - - let ab = field_chip.mul(layouter.namespace(|| "a * b"), &a, &b)?; - - for (i, c) in ab.iter().enumerate() { - // Expose the result as a public input to the circuit. - field_chip.expose_public(layouter.namespace(|| "expose c"), c, i)?; - } - Ok(()) - } -} -// ANCHOR_END: circuit - -#[derive(Default)] -struct AddCircuit { - a: Vec>, - b: Vec>, -} - -impl Circuit for AddCircuit { - // Since we are using a single chip for everything, we can just reuse its config. - type Config = FieldConfig; - type FloorPlanner = SimpleFloorPlanner; - #[cfg(feature = "circuit-params")] - type Params = (); - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - // We create the three advice columns that FieldChip uses for I/O. - let advice = [ - meta.unblinded_advice_column(), - meta.unblinded_advice_column(), - meta.unblinded_advice_column(), - ]; - - // We also need an instance column to store public inputs. - let instance = meta.instance_column(); - - AddChip::configure(meta, advice, instance) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let field_chip = AddChip::::construct(config); - - // Load our unblinded values into the circuit. - let a = field_chip.load_unblinded(layouter.namespace(|| "load a"), &self.a)?; - let b = field_chip.load_unblinded(layouter.namespace(|| "load b"), &self.b)?; - - let ab = field_chip.add(layouter.namespace(|| "a + b"), &a, &b)?; - - for (i, c) in ab.iter().enumerate() { - // Expose the result as a public input to the circuit. - field_chip.expose_public(layouter.namespace(|| "expose c"), c, i)?; - } - Ok(()) - } -} -// ANCHOR_END: circuit - -fn test_prover( - k: u32, - circuit: impl Circuit, - expected: bool, - instances: Vec, -) -> Vec -where - E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, - E::G2Affine: SerdeCurveAffine, - E::Fr: FromUniformBytes<64> + WithSmallOrderMulGroup<3>, -{ - let params = ParamsKZG::::new(k); - let vk = keygen_vk(¶ms, &circuit).unwrap(); - let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); - - let proof = { - let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - - create_proof::, ProverGWC, _, _, _, _>( - ¶ms, - &pk, - &[circuit], - &[&[&instances]], - OsRng, - &mut transcript, - ) - .expect("proof generation should not fail"); - - transcript.finalize() - }; - - let accepted = { - let strategy = AccumulatorStrategy::new(¶ms); - let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - - verify_proof::, VerifierGWC, _, _, AccumulatorStrategy>( - ¶ms, - pk.get_vk(), - strategy, - &[&[&instances]], - &mut transcript, - ) - .map(|strategy| { - as VerificationStrategy< - KZGCommitmentScheme, - VerifierGWC, - >>::finalize(strategy) - }) - .unwrap_or_default() - }; - - assert_eq!(accepted, expected); - - proof -} - -fn main() { - use halo2curves::bn256::Fr; - const N: usize = 10; - // ANCHOR: test-circuit - // The number of rows in our circuit cannot exceed 2^k. Since our example - // circuit is very small, we can pick a very small value here. - let k = 7; - - // Prepare the inputs to the circuit! - let a = [Fr::from(2); N]; - let b = [Fr::from(3); N]; - let c_mul: Vec = a.iter().zip(b).map(|(&a, b)| a * b).collect(); - let c_add: Vec = a.iter().zip(b).map(|(&a, b)| a + b).collect(); - - // Instantiate the mul circuit with the inputs. - let mul_circuit = MulCircuit { - a: a.iter().map(|&x| Value::known(x)).collect(), - b: b.iter().map(|&x| Value::known(x)).collect(), - }; - - // Instantiate the add circuit with the inputs. - let add_circuit = AddCircuit { - a: a.iter().map(|&x| Value::known(x)).collect(), - b: b.iter().map(|&x| Value::known(x)).collect(), - }; - - // the commitments will be the first columns of the proof transcript so we can compare them easily - let proof_1 = test_prover::(k, mul_circuit, true, c_mul); - // the commitments will be the first columns of the proof transcript so we can compare them easily - let proof_2 = test_prover::(k, add_circuit, true, c_add); - - // the commitments will be the first columns of the proof transcript so we can compare them easily - // here we compare the first 10 bytes of the commitments - println!( - "Commitments are equal: {:?}", - proof_1[..10] == proof_2[..10] - ); - // ANCHOR_END: test-circuit -} +fn main() {} + +// /// Here we construct two circuits one for adding two vectors and one for multiplying and we check that their transcripts have the same inputs +// /// by way of the unblinded inputs. +// /// This is a simple example of how to use unblinded inputs to match up circuits that might be proved on different host machines. +// use std::marker::PhantomData; +// +// use ff::{FromUniformBytes, WithSmallOrderMulGroup}; +// use halo2_proofs::arithmetic::CurveExt; +// use halo2_proofs::helpers::SerdeCurveAffine; +// use halo2_proofs::poly::kzg::params::{KZGCommitmentScheme, ParamsKZG}; +// use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; +// use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; +// use halo2_proofs::{ +// arithmetic::Field, +// circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, +// plonk::*, +// poly::{commitment::ParamsProver, Rotation, VerificationStrategy}, +// transcript::{ +// Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, +// }, +// }; +// use halo2curves::pairing::{Engine, MultiMillerLoop}; +// use rand_core::OsRng; +// +// // ANCHOR: instructions +// trait NumericInstructions: Chip { +// /// Variable representing a number. +// type Num; +// +// /// Loads a number into the circuit as an unblinded input this can be matched to inputs in other circuits ! +// fn load_unblinded( +// &self, +// layouter: impl Layouter, +// a: &[Value], +// ) -> Result, Error>; +// +// /// Returns `c = a * b`. The caller is responsible for ensuring that `a.len() == b.len()`. +// fn mul( +// &self, +// layouter: impl Layouter, +// a: &[Self::Num], +// b: &[Self::Num], +// ) -> Result, Error>; +// +// /// Returns `c = a + b`. The caller is responsible for ensuring that `a.len() == b.len()`. +// fn add( +// &self, +// layouter: impl Layouter, +// a: &[Self::Num], +// b: &[Self::Num], +// ) -> Result, Error>; +// +// /// Exposes a number as a public input to the circuit. +// fn expose_public( +// &self, +// layouter: impl Layouter, +// num: &Self::Num, +// row: usize, +// ) -> Result<(), Error>; +// } +// // ANCHOR_END: instructions +// +// // ANCHOR: chip +// /// The chip that will implement our instructions! Chips store their own +// /// config, as well as type markers if necessary. +// struct MultChip { +// config: FieldConfig, +// _marker: PhantomData, +// } +// +// // ANCHOR: chip +// /// The chip that will implement our instructions! Chips store their own +// /// config, as well as type markers if necessary. +// struct AddChip { +// config: FieldConfig, +// _marker: PhantomData, +// } +// // ANCHOR_END: chip +// +// // ANCHOR: chip-config +// /// Chip state is stored in a config struct. This is generated by the chip +// /// during configuration, and then stored inside the chip. +// #[derive(Clone, Debug)] +// struct FieldConfig { +// advice: [Column; 3], +// instance: Column, +// s: Selector, +// } +// +// impl MultChip { +// fn construct(config: >::Config) -> Self { +// Self { +// config, +// _marker: PhantomData, +// } +// } +// +// fn configure( +// meta: &mut ConstraintSystem, +// advice: [Column; 3], +// instance: Column, +// ) -> >::Config { +// meta.enable_equality(instance); +// for column in &advice { +// meta.enable_equality(*column); +// } +// let s = meta.selector(); +// +// // Define our multiplication gate! +// meta.create_gate("mul", |meta| { +// let lhs = meta.query_advice(advice[0], Rotation::cur()); +// let rhs = meta.query_advice(advice[1], Rotation::cur()); +// let out = meta.query_advice(advice[2], Rotation::cur()); +// let s_mul = meta.query_selector(s); +// +// vec![s_mul * (lhs * rhs - out)] +// }); +// +// FieldConfig { +// advice, +// instance, +// s, +// } +// } +// } +// +// impl AddChip { +// fn construct(config: >::Config) -> Self { +// Self { +// config, +// _marker: PhantomData, +// } +// } +// +// fn configure( +// meta: &mut ConstraintSystem, +// advice: [Column; 3], +// instance: Column, +// ) -> >::Config { +// meta.enable_equality(instance); +// for column in &advice { +// meta.enable_equality(*column); +// } +// let s = meta.selector(); +// +// // Define our multiplication gate! +// meta.create_gate("add", |meta| { +// let lhs = meta.query_advice(advice[0], Rotation::cur()); +// let rhs = meta.query_advice(advice[1], Rotation::cur()); +// let out = meta.query_advice(advice[2], Rotation::cur()); +// let s_add = meta.query_selector(s); +// +// vec![s_add * (lhs + rhs - out)] +// }); +// +// FieldConfig { +// advice, +// instance, +// s, +// } +// } +// } +// // ANCHOR_END: chip-config +// +// // ANCHOR: chip-impl +// impl Chip for MultChip { +// type Config = FieldConfig; +// type Loaded = (); +// +// fn config(&self) -> &Self::Config { +// &self.config +// } +// +// fn loaded(&self) -> &Self::Loaded { +// &() +// } +// } +// // ANCHOR_END: chip-impl +// +// // ANCHOR: chip-impl +// impl Chip for AddChip { +// type Config = FieldConfig; +// type Loaded = (); +// +// fn config(&self) -> &Self::Config { +// &self.config +// } +// +// fn loaded(&self) -> &Self::Loaded { +// &() +// } +// } +// +// // ANCHOR: instructions-impl +// /// A variable representing a number. +// #[derive(Clone, Debug)] +// struct Number(AssignedCell); +// +// impl NumericInstructions for MultChip { +// type Num = Number; +// +// fn load_unblinded( +// &self, +// mut layouter: impl Layouter, +// values: &[Value], +// ) -> Result, Error> { +// let config = self.config(); +// +// layouter.assign_region( +// || "load unblinded", +// |mut region| { +// values +// .iter() +// .enumerate() +// .map(|(i, value)| { +// region +// .assign_advice(|| "unblinded input", config.advice[0], i, || *value) +// .map(Number) +// }) +// .collect() +// }, +// ) +// } +// +// fn add( +// &self, +// _: impl Layouter, +// _: &[Self::Num], +// _: &[Self::Num], +// ) -> Result, Error> { +// panic!("Not implemented") +// } +// +// fn mul( +// &self, +// mut layouter: impl Layouter, +// a: &[Self::Num], +// b: &[Self::Num], +// ) -> Result, Error> { +// let config = self.config(); +// assert_eq!(a.len(), b.len()); +// +// layouter.assign_region( +// || "mul", +// |mut region: Region<'_, F>| { +// a.iter() +// .zip(b.iter()) +// .enumerate() +// .map(|(i, (a, b))| { +// config.s.enable(&mut region, i)?; +// +// a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?; +// b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?; +// +// let value = a.0.value().copied() * b.0.value(); +// +// // Finally, we do the assignment to the output, returning a +// // variable to be used in another part of the circuit. +// region +// .assign_advice(|| "lhs * rhs", config.advice[2], i, || value) +// .map(Number) +// }) +// .collect() +// }, +// ) +// } +// +// fn expose_public( +// &self, +// mut layouter: impl Layouter, +// num: &Self::Num, +// row: usize, +// ) -> Result<(), Error> { +// let config = self.config(); +// +// layouter.constrain_instance(num.0.cell(), config.instance, row) +// } +// } +// // ANCHOR_END: instructions-impl +// +// impl NumericInstructions for AddChip { +// type Num = Number; +// +// fn load_unblinded( +// &self, +// mut layouter: impl Layouter, +// values: &[Value], +// ) -> Result, Error> { +// let config = self.config(); +// +// layouter.assign_region( +// || "load unblinded", +// |mut region| { +// values +// .iter() +// .enumerate() +// .map(|(i, value)| { +// region +// .assign_advice(|| "unblinded input", config.advice[0], i, || *value) +// .map(Number) +// }) +// .collect() +// }, +// ) +// } +// +// fn mul( +// &self, +// _: impl Layouter, +// _: &[Self::Num], +// _: &[Self::Num], +// ) -> Result, Error> { +// panic!("Not implemented") +// } +// +// fn add( +// &self, +// mut layouter: impl Layouter, +// a: &[Self::Num], +// b: &[Self::Num], +// ) -> Result, Error> { +// let config = self.config(); +// assert_eq!(a.len(), b.len()); +// +// layouter.assign_region( +// || "add", +// |mut region: Region<'_, F>| { +// a.iter() +// .zip(b.iter()) +// .enumerate() +// .map(|(i, (a, b))| { +// config.s.enable(&mut region, i)?; +// +// a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?; +// b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?; +// +// let value = a.0.value().copied() + b.0.value(); +// +// // Finally, we do the assignment to the output, returning a +// // variable to be used in another part of the circuit. +// region +// .assign_advice(|| "lhs + rhs", config.advice[2], i, || value) +// .map(Number) +// }) +// .collect() +// }, +// ) +// } +// +// fn expose_public( +// &self, +// mut layouter: impl Layouter, +// num: &Self::Num, +// row: usize, +// ) -> Result<(), Error> { +// let config = self.config(); +// +// layouter.constrain_instance(num.0.cell(), config.instance, row) +// } +// } +// +// #[derive(Default)] +// struct MulCircuit { +// a: Vec>, +// b: Vec>, +// } +// +// impl Circuit for MulCircuit { +// // Since we are using a single chip for everything, we can just reuse its config. +// type Config = FieldConfig; +// type FloorPlanner = SimpleFloorPlanner; +// #[cfg(feature = "circuit-params")] +// type Params = (); +// +// fn without_witnesses(&self) -> Self { +// Self::default() +// } +// +// fn configure(meta: &mut ConstraintSystem) -> Self::Config { +// // We create the three advice columns that FieldChip uses for I/O. +// let advice = [ +// meta.unblinded_advice_column(), +// meta.unblinded_advice_column(), +// meta.unblinded_advice_column(), +// ]; +// +// // We also need an instance column to store public inputs. +// let instance = meta.instance_column(); +// +// MultChip::configure(meta, advice, instance) +// } +// +// fn synthesize( +// &self, +// config: Self::Config, +// mut layouter: impl Layouter, +// ) -> Result<(), Error> { +// let field_chip = MultChip::::construct(config); +// +// // Load our unblinded values into the circuit. +// let a = field_chip.load_unblinded(layouter.namespace(|| "load a"), &self.a)?; +// let b = field_chip.load_unblinded(layouter.namespace(|| "load b"), &self.b)?; +// +// let ab = field_chip.mul(layouter.namespace(|| "a * b"), &a, &b)?; +// +// for (i, c) in ab.iter().enumerate() { +// // Expose the result as a public input to the circuit. +// field_chip.expose_public(layouter.namespace(|| "expose c"), c, i)?; +// } +// Ok(()) +// } +// } +// // ANCHOR_END: circuit +// +// #[derive(Default)] +// struct AddCircuit { +// a: Vec>, +// b: Vec>, +// } +// +// impl Circuit for AddCircuit { +// // Since we are using a single chip for everything, we can just reuse its config. +// type Config = FieldConfig; +// type FloorPlanner = SimpleFloorPlanner; +// #[cfg(feature = "circuit-params")] +// type Params = (); +// +// fn without_witnesses(&self) -> Self { +// Self::default() +// } +// +// fn configure(meta: &mut ConstraintSystem) -> Self::Config { +// // We create the three advice columns that FieldChip uses for I/O. +// let advice = [ +// meta.unblinded_advice_column(), +// meta.unblinded_advice_column(), +// meta.unblinded_advice_column(), +// ]; +// +// // We also need an instance column to store public inputs. +// let instance = meta.instance_column(); +// +// AddChip::configure(meta, advice, instance) +// } +// +// fn synthesize( +// &self, +// config: Self::Config, +// mut layouter: impl Layouter, +// ) -> Result<(), Error> { +// let field_chip = AddChip::::construct(config); +// +// // Load our unblinded values into the circuit. +// let a = field_chip.load_unblinded(layouter.namespace(|| "load a"), &self.a)?; +// let b = field_chip.load_unblinded(layouter.namespace(|| "load b"), &self.b)?; +// +// let ab = field_chip.add(layouter.namespace(|| "a + b"), &a, &b)?; +// +// for (i, c) in ab.iter().enumerate() { +// // Expose the result as a public input to the circuit. +// field_chip.expose_public(layouter.namespace(|| "expose c"), c, i)?; +// } +// Ok(()) +// } +// } +// // ANCHOR_END: circuit +// +// fn test_prover( +// k: u32, +// circuit: impl Circuit, +// expected: bool, +// instances: Vec, +// ) -> Vec +// where +// E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, +// E::G1: CurveExt, +// E::G2Affine: SerdeCurveAffine, +// E::Fr: FromUniformBytes<64> + WithSmallOrderMulGroup<3>, +// { +// let params = ParamsKZG::::new(k); +// let vk = keygen_vk(¶ms, &circuit).unwrap(); +// let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); +// +// let proof = { +// let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); +// +// create_proof::, ProverGWC, _, _, _, _>( +// ¶ms, +// &pk, +// &[circuit], +// &[&[&instances]], +// OsRng, +// &mut transcript, +// ) +// .expect("proof generation should not fail"); +// +// transcript.finalize() +// }; +// +// let accepted = { +// let strategy = AccumulatorStrategy::new(¶ms); +// let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); +// +// verify_proof::, VerifierGWC, _, _, AccumulatorStrategy>( +// ¶ms, +// pk.get_vk(), +// strategy, +// &[&[&instances]], +// &mut transcript, +// ) +// .map(|strategy| { +// as VerificationStrategy< +// KZGCommitmentScheme, +// VerifierGWC, +// >>::finalize(strategy) +// }) +// .unwrap_or_default() +// }; +// +// assert_eq!(accepted, expected); +// +// proof +// } +// +// fn main() { +// use halo2curves::bn256::Fr; +// const N: usize = 10; +// // ANCHOR: test-circuit +// // The number of rows in our circuit cannot exceed 2^k. Since our example +// // circuit is very small, we can pick a very small value here. +// let k = 7; +// +// // Prepare the inputs to the circuit! +// let a = [Fr::from(2); N]; +// let b = [Fr::from(3); N]; +// let c_mul: Vec = a.iter().zip(b).map(|(&a, b)| a * b).collect(); +// let c_add: Vec = a.iter().zip(b).map(|(&a, b)| a + b).collect(); +// +// // Instantiate the mul circuit with the inputs. +// let mul_circuit = MulCircuit { +// a: a.iter().map(|&x| Value::known(x)).collect(), +// b: b.iter().map(|&x| Value::known(x)).collect(), +// }; +// +// // Instantiate the add circuit with the inputs. +// let add_circuit = AddCircuit { +// a: a.iter().map(|&x| Value::known(x)).collect(), +// b: b.iter().map(|&x| Value::known(x)).collect(), +// }; +// +// // the commitments will be the first columns of the proof transcript so we can compare them easily +// let proof_1 = test_prover::(k, mul_circuit, true, c_mul); +// // the commitments will be the first columns of the proof transcript so we can compare them easily +// let proof_2 = test_prover::(k, add_circuit, true, c_add); +// +// // the commitments will be the first columns of the proof transcript so we can compare them easily +// // here we compare the first 10 bytes of the commitments +// println!( +// "Commitments are equal: {:?}", +// proof_1[..10] == proof_2[..10] +// ); +// // ANCHOR_END: test-circuit +// } From ea7a14bdc4731bac4f4233f1616aac174adc6020 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Wed, 4 Dec 2024 11:02:01 +0100 Subject: [PATCH 07/20] Working version of new polynomial commitment scheme interface. --- src/arithmetic.rs | 40 +- src/helpers.rs | 11 +- src/lib.rs | 8 +- src/poly.rs | 169 ++++-- src/poly/commitment.rs | 271 ++-------- src/poly/domain.rs | 4 +- src/poly/kzg/gwc.rs | 50 -- src/poly/kzg/gwc/prover.rs | 89 ---- src/poly/kzg/gwc/verifier.rs | 124 ----- src/poly/kzg/mod.rs | 369 ++++++++++++- src/poly/kzg/msm.rs | 38 +- src/poly/kzg/{commitment.rs => params.rs} | 125 ++--- src/poly/kzg/strategy.rs | 181 ------- src/poly/multiopen_test.rs | 184 ------- src/poly/query.rs | 144 +++-- src/poly/strategy.rs | 31 -- src/transcript.rs | 614 +++++----------------- 17 files changed, 803 insertions(+), 1649 deletions(-) delete mode 100644 src/poly/kzg/gwc.rs delete mode 100644 src/poly/kzg/gwc/prover.rs delete mode 100644 src/poly/kzg/gwc/verifier.rs rename src/poly/kzg/{commitment.rs => params.rs} (76%) delete mode 100644 src/poly/kzg/strategy.rs delete mode 100644 src/poly/multiopen_test.rs delete mode 100644 src/poly/strategy.rs diff --git a/src/arithmetic.rs b/src/arithmetic.rs index 9cd65dc4d1..6539e4876e 100644 --- a/src/arithmetic.rs +++ b/src/arithmetic.rs @@ -3,10 +3,12 @@ use super::multicore; pub use ff::Field; +use group::prime::PrimeCurveAffine; use group::{ ff::{BatchInvert, PrimeField}, Curve, Group, GroupOpsOwned, ScalarMulOwned, }; +use std::fmt::Debug; pub use halo2curves::{CurveAffine, CurveExt}; @@ -25,7 +27,7 @@ where { } -fn multiexp_serial(coeffs: &[C::Scalar], bases: &[C], acc: &mut C::Curve) { +fn multiexp_serial(coeffs: &[C::Scalar], bases: &[C], acc: &mut C::Curve) { let coeffs: Vec<_> = coeffs.iter().map(|a| a.to_repr()).collect(); let c = if bases.len() < 4 { @@ -64,17 +66,17 @@ fn multiexp_serial(coeffs: &[C::Scalar], bases: &[C], acc: &mut } #[derive(Clone, Copy)] - enum Bucket { + enum Bucket { None, Affine(C), Projective(C::Curve), } - impl Bucket { + impl Bucket { fn add_assign(&mut self, other: &C) { *self = match *self { Bucket::None => Bucket::Affine(*other), - Bucket::Affine(a) => Bucket::Projective(a + *other), + Bucket::Affine(a) => Bucket::Projective(a.to_curve() + other.to_curve()), Bucket::Projective(mut a) => { a += *other; Bucket::Projective(a) @@ -144,7 +146,7 @@ pub fn small_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::C /// This function will panic if coeffs and bases have a different length. /// /// This will use multithreading if beneficial. -pub fn best_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { +pub fn best_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { assert_eq!(coeffs.len(), bases.len()); let num_threads = multicore::current_num_threads(); @@ -289,7 +291,7 @@ pub fn recursive_butterfly_arithmetic>( } /// Convert coefficient bases group elements to lagrange basis by inverse FFT. -pub fn g_to_lagrange(g_projective: Vec, k: u32) -> Vec { +pub fn g_to_lagrange(g_projective: Vec, k: u32) -> Vec { let n_inv = C::Scalar::TWO_INV.pow_vartime([k as u64, 0, 0, 0]); let mut omega_inv = C::Scalar::ROOT_OF_UNITY_INV; for _ in k..C::Scalar::S { @@ -507,6 +509,32 @@ pub(crate) fn powers(base: F) -> impl Iterator { std::iter::successors(Some(F::ONE), move |power| Some(base * power)) } +/// Multi scalar multiplication engine +pub trait MSM: Clone + Debug + Send + Sync { + /// Add arbitrary term (the scalar and the point) + fn append_term(&mut self, scalar: C::Scalar, point: C::Curve); + + /// Add another multiexp into this one + fn add_msm(&mut self, other: &Self) + where + Self: Sized; + + /// Scale all scalars in the MSM by some scaling factor + fn scale(&mut self, factor: C::Scalar); + + /// Perform multiexp and check that it results in zero + fn check(&self) -> bool; + + /// Perform multiexp and return the result + fn eval(&self) -> C::Curve; + + /// Return base points + fn bases(&self) -> Vec; + + /// Scalars + fn scalars(&self) -> Vec; +} + #[cfg(test)] use rand_core::OsRng; diff --git a/src/helpers.rs b/src/helpers.rs index 92b977692f..a4820b1b27 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -3,7 +3,8 @@ use crate::poly::Polynomial; use ff::PrimeField; -use halo2curves::{serde::SerdeObject, CurveAffine}; +use group::prime::PrimeCurveAffine; +use halo2curves::serde::SerdeObject; use std::io; /// This enum specifies how various types are serialized and deserialized. @@ -23,7 +24,7 @@ pub enum SerdeFormat { } // Keep this trait for compatibility with IPA serialization -pub(crate) trait CurveRead: CurveAffine { +pub(crate) trait CurveRead: PrimeCurveAffine { /// Reads a compressed element from the buffer and attempts to parse it /// using `from_bytes`. fn read(reader: &mut R) -> io::Result { @@ -33,10 +34,10 @@ pub(crate) trait CurveRead: CurveAffine { .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid point encoding in proof")) } } -impl CurveRead for C {} +impl CurveRead for C {} /// Trait for serialising SerdeObjects -pub trait SerdeCurveAffine: CurveAffine + SerdeObject { +pub trait SerdeCurveAffine: PrimeCurveAffine + SerdeObject + Default { /// Reads an element from the buffer and parses it according to the `format`: /// - `Processed`: Reads a compressed curve element and decompress it /// - `RawBytes`: Reads an uncompressed curve element with coordinates in Montgomery form. @@ -68,7 +69,7 @@ pub trait SerdeCurveAffine: CurveAffine + SerdeObject { } } } -impl SerdeCurveAffine for C {} +impl SerdeCurveAffine for C {} /// Trait for implementing field SerdeObjects pub trait SerdePrimeField: PrimeField + SerdeObject { diff --git a/src/lib.rs b/src/lib.rs index 19994ce9be..cc536148cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,13 +9,15 @@ #![deny(unsafe_code)] pub mod arithmetic; -pub mod circuit; +// pub mod circuit; pub use halo2curves; mod multicore; -pub mod plonk; +// pub mod plonk; pub mod poly; pub mod transcript; -pub mod dev; +pub mod rational; + +// pub mod dev; pub mod helpers; pub use helpers::SerdeFormat; diff --git a/src/poly.rs b/src/poly.rs index f7c5c53b6b..8ac2403d47 100644 --- a/src/poly.rs +++ b/src/poly.rs @@ -4,30 +4,32 @@ use crate::arithmetic::parallelize; use crate::helpers::SerdePrimeField; -use crate::plonk::Assigned; +// use crate::plonk::Assigned; use crate::SerdeFormat; -use group::ff::{BatchInvert, Field}; +use group::ff::Field; +use rand_core::RngCore; use std::fmt::Debug; use std::io; use std::marker::PhantomData; -use std::ops::{Add, Deref, DerefMut, Index, IndexMut, Mul, RangeFrom, RangeFull, Sub}; +use std::ops::{ + Add, AddAssign, Deref, DerefMut, Index, IndexMut, Mul, MulAssign, RangeFrom, RangeFull, Sub, +}; -/// Generic commitment scheme structures -pub mod commitment; +// /// Generic commitment scheme structures +// pub mod commitment; mod domain; mod query; -mod strategy; +// mod strategy; /// KZG commitment scheme pub mod kzg; -#[cfg(test)] -mod multiopen_test; +pub mod commitment; pub use domain::*; pub use query::{ProverQuery, VerifierQuery}; -pub use strategy::{Guard, VerificationStrategy}; +// pub use strategy::{Guard, VerificationStrategy}; /// This is an error that could occur during proving or circuit synthesis. // TODO: these errors need to be cleaned up @@ -172,52 +174,52 @@ impl Polynomial { } } -pub(crate) fn batch_invert_assigned( - assigned: Vec, LagrangeCoeff>>, -) -> Vec> { - let mut assigned_denominators: Vec<_> = assigned - .iter() - .map(|f| { - f.iter() - .map(|value| value.denominator()) - .collect::>() - }) - .collect(); - - assigned_denominators - .iter_mut() - .flat_map(|f| { - f.iter_mut() - // If the denominator is trivial, we can skip it, reducing the - // size of the batch inversion. - .filter_map(|d| d.as_mut()) - }) - .batch_invert(); - - assigned - .iter() - .zip(assigned_denominators) - .map(|(poly, inv_denoms)| poly.invert(inv_denoms.into_iter().map(|d| d.unwrap_or(F::ONE)))) - .collect() -} - -impl Polynomial, LagrangeCoeff> { - pub(crate) fn invert( - &self, - inv_denoms: impl Iterator + ExactSizeIterator, - ) -> Polynomial { - assert_eq!(inv_denoms.len(), self.values.len()); - Polynomial { - values: self - .values - .iter() - .zip(inv_denoms) - .map(|(a, inv_den)| a.numerator() * inv_den) - .collect(), - _marker: self._marker, - } - } -} +// pub(crate) fn batch_invert_assigned( +// assigned: Vec, LagrangeCoeff>>, +// ) -> Vec> { +// let mut assigned_denominators: Vec<_> = assigned +// .iter() +// .map(|f| { +// f.iter() +// .map(|value| value.denominator()) +// .collect::>() +// }) +// .collect(); +// +// assigned_denominators +// .iter_mut() +// .flat_map(|f| { +// f.iter_mut() +// // If the denominator is trivial, we can skip it, reducing the +// // size of the batch inversion. +// .filter_map(|d| d.as_mut()) +// }) +// .batch_invert(); +// +// assigned +// .iter() +// .zip(assigned_denominators) +// .map(|(poly, inv_denoms)| poly.invert(inv_denoms.into_iter().map(|d| d.unwrap_or(F::ONE)))) +// .collect() +// } +// +// impl Polynomial, LagrangeCoeff> { +// pub(crate) fn invert( +// &self, +// inv_denoms: impl Iterator + ExactSizeIterator, +// ) -> Polynomial { +// assert_eq!(inv_denoms.len(), self.values.len()); +// Polynomial { +// values: self +// .values +// .iter() +// .zip(inv_denoms) +// .map(|(a, inv_den)| a.numerator() * inv_den) +// .collect(), +// _marker: self._marker, +// } +// } +// } impl<'a, F: Field, B: Basis> Add<&'a Polynomial> for Polynomial { type Output = Polynomial; @@ -319,3 +321,60 @@ impl Rotation { Rotation(1) } } + +/// Wrapper type around a blinding factor. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct Blind(pub F); + +impl Default for Blind { + fn default() -> Self { + Blind(F::ONE) + } +} + +impl Blind { + /// Given `rng` creates new blinding scalar + pub fn new(rng: &mut R) -> Self { + Blind(F::random(rng)) + } +} + +impl Add for Blind { + type Output = Self; + + fn add(self, rhs: Blind) -> Self { + Blind(self.0 + rhs.0) + } +} + +impl Mul for Blind { + type Output = Self; + + fn mul(self, rhs: Blind) -> Self { + Blind(self.0 * rhs.0) + } +} + +impl AddAssign for Blind { + fn add_assign(&mut self, rhs: Blind) { + self.0 += rhs.0; + } +} + +impl MulAssign for Blind { + fn mul_assign(&mut self, rhs: Blind) { + self.0 *= rhs.0; + } +} + +impl AddAssign for Blind { + fn add_assign(&mut self, rhs: F) { + self.0 += rhs; + } +} + +impl MulAssign for Blind { + fn mul_assign(&mut self, rhs: F) { + self.0 *= rhs; + } +} diff --git a/src/poly/commitment.rs b/src/poly/commitment.rs index ebc26fe9c3..9c2810e3ed 100644 --- a/src/poly/commitment.rs +++ b/src/poly/commitment.rs @@ -1,245 +1,40 @@ -use super::{ - query::{ProverQuery, VerifierQuery}, - strategy::Guard, - Coeff, LagrangeCoeff, Polynomial, -}; -use crate::poly::Error; -use crate::transcript::{EncodedChallenge, TranscriptRead, TranscriptWrite}; -use ff::Field; -use halo2curves::CurveAffine; -use rand_core::RngCore; -use std::{ - fmt::Debug, - io::{self}, - ops::{Add, AddAssign, Mul, MulAssign}, -}; - -/// Defines components of a commitment scheme. -pub trait CommitmentScheme { - /// Application field of this commitment scheme - type Scalar: Field; - - /// Elliptic curve used to commit the application and witnesses - type Curve: CurveAffine; - - /// Constant prover parameters - type ParamsProver: for<'params> ParamsProver< - 'params, - Self::Curve, - ParamsVerifier = Self::ParamsVerifier, - >; - - /// Constant verifier parameters - type ParamsVerifier: for<'params> ParamsVerifier<'params, Self::Curve>; - - /// Wrapper for parameter generator - fn new_params(k: u32) -> Self::ParamsProver; - - /// Wrapper for parameter reader - fn read_params(reader: &mut R) -> io::Result; -} - -/// Parameters for circuit sysnthesis and prover parameters. -pub trait Params<'params, C: CurveAffine>: Sized + Clone { - /// Multi scalar multiplication engine - type MSM: MSM + 'params; - - /// Logaritmic size of the circuit - fn k(&self) -> u32; - - /// Size of the circuit - fn n(&self) -> u64; - - /// Downsize `Params` with smaller `k`. - fn downsize(&mut self, k: u32); - - /// Generates an empty multiscalar multiplication struct using the - /// appropriate params. - fn empty_msm(&'params self) -> Self::MSM; - - /// This commits to a polynomial using its evaluations over the $2^k$ size - /// evaluation domain. The commitment will be blinded by the blinding factor - /// `r`. - fn commit_lagrange( +//! Trait for a commitment scheme +use crate::poly::{Blind, Coeff, Error, Polynomial, ProverQuery, VerifierQuery}; +use crate::transcript::{Hashable, Sampleable, Transcript}; +use ff::PrimeField; +use halo2curves::serde::SerdeObject; + +/// Public interface for a Polynomial Commitment Scheme (PCS) +pub trait PolynomialCommitmentScheme: Clone { + /// Parameters needed to generate a proof in the PCS + type Parameters; + /// Parameters needed to verify a proof in the PCS + type VerifierParameters: From; + /// Type of a committed polynomial + type Commitment: Clone + Copy + PartialEq + SerdeObject + Send + Sync; // Thinking of having an MSM here + + /// Setup the parameters for the PCS + fn setup(k: u32) -> Self::Parameters; + /// Create a new instantiation of the PCS + fn new(params: Self::Parameters) -> Self; + /// Commit to a polynomial + fn commit(&self, polynomial: &Polynomial, blind: Blind) -> Self::Commitment; + /// Create an opening proof at a specific query + fn open( &self, - poly: &Polynomial, - r: Blind, - ) -> C::CurveExt; - - /// Writes params to a buffer. - fn write(&self, writer: &mut W) -> io::Result<()>; - - /// Reads params from a buffer. - fn read(reader: &mut R) -> io::Result; -} - -/// Parameters for circuit sysnthesis and prover parameters. -pub trait ParamsProver<'params, C: CurveAffine>: Params<'params, C> { - /// Constant verifier parameters. - type ParamsVerifier: ParamsVerifier<'params, C>; - - /// Returns new instance of parameters - fn new(k: u32) -> Self; - - /// This computes a commitment to a polynomial described by the provided - /// slice of coefficients. The commitment may be blinded by the blinding - /// factor `r`. - fn commit(&self, poly: &Polynomial, r: Blind) - -> C::CurveExt; - - /// Getter for g generators - fn get_g(&self) -> &[C]; - - /// Returns verification parameters. - fn verifier_params(&'params self) -> &'params Self::ParamsVerifier; -} - -/// Verifier specific functionality with circuit constraints -pub trait ParamsVerifier<'params, C: CurveAffine>: Params<'params, C> {} - -/// Multi scalar multiplication engine -pub trait MSM: Clone + Debug + Send + Sync { - /// Add arbitrary term (the scalar and the point) - fn append_term(&mut self, scalar: C::Scalar, point: C::CurveExt); - - /// Add another multiexp into this one - fn add_msm(&mut self, other: &Self) - where - Self: Sized; - - /// Scale all scalars in the MSM by some scaling factor - fn scale(&mut self, factor: C::Scalar); - - /// Perform multiexp and check that it results in zero - fn check(&self) -> bool; - - /// Perform multiexp and return the result - fn eval(&self) -> C::CurveExt; - - /// Return base points - fn bases(&self) -> Vec; - - /// Scalars - fn scalars(&self) -> Vec; -} - -/// Common multi-open prover interface for various commitment schemes -pub trait Prover<'params, Scheme: CommitmentScheme> { - /// Query instance or not - const QUERY_INSTANCE: bool; - - /// Creates new prover instance - fn new(params: &'params Scheme::ParamsProver) -> Self; - - /// Create a multi-opening proof - fn create_proof< - 'com, - E: EncodedChallenge, - T: TranscriptWrite, - R, - I, - >( - &self, - rng: R, + prover_query: Vec>, transcript: &mut T, - queries: I, - ) -> io::Result<()> + ) -> Result<(), Error> where - I: IntoIterator> + Clone, - R: RngCore; -} - -/// Common multi-open verifier interface for various commitment schemes -pub trait Verifier<'params, Scheme: CommitmentScheme> { - /// Unfinalized verification result. This is returned in verification - /// to allow developer to compress or combined verification results - type Guard: Guard; - - /// Accumulator fot comressed verification - type MSMAccumulator; - - /// Query instance or not - const QUERY_INSTANCE: bool; - - /// Creates new verifier instance - fn new(params: &'params Scheme::ParamsVerifier) -> Self; - - /// Process the proof and returns unfinished result named `Guard` - fn verify_proof< - 'com, - E: EncodedChallenge, - T: TranscriptRead, - I, - >( + F: Sampleable, + Self::Commitment: Hashable; + /// Verify an opening proof at a given query + fn verify( &self, + verifier_query: Vec>, transcript: &mut T, - queries: I, - msm: Self::MSMAccumulator, - ) -> Result + ) -> Result<(), Error> where - 'params: 'com, - I: IntoIterator< - Item = VerifierQuery< - 'com, - Scheme::Curve, - >::MSM, - >, - > + Clone; -} - -/// Wrapper type around a blinding factor. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub struct Blind(pub F); - -impl Default for Blind { - fn default() -> Self { - Blind(F::ONE) - } -} - -impl Blind { - /// Given `rng` creates new blinding scalar - pub fn new(rng: &mut R) -> Self { - Blind(F::random(rng)) - } -} - -impl Add for Blind { - type Output = Self; - - fn add(self, rhs: Blind) -> Self { - Blind(self.0 + rhs.0) - } -} - -impl Mul for Blind { - type Output = Self; - - fn mul(self, rhs: Blind) -> Self { - Blind(self.0 * rhs.0) - } -} - -impl AddAssign for Blind { - fn add_assign(&mut self, rhs: Blind) { - self.0 += rhs.0; - } -} - -impl MulAssign for Blind { - fn mul_assign(&mut self, rhs: Blind) { - self.0 *= rhs.0; - } -} - -impl AddAssign for Blind { - fn add_assign(&mut self, rhs: F) { - self.0 += rhs; - } -} - -impl MulAssign for Blind { - fn mul_assign(&mut self, rhs: F) { - self.0 *= rhs; - } + F: Sampleable, + Self::Commitment: Hashable; } diff --git a/src/poly/domain.rs b/src/poly/domain.rs index ae9b8bf9ae..7ab195c9b2 100644 --- a/src/poly/domain.rs +++ b/src/poly/domain.rs @@ -3,7 +3,7 @@ use crate::{ arithmetic::{best_fft, parallelize}, - plonk::Assigned, + rational::Rational, }; use super::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation}; @@ -186,7 +186,7 @@ impl> EvaluationDomain { /// Returns an empty (zero) polynomial in the Lagrange coefficient basis, with /// deferred inversions. - pub fn empty_lagrange_assigned(&self) -> Polynomial, LagrangeCoeff> { + pub fn empty_lagrange_rational(&self) -> Polynomial, LagrangeCoeff> { Polynomial { values: vec![F::ZERO.into(); self.n as usize], _marker: PhantomData, diff --git a/src/poly/kzg/gwc.rs b/src/poly/kzg/gwc.rs deleted file mode 100644 index 3fd28dd00a..0000000000 --- a/src/poly/kzg/gwc.rs +++ /dev/null @@ -1,50 +0,0 @@ -mod prover; -mod verifier; - -pub use prover::ProverGWC; -pub use verifier::VerifierGWC; - -use crate::{poly::query::Query, transcript::ChallengeScalar}; -use ff::Field; -use std::marker::PhantomData; - -#[derive(Clone, Copy, Debug)] -struct U {} -type ChallengeU = ChallengeScalar; - -#[derive(Clone, Copy, Debug)] -struct V {} -type ChallengeV = ChallengeScalar; - -struct CommitmentData> { - queries: Vec, - point: F, - _marker: PhantomData, -} - -fn construct_intermediate_sets>(queries: I) -> Vec> -where - I: IntoIterator + Clone, -{ - let mut point_query_map: Vec<(F, Vec)> = Vec::new(); - for query in queries { - if let Some(pos) = point_query_map - .iter() - .position(|(point, _)| *point == query.get_point()) - { - let (_, queries) = &mut point_query_map[pos]; - queries.push(query); - } else { - point_query_map.push((query.get_point(), vec![query])); - } - } - - point_query_map - .into_iter() - .map(|(point, queries)| CommitmentData { - queries, - point, - _marker: PhantomData, - }) - .collect() -} diff --git a/src/poly/kzg/gwc/prover.rs b/src/poly/kzg/gwc/prover.rs deleted file mode 100644 index ecea01cb01..0000000000 --- a/src/poly/kzg/gwc/prover.rs +++ /dev/null @@ -1,89 +0,0 @@ -use super::{construct_intermediate_sets, ChallengeV, Query}; -use crate::arithmetic::{kate_division, powers}; -use crate::helpers::SerdeCurveAffine; -use crate::poly::commitment::ParamsProver; -use crate::poly::commitment::Prover; -use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; -use crate::poly::query::ProverQuery; -use crate::poly::{commitment::Blind, Polynomial}; -use crate::transcript::{EncodedChallenge, TranscriptWrite}; - -use group::Curve; -use halo2curves::pairing::Engine; -use halo2curves::CurveExt; -use rand_core::RngCore; -use std::fmt::Debug; -use std::io; -use std::marker::PhantomData; - -/// Concrete KZG prover with GWC variant -#[derive(Debug)] -pub struct ProverGWC<'params, E: Engine> { - params: &'params ParamsKZG, -} - -/// Create a multi-opening proof -impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> for ProverGWC<'params, E> -where - E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, - E::G2Affine: SerdeCurveAffine, -{ - const QUERY_INSTANCE: bool = false; - - fn new(params: &'params ParamsKZG) -> Self { - Self { params } - } - - /// Create a multi-opening proof - fn create_proof< - 'com, - Ch: EncodedChallenge, - T: TranscriptWrite, - R, - I, - >( - &self, - _: R, - transcript: &mut T, - queries: I, - ) -> io::Result<()> - where - I: IntoIterator> + Clone, - R: RngCore, - { - let v: ChallengeV<_> = transcript.squeeze_challenge_scalar(); - let commitment_data = construct_intermediate_sets(queries); - - for commitment_at_a_point in commitment_data.iter() { - let z = commitment_at_a_point.point; - let (poly_batch, eval_batch) = commitment_at_a_point - .queries - .iter() - .zip(powers(*v)) - .map(|(query, power_of_v)| { - assert_eq!(query.get_point(), z); - - let poly = query.get_commitment().poly; - let eval = query.get_eval(); - - (poly.clone() * power_of_v, eval * power_of_v) - }) - .reduce(|(poly_acc, eval_acc), (poly, eval)| (poly_acc + &poly, eval_acc + eval)) - .unwrap(); - - let poly_batch = &poly_batch - eval_batch; - let witness_poly = Polynomial { - values: kate_division(&poly_batch.values, z), - _marker: PhantomData, - }; - let w = self - .params - .commit(&witness_poly, Blind::default()) - .to_affine(); - - transcript.write_point(w)?; - } - Ok(()) - } -} diff --git a/src/poly/kzg/gwc/verifier.rs b/src/poly/kzg/gwc/verifier.rs deleted file mode 100644 index fcfda6941f..0000000000 --- a/src/poly/kzg/gwc/verifier.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::fmt::Debug; - -use super::{construct_intermediate_sets, ChallengeU, ChallengeV}; -use crate::arithmetic::powers; -use crate::helpers::SerdeCurveAffine; -use crate::poly::commitment::Verifier; -use crate::poly::commitment::MSM; -use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; -use crate::poly::kzg::msm::{DualMSM, MSMKZG}; -use crate::poly::kzg::strategy::GuardKZG; -use crate::poly::query::Query; -use crate::poly::query::{CommitmentReference, VerifierQuery}; -use crate::poly::Error; -use crate::transcript::{EncodedChallenge, TranscriptRead}; - -use ff::Field; -use halo2curves::pairing::{Engine, MultiMillerLoop}; -use halo2curves::CurveExt; - -#[derive(Debug)] -/// Concrete KZG verifier with GWC variant -pub struct VerifierGWC<'params, E: Engine> { - params: &'params ParamsKZG, -} - -impl<'params, E> Verifier<'params, KZGCommitmentScheme> for VerifierGWC<'params, E> -where - E: MultiMillerLoop + Debug, - E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, - E::G2Affine: SerdeCurveAffine, -{ - type Guard = GuardKZG<'params, E>; - type MSMAccumulator = DualMSM<'params, E>; - - const QUERY_INSTANCE: bool = false; - - fn new(params: &'params ParamsKZG) -> Self { - Self { params } - } - - fn verify_proof< - 'com, - Ch: EncodedChallenge, - T: TranscriptRead, - I, - >( - &self, - transcript: &mut T, - queries: I, - mut msm_accumulator: DualMSM<'params, E>, - ) -> Result - where - I: IntoIterator>> + Clone, - { - let v: ChallengeV<_> = transcript.squeeze_challenge_scalar(); - - let commitment_data = construct_intermediate_sets(queries); - - let w: Vec = (0..commitment_data.len()) - .map(|_| transcript.read_point().map_err(|_| Error::SamplingError)) - .collect::, Error>>()?; - - let u: ChallengeU<_> = transcript.squeeze_challenge_scalar(); - - let mut commitment_multi = MSMKZG::::new(); - let mut eval_multi = E::Fr::ZERO; - - let mut witness = MSMKZG::::new(); - let mut witness_with_aux = MSMKZG::::new(); - - for ((commitment_at_a_point, wi), power_of_u) in - commitment_data.iter().zip(w.into_iter()).zip(powers(*u)) - { - assert!(!commitment_at_a_point.queries.is_empty()); - let z = commitment_at_a_point.point; - - let (mut commitment_batch, eval_batch) = commitment_at_a_point - .queries - .iter() - .zip(powers(*v)) - .map(|(query, power_of_v)| { - assert_eq!(query.get_point(), z); - - let commitment = match query.get_commitment() { - CommitmentReference::Commitment(c) => { - let mut msm = MSMKZG::::new(); - msm.append_term(power_of_v, (*c).into()); - msm - } - CommitmentReference::MSM(msm) => { - let mut msm = msm.clone(); - msm.scale(power_of_v); - msm - } - }; - let eval = power_of_v * query.get_eval(); - - (commitment, eval) - }) - .reduce(|(mut commitment_acc, eval_acc), (commitment, eval)| { - commitment_acc.add_msm(&commitment); - (commitment_acc, eval_acc + eval) - }) - .unwrap(); - - commitment_batch.scale(power_of_u); - commitment_multi.add_msm(&commitment_batch); - eval_multi += power_of_u * eval_batch; - - witness_with_aux.append_term(power_of_u * z, wi.into()); - witness.append_term(power_of_u, wi.into()); - } - - msm_accumulator.left.add_msm(&witness); - - msm_accumulator.right.add_msm(&witness_with_aux); - msm_accumulator.right.add_msm(&commitment_multi); - let g0: E::G1 = self.params.g[0].into(); - msm_accumulator.right.append_term(eval_multi, -g0); - - Ok(Self::Guard::new(msm_accumulator)) - } -} diff --git a/src/poly/kzg/mod.rs b/src/poly/kzg/mod.rs index de86e9b774..517d962cac 100644 --- a/src/poly/kzg/mod.rs +++ b/src/poly/kzg/mod.rs @@ -1,8 +1,365 @@ -/// KZG commitment scheme -pub mod commitment; -/// KZG multi-open scheme -pub mod gwc; +use halo2curves::pairing::Engine; +use std::marker::PhantomData; + /// Multiscalar multiplication engines pub mod msm; -/// Strategies used with KZG scheme -pub mod strategy; +/// KZG commitment scheme +pub mod params; + +use std::fmt::Debug; + +use crate::arithmetic::{best_multiexp, kate_division, powers, MSM}; +use crate::poly::kzg::msm::{DualMSM, MSMKZG}; +use crate::poly::kzg::params::{ParamsKZG, ParamsVerifierKZG}; +use crate::poly::query::Query; +use crate::poly::query::VerifierQuery; +use crate::poly::{Blind, Coeff, Error, Polynomial, ProverQuery}; + +use crate::poly::commitment::PolynomialCommitmentScheme; +use crate::transcript::{Hashable, Sampleable, Transcript}; +use ff::Field; +use group::prime::PrimeCurveAffine; +use halo2curves::pairing::MultiMillerLoop; +use halo2curves::serde::SerdeObject; + +#[derive(Clone, Debug)] +/// KZG verifier +pub struct KZGCommitmentScheme { + params: ParamsKZG, +} + +impl PolynomialCommitmentScheme for KZGCommitmentScheme +where + E::Fr: SerdeObject, + E::G1Affine: SerdeObject, +{ + type Parameters = ParamsKZG; + type VerifierParameters = ParamsVerifierKZG; + type Commitment = E::G1Affine; + + fn setup(k: u32) -> ParamsKZG { + ParamsKZG::new(k) + } + + fn new(params: Self::Parameters) -> Self { + Self { params } + } + + fn commit( + &self, + polynomial: &Polynomial, + _blind: Blind, + ) -> Self::Commitment { + let mut scalars = Vec::with_capacity(polynomial.len()); + scalars.extend(polynomial.iter()); + let bases = &self.params.g; + let size = scalars.len(); + assert!(bases.len() >= size); + best_multiexp(&scalars, &bases[0..size]).into() + } + + fn open( + &self, + prover_query: Vec>, + transcript: &mut T, + ) -> Result<(), Error> + where + E::Fr: Sampleable, + E::G1Affine: Hashable, + { + let v: E::Fr = transcript.squeeze_challenge(); + let commitment_data = construct_intermediate_sets(prover_query.into_iter()); + + for commitment_at_a_point in commitment_data.iter() { + let z = commitment_at_a_point.point; + let (poly_batch, eval_batch) = commitment_at_a_point + .queries + .iter() + .zip(powers(v)) + .map(|(query, power_of_v)| { + assert_eq!(query.get_point(), z); + + let poly = query.get_commitment().poly; + let eval = query.get_eval(); + + (poly.clone() * power_of_v, eval * power_of_v) + }) + .reduce(|(poly_acc, eval_acc), (poly, eval)| (poly_acc + &poly, eval_acc + eval)) + .unwrap(); + + let poly_batch = &poly_batch - eval_batch; + let witness_poly = Polynomial { + values: kate_division(&poly_batch.values, z), + _marker: PhantomData, + }; + let w = self.commit(&witness_poly, Blind::default()); + + transcript.write(&w).map_err(|_| Error::OpeningError)?; + } + + Ok(()) + } + + fn verify( + &self, + verifier_query: Vec>, + transcript: &mut T, + ) -> Result<(), Error> + where + E::Fr: Sampleable, + E::G1Affine: Hashable, + { + let v: E::Fr = transcript.squeeze_challenge(); + + let commitment_data = construct_intermediate_sets(verifier_query.into_iter()); + + let w = (0..commitment_data.len()) + .map(|_| transcript.read().map_err(|_| Error::SamplingError)) + .collect::, Error>>()?; + + let u: E::Fr = transcript.squeeze_challenge(); + + let mut commitment_multi = MSMKZG::::new(); + let mut eval_multi = E::Fr::ZERO; + + let mut witness = MSMKZG::::new(); + let mut witness_with_aux = MSMKZG::::new(); + + for ((commitment_at_a_point, wi), power_of_u) in + commitment_data.iter().zip(w.into_iter()).zip(powers(u)) + { + assert!(!commitment_at_a_point.queries.is_empty()); + let z = commitment_at_a_point.point; + + let (mut commitment_batch, eval_batch) = commitment_at_a_point + .queries + .iter() + .zip(powers(v)) + .map(|(query, power_of_v)| { + assert_eq!(query.get_point(), z); + + let mut commitment = MSMKZG::::new(); + commitment.append_term(power_of_v, query.get_commitment().to_curve()); + + let eval = power_of_v * query.get_eval(); + + (commitment, eval) + }) + .reduce(|(mut commitment_acc, eval_acc), (commitment, eval)| { + commitment_acc.add_msm(&commitment); + (commitment_acc, eval_acc + eval) + }) + .unwrap(); + + commitment_batch.scale(power_of_u); + commitment_multi.add_msm(&commitment_batch); + eval_multi += power_of_u * eval_batch; + + witness_with_aux.append_term(power_of_u * z, wi.to_curve()); + witness.append_term(power_of_u, wi.to_curve()); + } + + let mut msm = DualMSM::new(&self.params); + + msm.left.add_msm(&witness); + + msm.right.add_msm(&witness_with_aux); + msm.right.add_msm(&commitment_multi); + let g0: E::G1 = self.params.g[0].to_curve(); + msm.right.append_term(eval_multi, -g0); + + if msm.check() { + Ok(()) + } else { + Err(Error::OpeningError) + } + } +} + +#[derive(Debug)] +struct CommitmentData> { + queries: Vec, + point: F, + _marker: PhantomData, +} + +fn construct_intermediate_sets>(queries: I) -> Vec> +where + I: IntoIterator + Clone, +{ + let mut point_query_map: Vec<(F, Vec)> = Vec::new(); + for query in queries { + if let Some(pos) = point_query_map + .iter() + .position(|(point, _)| *point == query.get_point()) + { + let (_, queries) = &mut point_query_map[pos]; + queries.push(query); + } else { + point_query_map.push((query.get_point(), vec![query])); + } + } + + point_query_map + .into_iter() + .map(|(point, queries)| CommitmentData { + queries, + point, + _marker: PhantomData, + }) + .collect() +} + +#[cfg(test)] +mod tests { + use crate::arithmetic::eval_polynomial; + use crate::poly::commitment::PolynomialCommitmentScheme; + use crate::poly::kzg::KZGCommitmentScheme; + use crate::poly::{ + query::{ProverQuery, VerifierQuery}, + Blind, Error, EvaluationDomain, + }; + use crate::transcript::{CircuitTranscript, Hashable, Sampleable, Transcript}; + use blake2b_simd::State as Blake2bState; + use ff::WithSmallOrderMulGroup; + use halo2curves::pairing::{Engine, MultiMillerLoop}; + use halo2curves::serde::SerdeObject; + use halo2curves::CurveAffine; + use rand_core::OsRng; + + #[test] + fn test_roundtrip_gwc() { + use crate::poly::kzg::params::ParamsKZG; + use crate::poly::kzg::KZGCommitmentScheme; + use halo2curves::bn256::Bn256; + + const K: u32 = 4; + + let params = ParamsKZG::::new(K); + + let kzg_prover = KZGCommitmentScheme::new(params); + let proof = create_proof::<_, CircuitTranscript>(&kzg_prover); + + verify::<_, CircuitTranscript>(&kzg_prover, &proof[..], false); + + verify::>(&kzg_prover, &proof[..], true); + } + + fn verify( + verifier: &KZGCommitmentScheme, + proof: &[u8], + should_fail: bool, + ) where + E::Fr: SerdeObject + Hashable + Sampleable, + E::G1Affine: CurveAffine::Fr, CurveExt = ::G1> + + SerdeObject + + Hashable, + { + let mut transcript = T::parse(proof); + + let a: E::G1Affine = transcript.read().unwrap(); + let b: E::G1Affine = transcript.read().unwrap(); + let c: E::G1Affine = transcript.read().unwrap(); + + let x: E::Fr = transcript.squeeze_challenge(); + let y: E::Fr = transcript.squeeze_challenge(); + + let avx: E::Fr = transcript.read().unwrap(); + let bvx: E::Fr = transcript.read().unwrap(); + let cvy: E::Fr = transcript.read().unwrap(); + + let valid_queries = std::iter::empty() + .chain(Some(VerifierQuery::new(x, a, avx))) + .chain(Some(VerifierQuery::new(x, b, bvx))) + .chain(Some(VerifierQuery::new(y, c, cvy))); + + let invalid_queries = std::iter::empty() + .chain(Some(VerifierQuery::new(x, a, avx))) + .chain(Some(VerifierQuery::new(x, b, avx))) + .chain(Some(VerifierQuery::new(y, c, cvy))); + + let queries = if should_fail { + invalid_queries + } else { + valid_queries + }; + + let result = verifier + .verify(queries.collect::>(), &mut transcript) + .map_err(|_| Error::OpeningError); + + if should_fail { + assert!(result.is_err()); + } else { + assert!(result.is_ok()); + } + } + + fn create_proof( + kzg_prover: &KZGCommitmentScheme, + ) -> Vec + where + E::Fr: WithSmallOrderMulGroup<3> + SerdeObject + Hashable + Sampleable, + E::G1Affine: SerdeObject + Hashable, + { + let domain = EvaluationDomain::new(1, kzg_prover.params.k); + + let mut ax = domain.empty_coeff(); + for (i, a) in ax.iter_mut().enumerate() { + *a = ::from(10 + i as u64); + } + + let mut bx = domain.empty_coeff(); + for (i, a) in bx.iter_mut().enumerate() { + *a = ::from(100 + i as u64); + } + + let mut cx = domain.empty_coeff(); + for (i, a) in cx.iter_mut().enumerate() { + *a = ::from(100 + i as u64); + } + + let mut transcript = T::init(); + + let blind = Blind::new(&mut OsRng); + + let a = kzg_prover.commit(&ax, blind); + let b = kzg_prover.commit(&bx, blind); + let c = kzg_prover.commit(&cx, blind); + + transcript.write(&a).unwrap(); + transcript.write(&b).unwrap(); + transcript.write(&c).unwrap(); + + let x: E::Fr = transcript.squeeze_challenge(); + let y = transcript.squeeze_challenge(); + + let avx = eval_polynomial(&ax, x); + let bvx = eval_polynomial(&bx, x); + let cvy = eval_polynomial(&cx, y); + + transcript.write(&avx).unwrap(); + transcript.write(&bvx).unwrap(); + transcript.write(&cvy).unwrap(); + + let queries = [ + ProverQuery { + point: x, + poly: &ax, + }, + ProverQuery { + point: x, + poly: &bx, + }, + ProverQuery { + point: y, + poly: &cx, + }, + ] + .to_vec(); + + kzg_prover.open(queries, &mut transcript).unwrap(); + + transcript.finalize() + } +} diff --git a/src/poly/kzg/msm.rs b/src/poly/kzg/msm.rs index d7cf75ba13..afbd1be5a1 100644 --- a/src/poly/kzg/msm.rs +++ b/src/poly/kzg/msm.rs @@ -1,10 +1,8 @@ use std::fmt::Debug; -use super::commitment::ParamsKZG; -use crate::{ - arithmetic::{best_multiexp, parallelize}, - poly::commitment::MSM, -}; +use super::params::ParamsKZG; +use crate::arithmetic::MSM; +use crate::arithmetic::{best_multiexp, parallelize}; use group::{Curve, Group}; use halo2curves::{ pairing::{Engine, MillerLoopResult, MultiMillerLoop}, @@ -13,20 +11,12 @@ use halo2curves::{ /// A multiscalar multiplication in the polynomial commitment scheme #[derive(Clone, Default, Debug)] -pub struct MSMKZG -where - E::G1Affine: CurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, -{ +pub struct MSMKZG { pub(crate) scalars: Vec, pub(crate) bases: Vec, } -impl MSMKZG -where - E::G1Affine: CurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, -{ +impl MSMKZG { /// Create an empty MSM instance pub fn new() -> Self { MSMKZG { @@ -48,11 +38,7 @@ where } } -impl MSM for MSMKZG -where - E::G1Affine: CurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, -{ +impl MSM for MSMKZG { fn append_term(&mut self, scalar: E::Fr, point: E::G1) { self.scalars.push(scalar); self.bases.push(point); @@ -105,21 +91,13 @@ where /// Two channel MSM accumulator #[derive(Debug, Clone)] -pub struct DualMSM<'a, E: Engine> -where - E::G1Affine: CurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, -{ +pub struct DualMSM<'a, E: Engine> { pub(crate) params: &'a ParamsKZG, pub(crate) left: MSMKZG, pub(crate) right: MSMKZG, } -impl<'a, E: MultiMillerLoop + Debug> DualMSM<'a, E> -where - E::G1Affine: CurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, -{ +impl<'a, E: MultiMillerLoop + Debug> DualMSM<'a, E> { /// Create a new two channel MSM accumulator instance pub fn new(params: &'a ParamsKZG) -> Self { Self { diff --git a/src/poly/kzg/commitment.rs b/src/poly/kzg/params.rs similarity index 76% rename from src/poly/kzg/commitment.rs rename to src/poly/kzg/params.rs index 114b9ac013..e2b8dbff40 100644 --- a/src/poly/kzg/commitment.rs +++ b/src/poly/kzg/params.rs @@ -1,16 +1,13 @@ use crate::arithmetic::{best_multiexp, g_to_lagrange, parallelize}; use crate::helpers::SerdeCurveAffine; -use crate::poly::commitment::{Blind, CommitmentScheme, Params, ParamsProver, ParamsVerifier}; -use crate::poly::{Coeff, LagrangeCoeff, Polynomial}; +use crate::poly::{Blind, LagrangeCoeff, Polynomial}; use crate::SerdeFormat; use ff::{Field, PrimeField}; use group::{prime::PrimeCurveAffine, Curve, Group}; use halo2curves::pairing::Engine; -use halo2curves::CurveExt; use rand_core::{OsRng, RngCore}; use std::fmt::Debug; -use std::marker::PhantomData; use std::io; @@ -27,38 +24,7 @@ pub struct ParamsKZG { pub(crate) s_g2: E::G2Affine, } -/// Umbrella commitment scheme construction for all KZG variants -#[derive(Debug)] -pub struct KZGCommitmentScheme { - _marker: PhantomData, -} - -impl CommitmentScheme for KZGCommitmentScheme -where - E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, - E::G2Affine: SerdeCurveAffine, -{ - type Scalar = E::Fr; - type Curve = E::G1Affine; - - type ParamsProver = ParamsKZG; - type ParamsVerifier = ParamsVerifierKZG; - - fn new_params(k: u32) -> Self::ParamsProver { - ParamsKZG::new(k) - } - - fn read_params(reader: &mut R) -> io::Result { - ParamsKZG::read(reader) - } -} - -impl ParamsKZG -where - E::G1Affine: SerdeCurveAffine, - E::G1: CurveExt, -{ +impl ParamsKZG { /// Initializes parameters for the curve, draws toxic secret from given rng. /// MUST NOT be used in production. pub fn setup(k: u32, rng: R) -> Self { @@ -166,6 +132,7 @@ where /// Writes parameters to buffer pub fn write_custom(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> where + E::G1Affine: SerdeCurveAffine, E::G2Affine: SerdeCurveAffine, { writer.write_all(&self.k.to_le_bytes())?; @@ -183,6 +150,7 @@ where /// Reads params from a buffer. pub fn read_custom(reader: &mut R, format: SerdeFormat) -> io::Result where + E::G1Affine: SerdeCurveAffine, E::G2Affine: SerdeCurveAffine, { let mut k = [0u8; 4]; @@ -270,25 +238,25 @@ where // TODO: see the issue at https://github.com/appliedzkp/halo2/issues/45 // So we probably need much smaller verifier key. However for new bases in g1 should be in verifier keys. /// KZG multi-open verification parameters -pub type ParamsVerifierKZG = ParamsKZG; +pub type ParamsVerifierKZG = ParamsKZG; -impl<'params, E: Engine + Debug> Params<'params, E::G1Affine> for ParamsKZG +impl<'params, E: Engine + Debug> ParamsKZG where - E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, + E::G1Affine: SerdeCurveAffine, E::G2Affine: SerdeCurveAffine, { - type MSM = MSMKZG; - - fn k(&self) -> u32 { + /// TODO + pub fn k(&self) -> u32 { self.k } - fn n(&self) -> u64 { + /// TODO + pub fn n(&self) -> u64 { self.n } - fn downsize(&mut self, k: u32) { + /// TODO + pub fn downsize(&mut self, k: u32) { assert!(k <= self.k); self.k = k; @@ -298,11 +266,17 @@ where self.g_lagrange = g_to_lagrange(self.g.iter().map(|g| g.to_curve()).collect(), k); } - fn empty_msm(&'params self) -> MSMKZG { + /// TODO + pub fn empty_msm(&'params self) -> MSMKZG { MSMKZG::new() } - fn commit_lagrange(&self, poly: &Polynomial, _: Blind) -> E::G1 { + /// TODO + pub fn commit_lagrange( + &self, + poly: &Polynomial, + _: Blind, + ) -> E::G1 { let mut scalars = Vec::with_capacity(poly.len()); scalars.extend(poly.iter()); let bases = &self.g_lagrange; @@ -312,60 +286,41 @@ where } /// Writes params to a buffer. - fn write(&self, writer: &mut W) -> io::Result<()> { + pub fn write(&self, writer: &mut W) -> io::Result<()> { self.write_custom(writer, SerdeFormat::RawBytes) } /// Reads params from a buffer. - fn read(reader: &mut R) -> io::Result { + pub fn read(reader: &mut R) -> io::Result { Self::read_custom(reader, SerdeFormat::RawBytes) } } -impl<'params, E: Engine + Debug> ParamsVerifier<'params, E::G1Affine> for ParamsKZG -where - E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, - E::G2Affine: SerdeCurveAffine, -{ -} - -impl<'params, E: Engine + Debug> ParamsProver<'params, E::G1Affine> for ParamsKZG -where - E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, - E::G2Affine: SerdeCurveAffine, -{ - type ParamsVerifier = ParamsVerifierKZG; - - fn verifier_params(&'params self) -> &'params Self::ParamsVerifier { +impl<'params, E: Engine + Debug> ParamsKZG { + /// TODO + pub fn verifier_params(&'params self) -> &'params ParamsVerifierKZG { self } - fn new(k: u32) -> Self { + /// TODO + pub fn new(k: u32) -> Self { Self::setup(k, OsRng) } - fn commit(&self, poly: &Polynomial, _: Blind) -> E::G1 { - let mut scalars = Vec::with_capacity(poly.len()); - scalars.extend(poly.iter()); - let bases = &self.g; - let size = scalars.len(); - assert!(bases.len() >= size); - best_multiexp(&scalars, &bases[0..size]) - } - - fn get_g(&self) -> &[E::G1Affine] { + /// TODO + pub fn get_g(&self) -> &[E::G1Affine] { &self.g } } #[cfg(test)] mod test { - use crate::poly::commitment::ParamsProver; - use crate::poly::commitment::{Blind, Params}; - use crate::poly::kzg::commitment::ParamsKZG; + use crate::poly::commitment::PolynomialCommitmentScheme; + use crate::poly::kzg::params::ParamsKZG; + use crate::poly::kzg::KZGCommitmentScheme; + use crate::poly::Blind; use ff::Field; + use halo2curves::pairing::Engine; #[test] fn test_commit_lagrange() { @@ -388,21 +343,23 @@ mod test { let b = domain.lagrange_to_coeff(a.clone()); let alpha = Blind(Fr::random(OsRng)); + let tmp = params.commit_lagrange(&a, alpha); + let cs = KZGCommitmentScheme::new(params); + let commitment: ::G1 = cs.commit(&b, alpha).into(); - assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha)); + assert_eq!(commitment, tmp); } #[test] fn test_parameter_serialisation_roundtrip() { const K: u32 = 4; - use super::super::commitment::Params; use crate::halo2curves::bn256::Bn256; let params0 = ParamsKZG::::new(K); let mut data = vec![]; - as Params<_>>::write(¶ms0, &mut data).unwrap(); - let params1: ParamsKZG = Params::read::<_>(&mut &data[..]).unwrap(); + ParamsKZG::write(¶ms0, &mut data).unwrap(); + let params1 = ParamsKZG::::read::<_>(&mut &data[..]).unwrap(); assert_eq!(params0.k, params1.k); assert_eq!(params0.n, params1.n); diff --git a/src/poly/kzg/strategy.rs b/src/poly/kzg/strategy.rs deleted file mode 100644 index ee80d800ac..0000000000 --- a/src/poly/kzg/strategy.rs +++ /dev/null @@ -1,181 +0,0 @@ -use super::{ - commitment::{KZGCommitmentScheme, ParamsKZG}, - msm::DualMSM, -}; -use crate::{ - helpers::SerdeCurveAffine, - plonk::Error, - poly::{ - commitment::Verifier, - strategy::{Guard, VerificationStrategy}, - }, -}; -use ff::Field; -use halo2curves::{ - pairing::{Engine, MultiMillerLoop}, - CurveAffine, CurveExt, -}; -use rand_core::OsRng; -use std::fmt::Debug; - -/// Wrapper for linear verification accumulator -#[derive(Debug, Clone)] -pub struct GuardKZG<'params, E: MultiMillerLoop + Debug> -where - E::G1Affine: CurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, -{ - pub(crate) msm_accumulator: DualMSM<'params, E>, -} - -/// Define accumulator type as `DualMSM` -impl<'params, E> Guard> for GuardKZG<'params, E> -where - E: MultiMillerLoop + Debug, - E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, - E::G2Affine: SerdeCurveAffine, -{ - type MSMAccumulator = DualMSM<'params, E>; -} - -/// KZG specific operations -impl<'params, E: MultiMillerLoop + Debug> GuardKZG<'params, E> -where - E::G1Affine: CurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, -{ - pub(crate) fn new(msm_accumulator: DualMSM<'params, E>) -> Self { - Self { msm_accumulator } - } -} - -/// A verifier that checks multiple proofs in a batch -#[derive(Clone, Debug)] -pub struct AccumulatorStrategy<'params, E: Engine> -where - E::G1Affine: CurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, -{ - pub(crate) msm_accumulator: DualMSM<'params, E>, -} - -impl<'params, E: MultiMillerLoop + Debug> AccumulatorStrategy<'params, E> -where - E::G1Affine: CurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, -{ - /// Constructs an empty batch verifier - pub fn new(params: &'params ParamsKZG) -> Self { - AccumulatorStrategy { - msm_accumulator: DualMSM::new(params), - } - } - - /// Constructs and initialized new batch verifier - pub fn with(msm_accumulator: DualMSM<'params, E>) -> Self { - AccumulatorStrategy { msm_accumulator } - } -} - -/// A verifier that checks a single proof -#[derive(Clone, Debug)] -pub struct SingleStrategy<'params, E: Engine> -where - E::G1Affine: CurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, -{ - pub(crate) msm: DualMSM<'params, E>, -} - -impl<'params, E: MultiMillerLoop + Debug> SingleStrategy<'params, E> -where - E::G1Affine: CurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, -{ - /// Constructs an empty batch verifier - pub fn new(params: &'params ParamsKZG) -> Self { - SingleStrategy { - msm: DualMSM::new(params), - } - } -} - -impl< - 'params, - E: MultiMillerLoop + Debug, - V: Verifier< - 'params, - KZGCommitmentScheme, - MSMAccumulator = DualMSM<'params, E>, - Guard = GuardKZG<'params, E>, - >, - > VerificationStrategy<'params, KZGCommitmentScheme, V> for AccumulatorStrategy<'params, E> -where - E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, - E::G2Affine: SerdeCurveAffine, -{ - type Output = Self; - - fn new(params: &'params ParamsKZG) -> Self { - AccumulatorStrategy::new(params) - } - - fn process( - mut self, - f: impl FnOnce(V::MSMAccumulator) -> Result, - ) -> Result { - self.msm_accumulator.scale(E::Fr::random(OsRng)); - - // Guard is updated with new msm contributions - let guard = f(self.msm_accumulator)?; - Ok(Self { - msm_accumulator: guard.msm_accumulator, - }) - } - - fn finalize(self) -> bool { - self.msm_accumulator.check() - } -} - -impl< - 'params, - E: MultiMillerLoop + Debug, - V: Verifier< - 'params, - KZGCommitmentScheme, - MSMAccumulator = DualMSM<'params, E>, - Guard = GuardKZG<'params, E>, - >, - > VerificationStrategy<'params, KZGCommitmentScheme, V> for SingleStrategy<'params, E> -where - E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, - E::G1: CurveExt, - E::G2Affine: SerdeCurveAffine, -{ - type Output = (); - - fn new(params: &'params ParamsKZG) -> Self { - Self::new(params) - } - - fn process( - self, - f: impl FnOnce(V::MSMAccumulator) -> Result, - ) -> Result { - // Guard is updated with new msm contributions - let guard = f(self.msm)?; - let msm = guard.msm_accumulator; - if msm.check() { - Ok(()) - } else { - Err(Error::ConstraintSystemFailure) - } - } - - fn finalize(self) -> bool { - unreachable!(); - } -} diff --git a/src/poly/multiopen_test.rs b/src/poly/multiopen_test.rs deleted file mode 100644 index 14f1de7f59..0000000000 --- a/src/poly/multiopen_test.rs +++ /dev/null @@ -1,184 +0,0 @@ -#[cfg(test)] -mod test { - use crate::arithmetic::eval_polynomial; - use crate::plonk::Error; - use crate::poly::commitment::Blind; - use crate::poly::commitment::ParamsProver; - use crate::poly::{ - commitment::{CommitmentScheme, Params, Prover, Verifier}, - query::{ProverQuery, VerifierQuery}, - strategy::VerificationStrategy, - EvaluationDomain, - }; - use crate::transcript::{ - Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge, TranscriptReadBuffer, - TranscriptWriterBuffer, - }; - use ff::WithSmallOrderMulGroup; - use group::Curve; - use rand_core::OsRng; - - #[test] - fn test_roundtrip_gwc() { - use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; - use crate::poly::kzg::gwc::{ProverGWC, VerifierGWC}; - use crate::poly::kzg::strategy::AccumulatorStrategy; - use halo2curves::bn256::Bn256; - - const K: u32 = 4; - - let params = ParamsKZG::::new(K); - - let proof = - create_proof::<_, ProverGWC<_>, _, Blake2bWrite<_, _, Challenge255<_>>>(¶ms); - - let verifier_params = params.verifier_params(); - - verify::<_, VerifierGWC<_>, _, Blake2bRead<_, _, Challenge255<_>>, AccumulatorStrategy<_>>( - verifier_params, - &proof[..], - false, - ); - - verify::< - KZGCommitmentScheme, - VerifierGWC<_>, - _, - Blake2bRead<_, _, Challenge255<_>>, - AccumulatorStrategy<_>, - >(verifier_params, &proof[..], true); - } - - fn verify< - 'a, - 'params, - Scheme: CommitmentScheme, - V: Verifier<'params, Scheme>, - E: EncodedChallenge, - T: TranscriptReadBuffer<&'a [u8], Scheme::Curve, E>, - Strategy: VerificationStrategy<'params, Scheme, V, Output = Strategy>, - >( - params: &'params Scheme::ParamsVerifier, - proof: &'a [u8], - should_fail: bool, - ) { - let verifier = V::new(params); - - let mut transcript = T::init(proof); - - let a = transcript.read_point().unwrap(); - let b = transcript.read_point().unwrap(); - let c = transcript.read_point().unwrap(); - - let x = transcript.squeeze_challenge(); - let y = transcript.squeeze_challenge(); - - let avx = transcript.read_scalar().unwrap(); - let bvx = transcript.read_scalar().unwrap(); - let cvy = transcript.read_scalar().unwrap(); - - let valid_queries = std::iter::empty() - .chain(Some(VerifierQuery::new_commitment(&a, x.get_scalar(), avx))) - .chain(Some(VerifierQuery::new_commitment(&b, x.get_scalar(), bvx))) - .chain(Some(VerifierQuery::new_commitment(&c, y.get_scalar(), cvy))); - - let invalid_queries = std::iter::empty() - .chain(Some(VerifierQuery::new_commitment(&a, x.get_scalar(), avx))) - .chain(Some(VerifierQuery::new_commitment(&b, x.get_scalar(), avx))) - .chain(Some(VerifierQuery::new_commitment(&c, y.get_scalar(), cvy))); - - let queries = if should_fail { - invalid_queries.clone() - } else { - valid_queries.clone() - }; - - { - let strategy = Strategy::new(params); - let strategy = strategy - .process(|msm_accumulator| { - verifier - .verify_proof(&mut transcript, queries.clone(), msm_accumulator) - .map_err(|_| Error::Opening) - }) - .unwrap(); - - assert_eq!(strategy.finalize(), !should_fail); - } - } - - fn create_proof< - 'params, - Scheme: CommitmentScheme, - P: Prover<'params, Scheme>, - E: EncodedChallenge, - T: TranscriptWriterBuffer, Scheme::Curve, E>, - >( - params: &'params Scheme::ParamsProver, - ) -> Vec - where - Scheme::Scalar: WithSmallOrderMulGroup<3>, - { - let domain = EvaluationDomain::new(1, params.k()); - - let mut ax = domain.empty_coeff(); - for (i, a) in ax.iter_mut().enumerate() { - *a = <::Scalar>::from(10 + i as u64); - } - - let mut bx = domain.empty_coeff(); - for (i, a) in bx.iter_mut().enumerate() { - *a = <::Scalar>::from(100 + i as u64); - } - - let mut cx = domain.empty_coeff(); - for (i, a) in cx.iter_mut().enumerate() { - *a = <::Scalar>::from(100 + i as u64); - } - - let mut transcript = T::init(vec![]); - - let blind = Blind::new(&mut OsRng); - let a = params.commit(&ax, blind).to_affine(); - let b = params.commit(&bx, blind).to_affine(); - let c = params.commit(&cx, blind).to_affine(); - - transcript.write_point(a).unwrap(); - transcript.write_point(b).unwrap(); - transcript.write_point(c).unwrap(); - - let x = transcript.squeeze_challenge(); - let y = transcript.squeeze_challenge(); - - let avx = eval_polynomial(&ax, x.get_scalar()); - let bvx = eval_polynomial(&bx, x.get_scalar()); - let cvy = eval_polynomial(&cx, y.get_scalar()); - - transcript.write_scalar(avx).unwrap(); - transcript.write_scalar(bvx).unwrap(); - transcript.write_scalar(cvy).unwrap(); - - let queries = [ - ProverQuery { - point: x.get_scalar(), - poly: &ax, - }, - ProverQuery { - point: x.get_scalar(), - poly: &bx, - }, - ProverQuery { - point: y.get_scalar(), - poly: &cx, - }, - ] - .to_vec(); - - let prover = P::new(params); - prover - .create_proof(&mut OsRng, &mut transcript, queries) - .unwrap(); - - transcript.finalize() - } -} diff --git a/src/poly/query.rs b/src/poly/query.rs index 6fa214fce2..7f022175cc 100644 --- a/src/poly/query.rs +++ b/src/poly/query.rs @@ -1,11 +1,11 @@ +use ff::PrimeField; use std::fmt::Debug; -use super::commitment::MSM; +use crate::poly::commitment::PolynomialCommitmentScheme; use crate::{ arithmetic::eval_polynomial, poly::{Coeff, Polynomial}, }; -use halo2curves::CurveAffine; pub trait Query: Sized + Clone + Send + Sync { type Commitment: PartialEq + Copy + Send + Sync; @@ -18,40 +18,40 @@ pub trait Query: Sized + Clone + Send + Sync { /// A polynomial query at a point #[derive(Debug, Clone, Copy)] -pub struct ProverQuery<'com, C: CurveAffine> { +pub struct ProverQuery<'com, F: PrimeField> { /// Point at which polynomial is queried - pub(crate) point: C::Scalar, + pub(crate) point: F, /// Coefficients of polynomial - pub(crate) poly: &'com Polynomial, + pub(crate) poly: &'com Polynomial, } -impl<'com, C> ProverQuery<'com, C> +impl<'com, F> ProverQuery<'com, F> where - C: CurveAffine, + F: PrimeField, { /// Create a new prover query based on a polynomial - pub fn new(point: C::Scalar, poly: &'com Polynomial) -> Self { + pub fn new(point: F, poly: &'com Polynomial) -> Self { ProverQuery { point, poly } } } #[doc(hidden)] #[derive(Copy, Clone)] -pub struct PolynomialPointer<'com, C: CurveAffine> { - pub(crate) poly: &'com Polynomial, +pub struct PolynomialPointer<'com, F: PrimeField> { + pub(crate) poly: &'com Polynomial, } -impl<'com, C: CurveAffine> PartialEq for PolynomialPointer<'com, C> { +impl<'com, F: PrimeField> PartialEq for PolynomialPointer<'com, F> { fn eq(&self, other: &Self) -> bool { std::ptr::eq(self.poly, other.poly) } } -impl<'com, C: CurveAffine> Query for ProverQuery<'com, C> { - type Commitment = PolynomialPointer<'com, C>; - type Eval = C::Scalar; +impl<'com, F: PrimeField> Query for ProverQuery<'com, F> { + type Commitment = PolynomialPointer<'com, F>; + type Eval = F; - fn get_point(&self) -> C::Scalar { + fn get_point(&self) -> F { self.point } fn get_eval(&self) -> Self::Eval { @@ -62,48 +62,44 @@ impl<'com, C: CurveAffine> Query for ProverQuery<'com, C> { } } -impl<'com, C: CurveAffine, M: MSM> VerifierQuery<'com, C, M> { - /// Create a new verifier query based on a commitment - pub fn new_commitment(commitment: &'com C, point: C::Scalar, eval: C::Scalar) -> Self { - VerifierQuery { - point, - eval, - commitment: CommitmentReference::Commitment(commitment), - } - } - - /// Create a new verifier query based on a linear combination of commitments - pub fn new_msm(msm: &'com M, point: C::Scalar, eval: C::Scalar) -> VerifierQuery<'com, C, M> { - VerifierQuery { - point, - eval, - commitment: CommitmentReference::MSM(msm), - } - } -} +// impl<'com, F: PrimeField, CS: PolynomialCommitmentScheme> VerifierQuery<'com, F, CS> { +// /// Create a new verifier query based on a commitment +// pub fn new_commitment(commitment: &'com C, point: C::Scalar, eval: C::Scalar) -> Self { +// VerifierQuery { +// point, +// eval, +// commitment: CommitmentReference::Commitment(commitment), +// } +// } +// +// /// Create a new verifier query based on a linear combination of commitments +// pub fn new_msm(msm: &'com M, point: C::Scalar, eval: C::Scalar) -> VerifierQuery<'com, C, M> { +// VerifierQuery { +// point, +// eval, +// commitment: CommitmentReference::MSM(msm), +// } +// } +// } /// A polynomial query at a point #[derive(Debug, Clone, Copy)] -pub struct VerifierQuery<'com, C: CurveAffine, M: MSM> { +pub struct VerifierQuery> { /// Point at which polynomial is queried - pub(crate) point: C::Scalar, + pub(crate) point: F, /// Commitment to polynomial - pub(crate) commitment: CommitmentReference<'com, C, M>, + pub(crate) commitment: CS::Commitment, /// Evaluation of polynomial at query point - pub(crate) eval: C::Scalar, + pub(crate) eval: F, } -impl<'com, C, M> VerifierQuery<'com, C, M> +impl VerifierQuery where - C: CurveAffine, - M: MSM, + F: PrimeField, + CS: PolynomialCommitmentScheme, { /// Create a new verifier query based on a commitment - pub fn new( - point: C::Scalar, - commitment: CommitmentReference<'com, C, M>, - eval: C::Scalar, - ) -> Self { + pub fn new(point: F, commitment: CS::Commitment, eval: F) -> Self { VerifierQuery { point, commitment, @@ -112,36 +108,36 @@ where } } -#[allow(clippy::upper_case_acronyms)] -#[derive(Clone, Debug)] -pub enum CommitmentReference<'r, C: CurveAffine, M: MSM> { - Commitment(&'r C), - MSM(&'r M), -} - -impl<'r, C: CurveAffine, M: MSM> Copy for CommitmentReference<'r, C, M> {} - -impl<'r, C: CurveAffine, M: MSM> PartialEq for CommitmentReference<'r, C, M> { - #![allow(clippy::vtable_address_comparisons)] - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (&CommitmentReference::Commitment(a), &CommitmentReference::Commitment(b)) => { - std::ptr::eq(a, b) - } - (&CommitmentReference::MSM(a), &CommitmentReference::MSM(b)) => std::ptr::eq(a, b), - _ => false, - } - } -} - -impl<'com, C: CurveAffine, M: MSM> Query for VerifierQuery<'com, C, M> { - type Eval = C::Scalar; - type Commitment = CommitmentReference<'com, C, M>; - - fn get_point(&self) -> C::Scalar { +// #[allow(clippy::upper_case_acronyms)] +// #[derive(Clone, Debug)] +// pub enum CommitmentReference<'r, C: CurveAffine, M: MSM> { +// Commitment(&'r C), +// MSM(&'r M), +// } +// +// impl<'r, C: CurveAffine, M: MSM> Copy for CommitmentReference<'r, C, M> {} +// +// impl<'r, C: CurveAffine, M: MSM> PartialEq for CommitmentReference<'r, C, M> { +// #![allow(clippy::vtable_address_comparisons)] +// fn eq(&self, other: &Self) -> bool { +// match (self, other) { +// (&CommitmentReference::Commitment(a), &CommitmentReference::Commitment(b)) => { +// std::ptr::eq(a, b) +// } +// (&CommitmentReference::MSM(a), &CommitmentReference::MSM(b)) => std::ptr::eq(a, b), +// _ => false, +// } +// } +// } + +impl> Query for VerifierQuery { + type Commitment = CS::Commitment; + type Eval = F; + + fn get_point(&self) -> F { self.point } - fn get_eval(&self) -> C::Scalar { + fn get_eval(&self) -> F { self.eval } fn get_commitment(&self) -> Self::Commitment { diff --git a/src/poly/strategy.rs b/src/poly/strategy.rs deleted file mode 100644 index 850f95e6c9..0000000000 --- a/src/poly/strategy.rs +++ /dev/null @@ -1,31 +0,0 @@ -use super::commitment::{CommitmentScheme, Verifier}; -use crate::plonk::Error; - -/// Guards is unfinished verification result. Implement this to construct various -/// verification strategies such as aggregation and recursion. -pub trait Guard { - /// Multi scalar engine which is not evaluated yet. - type MSMAccumulator; -} - -/// Trait representing a strategy for verifying Halo 2 proofs. -pub trait VerificationStrategy<'params, Scheme: CommitmentScheme, V: Verifier<'params, Scheme>> { - /// The output type of this verification strategy after processing a proof. - type Output; - - /// Creates new verification strategy instance - fn new(params: &'params Scheme::ParamsVerifier) -> Self; - - /// Obtains an MSM from the verifier strategy and yields back the strategy's - /// output. - fn process( - self, - f: impl FnOnce(V::MSMAccumulator) -> Result, - ) -> Result; - - /// Finalizes the batch and checks its validity. - /// - /// Returns `false` if *some* proof was invalid. If the caller needs to identify - /// specific failing proofs, it must re-process the proofs separately. - fn finalize(self) -> bool; -} diff --git a/src/transcript.rs b/src/transcript.rs index 6e4f812bdf..2f895ffaeb 100644 --- a/src/transcript.rs +++ b/src/transcript.rs @@ -1,554 +1,194 @@ //! This module contains utilities and traits for dealing with Fiat-Shamir //! transcripts. +use blake2b_simd::{Params, State as Blake2bState}; -use blake2b_simd::{Params as Blake2bParams, State as Blake2bState}; -use group::ff::{FromUniformBytes, PrimeField}; -use sha3::{Digest, Keccak256}; -use std::convert::TryInto; - -use halo2curves::{Coordinates, CurveAffine}; - -use std::io::{self, Read, Write}; -use std::marker::PhantomData; +use ff::{FromUniformBytes, PrimeField}; +use group::prime::PrimeCurveAffine; +use group::GroupEncoding; +use halo2curves::bn256::{Fr, G1Affine}; +use halo2curves::serde::SerdeObject; +use std::io::{self, Cursor}; /// Prefix to a prover's message soliciting a challenge const BLAKE2B_PREFIX_CHALLENGE: u8 = 0; -/// Prefix to a prover's message containing a curve point -const BLAKE2B_PREFIX_POINT: u8 = 1; - -/// Prefix to a prover's message containing a scalar -const BLAKE2B_PREFIX_SCALAR: u8 = 2; - -/// Prefix to a prover's message soliciting a challenge -const KECCAK256_PREFIX_CHALLENGE: u8 = 0; - -/// First prefix to a prover's message soliciting a challenge -/// Not included in the growing state! -const KECCAK256_PREFIX_CHALLENGE_LO: u8 = 10; - -/// Second prefix to a prover's message soliciting a challenge -/// Not included in the growing state! -const KECCAK256_PREFIX_CHALLENGE_HI: u8 = 11; - -/// Prefix to a prover's message containing a curve point -const KECCAK256_PREFIX_POINT: u8 = 1; - -/// Prefix to a prover's message containing a scalar -const KECCAK256_PREFIX_SCALAR: u8 = 2; - -/// Generic transcript view (from either the prover or verifier's perspective) -pub trait Transcript> { - /// Squeeze an encoded verifier challenge from the transcript. - fn squeeze_challenge(&mut self) -> E; - - /// Squeeze a typed challenge (in the scalar field) from the transcript. - fn squeeze_challenge_scalar(&mut self) -> ChallengeScalar { - ChallengeScalar { - inner: self.squeeze_challenge().get_scalar(), - _marker: PhantomData, - } - } - - /// Writing the point to the transcript without writing it to the proof, - /// treating it as a common input. - fn common_point(&mut self, point: C) -> io::Result<()>; +// TODO: BEFORE WE HAD DIFFERENT PREFIXES FOR DIFFERENT TYPES +/// Prefix to a prover's message +const BLAKE2B_PREFIX_COMMON: u8 = 1; + +/* +/// NOTE ///// +Why do we need the below three traits? The reason is that we cannot implement +Into for an arbitrary type due to the orphan rule. This +means that we need to have a trait for something that is hashable. + */ + +/// Hash function that can be used for transcript +/// todo: This looks pretty similar to our sponge instructions in halo2 circuits. Check it out. +pub trait TranscriptHash { + /// Input type of the hash function + type Input; + /// Output type of the hash function + type Output; - /// Writing the scalar to the transcript without writing it to the proof, - /// treating it as a common input. - fn common_scalar(&mut self, scalar: C::Scalar) -> io::Result<()>; + /// Initialise the hasher + fn init() -> Self; + /// Absorb an element + fn absorb(&mut self, input: &Self::Input) -> &mut Self; + /// Squeeze an output + fn squeeze(&mut self) -> Self::Output; } -/// Transcript view from the perspective of a verifier that has access to an -/// input stream of data from the prover to the verifier. -pub trait TranscriptRead>: Transcript { - /// Read a curve point from the prover. - fn read_point(&mut self) -> io::Result; - - /// Read a curve scalar from the prover. - fn read_scalar(&mut self) -> io::Result; +/// Traits to represent values that can be hashed with a `TranscriptHash` +pub trait Hashable { + /// Converts the hashable type to a format that can be hashed with `H` + fn to_input(&self) -> H::Input; } -/// Transcript view from the perspective of a prover that has access to an -/// output stream of messages from the prover to the verifier. -pub trait TranscriptWrite>: Transcript { - /// Write a curve point to the proof and the transcript. - fn write_point(&mut self, point: C) -> io::Result<()>; - - /// Write a scalar to the proof and the transcript. - fn write_scalar(&mut self, scalar: C::Scalar) -> io::Result<()>; +/// Trait to represent values that can be sampled from a `TranscriptHash` +pub trait Sampleable { + /// Converts `H`'s output to Self + fn sample(out: H::Output) -> Self; } -/// Initializes transcript at verifier side. -pub trait TranscriptReadBuffer>: - TranscriptRead -{ - /// Initialize a transcript given an input buffer. - fn init(reader: R) -> Self; -} +/// Generic transcript view +pub trait Transcript { + /// Hash function + type Hash: TranscriptHash; -/// Manages beginning and finishing of transcript pipeline. -pub trait TranscriptWriterBuffer>: - TranscriptWrite -{ - /// Initialize a transcript given an output buffer. - fn init(writer: W) -> Self; + /// Initialises the transcript + fn init() -> Self; - /// Conclude the interaction and return the output buffer (writer). - fn finalize(self) -> W; -} + /// Parses an existing transcript + fn parse(bytes: &[u8]) -> Self; -/// We will replace BLAKE2b with an algebraic hash function in a later version. -#[derive(Debug, Clone)] -pub struct Blake2bRead> { - state: Blake2bState, - reader: R, - _marker: PhantomData<(C, E)>, -} + /// Squeeze a challenge + fn squeeze_challenge>(&mut self) -> T; -/// Keccak256 hash function reader for EVM compatibility -#[derive(Debug, Clone)] -pub struct Keccak256Read> { - state: Keccak256, - reader: R, - _marker: PhantomData<(C, E)>, -} + /// Writing a commitment to the transcript without writing it to the proof, + /// treating it as a common commitment. + fn common>(&mut self, input: &T) -> io::Result<()>; -impl TranscriptReadBuffer> - for Blake2bRead> -where - C::Scalar: FromUniformBytes<64>, -{ - /// Initialize a transcript given an input buffer. - fn init(reader: R) -> Self { - Blake2bRead { - state: Blake2bParams::new() - .hash_length(64) - .personal(b"Halo2-Transcript") - .to_state(), - reader, - _marker: PhantomData, - } - } -} + /// Read a value from the prover. + /// FIXME: Do we want to rely on `SerdeObject`? + fn read + SerdeObject>(&mut self) -> io::Result; -impl TranscriptReadBuffer> - for Keccak256Read> -where - C::Scalar: FromUniformBytes<64>, -{ - /// Initialize a transcript given an input buffer. - fn init(reader: R) -> Self { - let mut state = Keccak256::new(); - state.update(b"Halo2-Transcript"); - Keccak256Read { - state, - reader, - _marker: PhantomData, - } - } -} + /// Write a value to the proof and the transcript. + fn write + SerdeObject>(&mut self, input: &T) -> io::Result<()>; -impl TranscriptRead> - for Blake2bRead> -where - C::Scalar: FromUniformBytes<64>, -{ - fn read_point(&mut self) -> io::Result { - let mut compressed = C::Repr::default(); - self.reader.read_exact(compressed.as_mut())?; - let point: C = Option::from(C::from_bytes(&compressed)).ok_or_else(|| { - io::Error::new(io::ErrorKind::Other, "invalid point encoding in proof") - })?; - self.common_point(point)?; - - Ok(point) - } + /// Returns the buffer with the proof + fn finalize(self) -> Vec; +} - fn read_scalar(&mut self) -> io::Result { - let mut data = ::Repr::default(); - self.reader.read_exact(data.as_mut())?; - let scalar: C::Scalar = Option::from(C::Scalar::from_repr(data)).ok_or_else(|| { - io::Error::new( - io::ErrorKind::Other, - "invalid field element encoding in proof", - ) - })?; - self.common_scalar(scalar)?; - - Ok(scalar) - } +#[derive(Debug)] +/// Transcript used in proofs, parametrised by its hash function. +pub struct CircuitTranscript { + state: H, + buffer: Cursor>, } -impl TranscriptRead> - for Keccak256Read> -where - C::Scalar: FromUniformBytes<64>, -{ - fn read_point(&mut self) -> io::Result { - let mut compressed = C::Repr::default(); - self.reader.read_exact(compressed.as_mut())?; - let point: C = Option::from(C::from_bytes(&compressed)).ok_or_else(|| { - io::Error::new(io::ErrorKind::Other, "invalid point encoding in proof") - })?; - self.common_point(point)?; - - Ok(point) - } +impl Transcript for CircuitTranscript { + type Hash = H; - fn read_scalar(&mut self) -> io::Result { - let mut data = ::Repr::default(); - self.reader.read_exact(data.as_mut())?; - let scalar: C::Scalar = Option::from(C::Scalar::from_repr(data)).ok_or_else(|| { - io::Error::new( - io::ErrorKind::Other, - "invalid field element encoding in proof", - ) - })?; - self.common_scalar(scalar)?; - - Ok(scalar) + fn init() -> Self { + Self { + state: H::init(), + buffer: Cursor::new(vec![]), + } } -} -impl Transcript> for Blake2bRead> -where - C::Scalar: FromUniformBytes<64>, -{ - fn squeeze_challenge(&mut self) -> Challenge255 { - self.state.update(&[BLAKE2B_PREFIX_CHALLENGE]); - let hasher = self.state.clone(); - let result: [u8; 64] = hasher.finalize().as_bytes().try_into().unwrap(); - Challenge255::::new(&result) + fn parse(bytes: &[u8]) -> Self { + Self { + state: H::init(), + buffer: Cursor::new(bytes.to_vec()), + } } - fn common_point(&mut self, point: C) -> io::Result<()> { - self.state.update(&[BLAKE2B_PREFIX_POINT]); - let coords: Coordinates = Option::from(point.coordinates()).ok_or_else(|| { - io::Error::new( - io::ErrorKind::Other, - "cannot write points at infinity to the transcript", - ) - })?; - self.state.update(coords.x().to_repr().as_ref()); - self.state.update(coords.y().to_repr().as_ref()); - - Ok(()) + fn squeeze_challenge>(&mut self) -> T { + T::sample(self.state.squeeze()) } - fn common_scalar(&mut self, scalar: C::Scalar) -> io::Result<()> { - self.state.update(&[BLAKE2B_PREFIX_SCALAR]); - self.state.update(scalar.to_repr().as_ref()); + fn common>(&mut self, input: &T) -> io::Result<()> { + self.state.absorb(&input.to_input()); Ok(()) } -} - -impl Transcript> - for Keccak256Read> -where - C::Scalar: FromUniformBytes<64>, -{ - fn squeeze_challenge(&mut self) -> Challenge255 { - self.state.update([KECCAK256_PREFIX_CHALLENGE]); - let mut state_lo = self.state.clone(); - let mut state_hi = self.state.clone(); - state_lo.update([KECCAK256_PREFIX_CHALLENGE_LO]); - state_hi.update([KECCAK256_PREFIX_CHALLENGE_HI]); - let result_lo: [u8; 32] = state_lo.finalize().as_slice().try_into().unwrap(); - let result_hi: [u8; 32] = state_hi.finalize().as_slice().try_into().unwrap(); + fn read + SerdeObject>(&mut self) -> io::Result { + let val = T::read_raw(&mut self.buffer)?; + self.common(&val)?; - let mut t = result_lo.to_vec(); - t.extend_from_slice(&result_hi[..]); - let result: [u8; 64] = t.as_slice().try_into().unwrap(); - - Challenge255::::new(&result) + Ok(val) } - fn common_point(&mut self, point: C) -> io::Result<()> { - self.state.update([KECCAK256_PREFIX_POINT]); - let coords: Coordinates = Option::from(point.coordinates()).ok_or_else(|| { - io::Error::new( - io::ErrorKind::Other, - "cannot write points at infinity to the transcript", - ) - })?; - self.state.update(coords.x().to_repr().as_ref()); - self.state.update(coords.y().to_repr().as_ref()); - - Ok(()) + fn write + SerdeObject>(&mut self, input: &T) -> io::Result<()> { + self.common(input)?; + input.write_raw(&mut self.buffer) } - fn common_scalar(&mut self, scalar: C::Scalar) -> io::Result<()> { - self.state.update([KECCAK256_PREFIX_SCALAR]); - self.state.update(scalar.to_repr().as_ref()); - - Ok(()) + fn finalize(self) -> Vec { + self.buffer.into_inner() } } -/// We will replace BLAKE2b with an algebraic hash function in a later version. -#[derive(Debug, Clone)] -pub struct Blake2bWrite> { - state: Blake2bState, - writer: W, - _marker: PhantomData<(C, E)>, -} - -/// Keccak256 hash function writer for EVM compatibility -#[derive(Debug, Clone)] -pub struct Keccak256Write> { - state: Keccak256, - writer: W, - _marker: PhantomData<(C, E)>, -} - -impl TranscriptWriterBuffer> - for Blake2bWrite> -where - C::Scalar: FromUniformBytes<64>, -{ - /// Initialize a transcript given an output buffer. - fn init(writer: W) -> Self { - Blake2bWrite { - state: Blake2bParams::new() - .hash_length(64) - .personal(b"Halo2-Transcript") - .to_state(), - writer, - _marker: PhantomData, - } - } +impl TranscriptHash for Blake2bState { + type Input = Vec; + type Output = Vec; - fn finalize(self) -> W { - // TODO: handle outstanding scalars? see issue #138 - self.writer + fn init() -> Self { + Params::new() + .hash_length(64) + .key(b"Domain separator for transcript") + .to_state() } -} -impl TranscriptWriterBuffer> - for Keccak256Write> -where - C::Scalar: FromUniformBytes<64>, -{ - /// Initialize a transcript given an output buffer. - fn init(writer: W) -> Self { - let mut state = Keccak256::new(); - state.update(b"Halo2-Transcript"); - Keccak256Write { - state, - writer, - _marker: PhantomData, - } + fn absorb(&mut self, input: &Self::Input) -> &mut Self { + self.update(&[BLAKE2B_PREFIX_COMMON]); + self.update(input) } - /// Conclude the interaction and return the output buffer (writer). - fn finalize(self) -> W { - // TODO: handle outstanding scalars? see issue #138 - self.writer + fn squeeze(&mut self) -> Self::Output { + self.update(&[BLAKE2B_PREFIX_CHALLENGE]); + self.finalize().as_bytes().to_vec() } } +/////////////////////////////////////////////////// +/// Implementation of Hashable for BN with Blake // +/////////////////////////////////////////////////// -impl TranscriptWrite> - for Blake2bWrite> -where - C::Scalar: FromUniformBytes<64>, -{ - fn write_point(&mut self, point: C) -> io::Result<()> { - self.common_point(point)?; - let compressed = point.to_bytes(); - self.writer.write_all(compressed.as_ref()) - } - fn write_scalar(&mut self, scalar: C::Scalar) -> io::Result<()> { - self.common_scalar(scalar)?; - let data = scalar.to_repr(); - self.writer.write_all(data.as_ref()) +impl Hashable for G1Affine { + fn to_input(&self) -> Vec { + self.to_bytes().as_ref().to_vec() } } -impl TranscriptWrite> - for Keccak256Write> -where - C::Scalar: FromUniformBytes<64>, -{ - fn write_point(&mut self, point: C) -> io::Result<()> { - self.common_point(point)?; - let compressed = point.to_bytes(); - self.writer.write_all(compressed.as_ref()) - } - fn write_scalar(&mut self, scalar: C::Scalar) -> io::Result<()> { - self.common_scalar(scalar)?; - let data = scalar.to_repr(); - self.writer.write_all(data.as_ref()) +impl Hashable for Fr { + fn to_input(&self) -> Vec { + self.to_bytes().to_vec() } } -impl Transcript> - for Blake2bWrite> -where - C::Scalar: FromUniformBytes<64>, -{ - fn squeeze_challenge(&mut self) -> Challenge255 { - self.state.update(&[BLAKE2B_PREFIX_CHALLENGE]); - let hasher = self.state.clone(); - let result: [u8; 64] = hasher.finalize().as_bytes().try_into().unwrap(); - Challenge255::::new(&result) - } - - fn common_point(&mut self, point: C) -> io::Result<()> { - self.state.update(&[BLAKE2B_PREFIX_POINT]); - let coords: Coordinates = Option::from(point.coordinates()).ok_or_else(|| { - io::Error::new( - io::ErrorKind::Other, - "cannot write points at infinity to the transcript", - ) - })?; - self.state.update(coords.x().to_repr().as_ref()); - self.state.update(coords.y().to_repr().as_ref()); - - Ok(()) - } - - fn common_scalar(&mut self, scalar: C::Scalar) -> io::Result<()> { - self.state.update(&[BLAKE2B_PREFIX_SCALAR]); - self.state.update(scalar.to_repr().as_ref()); - - Ok(()) +impl Sampleable for Fr { + fn sample(out: Vec) -> Self { + assert!(out.len() <= 64); + let mut bytes = [0u8; 64]; + bytes[..out.len()].copy_from_slice(&out); + Fr::from_uniform_bytes(&bytes) } } -impl Transcript> - for Keccak256Write> +pub(crate) fn read_n_points(transcript: &mut T, n: usize) -> io::Result> where - C::Scalar: FromUniformBytes<64>, + T: Transcript, + C: PrimeCurveAffine + Hashable + SerdeObject, { - fn squeeze_challenge(&mut self) -> Challenge255 { - self.state.update([KECCAK256_PREFIX_CHALLENGE]); - - let mut state_lo = self.state.clone(); - let mut state_hi = self.state.clone(); - state_lo.update([KECCAK256_PREFIX_CHALLENGE_LO]); - state_hi.update([KECCAK256_PREFIX_CHALLENGE_HI]); - let result_lo: [u8; 32] = state_lo.finalize().as_slice().try_into().unwrap(); - let result_hi: [u8; 32] = state_hi.finalize().as_slice().try_into().unwrap(); - - let mut t = result_lo.to_vec(); - t.extend_from_slice(&result_hi[..]); - let result: [u8; 64] = t.as_slice().try_into().unwrap(); - - Challenge255::::new(&result) - } - - fn common_point(&mut self, point: C) -> io::Result<()> { - self.state.update([KECCAK256_PREFIX_POINT]); - let coords: Coordinates = Option::from(point.coordinates()).ok_or_else(|| { - io::Error::new( - io::ErrorKind::Other, - "cannot write points at infinity to the transcript", - ) - })?; - self.state.update(coords.x().to_repr().as_ref()); - self.state.update(coords.y().to_repr().as_ref()); - - Ok(()) - } - - fn common_scalar(&mut self, scalar: C::Scalar) -> io::Result<()> { - self.state.update([KECCAK256_PREFIX_SCALAR]); - self.state.update(scalar.to_repr().as_ref()); - - Ok(()) - } -} - -/// The scalar representation of a verifier challenge. -/// -/// The `Type` type can be used to scope the challenge to a specific context, or -/// set to `()` if no context is required. -#[derive(Copy, Clone, Debug)] -pub struct ChallengeScalar { - inner: C::Scalar, - _marker: PhantomData, -} - -impl std::ops::Deref for ChallengeScalar { - type Target = C::Scalar; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -/// `EncodedChallenge` defines a challenge encoding with a [`Self::Input`] -/// that is used to derive the challenge encoding and `get_challenge` obtains -/// the _real_ `C::Scalar` that the challenge encoding represents. -pub trait EncodedChallenge { - /// The Input type used to derive the challenge encoding. For example, - /// an input from the Poseidon hash would be a base field element; - /// an input from the Blake2b hash would be a [u8; 64]. - type Input; - - /// Get an encoded challenge from a given input challenge. - fn new(challenge_input: &Self::Input) -> Self; - - /// Get a scalar field element from an encoded challenge. - fn get_scalar(&self) -> C::Scalar; - - /// Cast an encoded challenge as a typed `ChallengeScalar`. - fn as_challenge_scalar(&self) -> ChallengeScalar { - ChallengeScalar { - inner: self.get_scalar(), - _marker: PhantomData, - } - } + (0..n).map(|_| transcript.read()).collect() } -/// A 255-bit challenge. -#[derive(Copy, Clone, Debug)] -pub struct Challenge255([u8; 32], PhantomData); - -impl std::ops::Deref for Challenge255 { - type Target = [u8; 32]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl EncodedChallenge for Challenge255 +pub(crate) fn read_n_scalars(transcript: &mut T, n: usize) -> io::Result> where - C::Scalar: FromUniformBytes<64>, + T: Transcript, + F: PrimeField + Hashable + SerdeObject, { - type Input = [u8; 64]; - - fn new(challenge_input: &[u8; 64]) -> Self { - Challenge255( - C::Scalar::from_uniform_bytes(challenge_input) - .to_repr() - .as_ref() - .try_into() - .expect("Scalar fits into 256 bits"), - PhantomData, - ) - } - fn get_scalar(&self) -> C::Scalar { - let mut repr = ::Repr::default(); - repr.as_mut().copy_from_slice(&self.0); - C::Scalar::from_repr(repr).unwrap() - } -} - -pub(crate) fn read_n_points, T: TranscriptRead>( - transcript: &mut T, - n: usize, -) -> io::Result> { - (0..n).map(|_| transcript.read_point()).collect() -} - -pub(crate) fn read_n_scalars, T: TranscriptRead>( - transcript: &mut T, - n: usize, -) -> io::Result> { - (0..n).map(|_| transcript.read_scalar()).collect() + (0..n).map(|_| transcript.read()).collect() } From 5fda15b5cbcb8f4dc0b119bf6a55d4c7d81c82a0 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Fri, 6 Dec 2024 12:00:06 +0100 Subject: [PATCH 08/20] Plonk working parametrised only by F and PCS. --- examples/shuffle.rs | 2 +- examples/vector-ops-unblinded.rs | 2 +- src/circuit.rs | 23 +- src/circuit/floor_planner/single_pass.rs | 15 +- src/circuit/floor_planner/v1.rs | 15 +- src/circuit/layouter.rs | 19 +- src/circuit/table_layouter.rs | 9 +- src/circuit/value.rs | 90 +- src/dev.rs | 13 +- src/dev/cost.rs | 11 +- src/dev/tfp.rs | 19 +- src/helpers.rs | 8 + src/lib.rs | 6 +- src/plonk.rs | 176 ++-- src/plonk/circuit.rs | 9 +- src/plonk/evaluation.rs | 108 +-- src/plonk/keygen.rs | 112 +-- src/plonk/lookup/prover.rs | 236 +++-- src/plonk/lookup/verifier.rs | 171 ++-- src/plonk/permutation.rs | 48 +- src/plonk/permutation/keygen.rs | 79 +- src/plonk/permutation/prover.rs | 161 ++-- src/plonk/permutation/verifier.rs | 155 +-- src/plonk/prover.rs | 272 ++---- src/plonk/vanishing.rs | 8 +- src/plonk/vanishing/prover.rs | 154 ++- src/plonk/vanishing/verifier.rs | 156 ++- src/plonk/verifier.rs | 169 +--- src/poly.rs | 156 +-- src/poly/commitment.rs | 53 +- src/poly/domain.rs | 12 +- src/poly/kzg/mod.rs | 79 +- src/poly/kzg/params.rs | 59 +- src/{plonk/assigned.rs => rational.rs} | 189 ++-- src/transcript.rs | 20 +- tests/plonk_api.rs | 1116 +++++++++++----------- 36 files changed, 1820 insertions(+), 2110 deletions(-) rename src/{plonk/assigned.rs => rational.rs} (78%) diff --git a/examples/shuffle.rs b/examples/shuffle.rs index 90c3241747..b5e3502c97 100644 --- a/examples/shuffle.rs +++ b/examples/shuffle.rs @@ -1,6 +1,6 @@ fn main() {} -// use ff::{BatchInvert, FromUniformBytes, WithSmallOrderMulGroup}; +// use ff::{BatchInvert, FromUniformBytes}; // use halo2_proofs::arithmetic::CurveExt; // use halo2_proofs::helpers::SerdeCurveAffine; // use halo2_proofs::poly::kzg::params::{KZGCommitmentScheme, ParamsKZG}; diff --git a/examples/vector-ops-unblinded.rs b/examples/vector-ops-unblinded.rs index 501982560c..ed75fd1149 100644 --- a/examples/vector-ops-unblinded.rs +++ b/examples/vector-ops-unblinded.rs @@ -5,7 +5,7 @@ fn main() {} // /// This is a simple example of how to use unblinded inputs to match up circuits that might be proved on different host machines. // use std::marker::PhantomData; // -// use ff::{FromUniformBytes, WithSmallOrderMulGroup}; +// use ff::{FromUniformBytes}; // use halo2_proofs::arithmetic::CurveExt; // use halo2_proofs::helpers::SerdeCurveAffine; // use halo2_proofs::poly::kzg::params::{KZGCommitmentScheme, ParamsKZG}; diff --git a/src/circuit.rs b/src/circuit.rs index 56a6be0e5c..301ffd2f83 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -4,9 +4,7 @@ use std::{fmt, marker::PhantomData}; use ff::Field; -use crate::plonk::{ - Advice, Any, Assigned, Challenge, Column, Error, Fixed, Instance, Selector, TableColumn, -}; +use crate::plonk::{Advice, Any, Challenge, Column, Error, Fixed, Instance, Selector, TableColumn}; mod value; pub use value::Value; @@ -17,6 +15,7 @@ pub use floor_planner::single_pass::SimpleFloorPlanner; pub mod layouter; mod table_layouter; +use crate::rational::Rational; pub use table_layouter::{SimpleTableLayouter, TableLayouter}; /// A chip implements a set of instructions that can be used by gadgets. @@ -119,15 +118,15 @@ impl AssignedCell { impl AssignedCell where - for<'v> Assigned: From<&'v V>, + for<'v> Rational: From<&'v V>, { /// Returns the field element value of the [`AssignedCell`]. - pub fn value_field(&self) -> Value> { + pub fn value_field(&self) -> Value> { self.value.to_field() } } -impl AssignedCell, F> { +impl AssignedCell, F> { /// Evaluates this assigned cell's value directly, performing an unbatched inversion /// if necessary. /// @@ -143,7 +142,7 @@ impl AssignedCell, F> { impl AssignedCell where - for<'v> Assigned: From<&'v V>, + for<'v> Rational: From<&'v V>, { /// Copies the value to a given advice cell and constrains them to be equal. /// @@ -232,7 +231,7 @@ impl<'r, F: Field> Region<'r, F> { ) -> Result, Error> where V: FnMut() -> Value + 'v, - for<'vr> Assigned: From<&'vr VR>, + for<'vr> Rational: From<&'vr VR>, A: Fn() -> AR, AR: Into, { @@ -267,7 +266,7 @@ impl<'r, F: Field> Region<'r, F> { constant: VR, ) -> Result, Error> where - for<'vr> Assigned: From<&'vr VR>, + for<'vr> Rational: From<&'vr VR>, A: Fn() -> AR, AR: Into, { @@ -341,7 +340,7 @@ impl<'r, F: Field> Region<'r, F> { ) -> Result, Error> where V: FnMut() -> Value + 'v, - for<'vr> Assigned: From<&'vr VR>, + for<'vr> Rational: From<&'vr VR>, A: Fn() -> AR, AR: Into, { @@ -367,7 +366,7 @@ impl<'r, F: Field> Region<'r, F> { /// Returns an error if the cell is in a column where equality has not been enabled. pub fn constrain_constant(&mut self, cell: Cell, constant: VR) -> Result<(), Error> where - VR: Into>, + VR: Into>, { self.region.constrain_constant(cell, constant.into()) } @@ -408,7 +407,7 @@ impl<'r, F: Field> Table<'r, F> { ) -> Result<(), Error> where V: FnMut() -> Value + 'v, - VR: Into>, + VR: Into>, A: Fn() -> AR, AR: Into, { diff --git a/src/circuit/floor_planner/single_pass.rs b/src/circuit/floor_planner/single_pass.rs index 33c09e4c57..a2d76a79c8 100644 --- a/src/circuit/floor_planner/single_pass.rs +++ b/src/circuit/floor_planner/single_pass.rs @@ -5,6 +5,7 @@ use std::marker::PhantomData; use ff::Field; +use crate::rational::Rational; use crate::{ circuit::{ layouter::{RegionColumn, RegionLayouter, RegionShape, SyncDeps, TableLayouter}, @@ -12,8 +13,8 @@ use crate::{ Cell, Layouter, Region, RegionIndex, RegionStart, Table, Value, }, plonk::{ - Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, Error, Fixed, FloorPlanner, - Instance, Selector, TableColumn, + Advice, Any, Assignment, Challenge, Circuit, Column, Error, Fixed, FloorPlanner, Instance, + Selector, TableColumn, }, }; @@ -225,7 +226,7 @@ struct SingleChipLayouterRegion<'r, 'a, F: Field, CS: Assignment + 'a> { layouter: &'r mut SingleChipLayouter<'a, F, CS>, region_index: RegionIndex, /// Stores the constants to be assigned, and the cells to which they are copied. - constants: Vec<(Assigned, Cell)>, + constants: Vec<(Rational, Cell)>, } impl<'r, 'a, F: Field, CS: Assignment + 'a> fmt::Debug @@ -278,7 +279,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a + SyncDeps> RegionLayouter annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result { self.layouter.cs.assign_advice( annotation, @@ -299,7 +300,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a + SyncDeps> RegionLayouter annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - constant: Assigned, + constant: Rational, ) -> Result { let advice = self.assign_advice(annotation, column, offset, &mut || Value::known(constant))?; @@ -343,7 +344,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a + SyncDeps> RegionLayouter annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result { self.layouter.cs.assign_fixed( annotation, @@ -359,7 +360,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a + SyncDeps> RegionLayouter }) } - fn constrain_constant(&mut self, cell: Cell, constant: Assigned) -> Result<(), Error> { + fn constrain_constant(&mut self, cell: Cell, constant: Rational) -> Result<(), Error> { self.constants.push((constant, cell)); Ok(()) } diff --git a/src/circuit/floor_planner/v1.rs b/src/circuit/floor_planner/v1.rs index fd26e681df..d42d407d19 100644 --- a/src/circuit/floor_planner/v1.rs +++ b/src/circuit/floor_planner/v1.rs @@ -2,6 +2,7 @@ use std::fmt; use ff::Field; +use crate::rational::Rational; use crate::{ circuit::{ layouter::{RegionColumn, RegionLayouter, RegionShape, SyncDeps, TableLayouter}, @@ -9,8 +10,8 @@ use crate::{ Cell, Layouter, Region, RegionIndex, RegionStart, Table, Value, }, plonk::{ - Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, Error, Fixed, FloorPlanner, - Instance, Selector, TableColumn, + Advice, Any, Assignment, Challenge, Circuit, Column, Error, Fixed, FloorPlanner, Instance, + Selector, TableColumn, }, }; @@ -32,7 +33,7 @@ struct V1Plan<'a, F: Field, CS: Assignment + 'a> { /// Stores the starting row for each region. regions: Vec, /// Stores the constants to be assigned, and the cells to which they are copied. - constants: Vec<(Assigned, Cell)>, + constants: Vec<(Rational, Cell)>, /// Stores the table fixed columns. table_columns: Vec, } @@ -386,7 +387,7 @@ impl<'r, 'a, F: Field, CS: Assignment + SyncDeps> RegionLayouter for V1Reg annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result { self.plan.cs.assign_advice( annotation, @@ -407,7 +408,7 @@ impl<'r, 'a, F: Field, CS: Assignment + SyncDeps> RegionLayouter for V1Reg annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - constant: Assigned, + constant: Rational, ) -> Result { let advice = self.assign_advice(annotation, column, offset, &mut || Value::known(constant))?; @@ -451,7 +452,7 @@ impl<'r, 'a, F: Field, CS: Assignment + SyncDeps> RegionLayouter for V1Reg annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result { self.plan.cs.assign_fixed( annotation, @@ -467,7 +468,7 @@ impl<'r, 'a, F: Field, CS: Assignment + SyncDeps> RegionLayouter for V1Reg }) } - fn constrain_constant(&mut self, cell: Cell, constant: Assigned) -> Result<(), Error> { + fn constrain_constant(&mut self, cell: Cell, constant: Rational) -> Result<(), Error> { self.plan.constants.push((constant, cell)); Ok(()) } diff --git a/src/circuit/layouter.rs b/src/circuit/layouter.rs index f939c3fca5..ba1c4fb8a6 100644 --- a/src/circuit/layouter.rs +++ b/src/circuit/layouter.rs @@ -8,7 +8,8 @@ use ff::Field; pub use super::table_layouter::TableLayouter; use super::{Cell, RegionIndex, Value}; -use crate::plonk::{Advice, Any, Assigned, Column, Error, Fixed, Instance, Selector}; +use crate::plonk::{Advice, Any, Column, Error, Fixed, Instance, Selector}; +use crate::rational::Rational; /// Intermediate trait requirements for [`RegionLayouter`] when thread-safe regions are enabled. #[cfg(feature = "thread-safe-region")] @@ -79,7 +80,7 @@ pub trait RegionLayouter: fmt::Debug + SyncDeps { annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result; /// Assigns a constant value to the column `advice` at `offset` within this region. @@ -93,7 +94,7 @@ pub trait RegionLayouter: fmt::Debug + SyncDeps { annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - constant: Assigned, + constant: Rational, ) -> Result; /// Assign the value of the instance column's cell at absolute location @@ -120,13 +121,13 @@ pub trait RegionLayouter: fmt::Debug + SyncDeps { annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result; /// Constrains a cell to have a constant value. /// /// Returns an error if the cell is in a column where equality has not been enabled. - fn constrain_constant(&mut self, cell: Cell, constant: Assigned) -> Result<(), Error>; + fn constrain_constant(&mut self, cell: Cell, constant: Rational) -> Result<(), Error>; /// Constraint two cells to have the same value. /// @@ -226,7 +227,7 @@ impl RegionLayouter for RegionShape { _: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - _to: &'v mut (dyn FnMut() -> Value> + 'v), + _to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result { self.columns.insert(Column::::from(column).into()); self.row_count = cmp::max(self.row_count, offset + 1); @@ -243,7 +244,7 @@ impl RegionLayouter for RegionShape { annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - constant: Assigned, + constant: Rational, ) -> Result { // The rest is identical to witnessing an advice cell. self.assign_advice(annotation, column, offset, &mut || Value::known(constant)) @@ -283,7 +284,7 @@ impl RegionLayouter for RegionShape { _: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - _to: &'v mut (dyn FnMut() -> Value> + 'v), + _to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result { self.columns.insert(Column::::from(column).into()); self.row_count = cmp::max(self.row_count, offset + 1); @@ -303,7 +304,7 @@ impl RegionLayouter for RegionShape { // Do nothing } - fn constrain_constant(&mut self, _cell: Cell, _constant: Assigned) -> Result<(), Error> { + fn constrain_constant(&mut self, _cell: Cell, _constant: Rational) -> Result<(), Error> { // Global constants don't affect the region shape. Ok(()) } diff --git a/src/circuit/table_layouter.rs b/src/circuit/table_layouter.rs index 06338bb896..dc7aa94ef7 100644 --- a/src/circuit/table_layouter.rs +++ b/src/circuit/table_layouter.rs @@ -7,7 +7,8 @@ use std::{ use ff::Field; -use crate::plonk::{Assigned, Assignment, Error, TableColumn, TableError}; +use crate::plonk::{Assignment, Error, TableColumn, TableError}; +use crate::rational::Rational; use super::Value; @@ -25,7 +26,7 @@ pub trait TableLayouter: std::fmt::Debug { annotation: &'v (dyn Fn() -> String + 'v), column: TableColumn, offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result<(), Error>; } @@ -36,7 +37,7 @@ pub trait TableLayouter: std::fmt::Debug { /// assigned. /// - The inner `Value` tracks whether the underlying `Assignment` is evaluating /// witnesses or not. -type DefaultTableValue = Option>>; +type DefaultTableValue = Option>>; /// A table layouter that can be used to assign values to a table. pub struct SimpleTableLayouter<'r, 'a, F: Field, CS: Assignment + 'a> { @@ -74,7 +75,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> TableLayouter annotation: &'v (dyn Fn() -> String + 'v), column: TableColumn, offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result<(), Error> { if self.used_columns.contains(&column) { return Err(Error::TableError(TableError::UsedColumn(column))); diff --git a/src/circuit/value.rs b/src/circuit/value.rs index f3ea6a39ea..5c42a08ccd 100644 --- a/src/circuit/value.rs +++ b/src/circuit/value.rs @@ -3,7 +3,8 @@ use std::ops::{Add, Mul, Neg, Sub}; use group::ff::Field; -use crate::plonk::{Assigned, Error}; +use crate::plonk::Error; +use crate::rational::Rational; /// A value that might exist within a circuit. /// @@ -498,19 +499,19 @@ where } // -// Assigned +// Rational // -impl From> for Value> { +impl From> for Value> { fn from(value: Value) -> Self { Self { - inner: value.inner.map(Assigned::from), + inner: value.inner.map(Rational::from), } } } -impl Add> for Value> { - type Output = Value>; +impl Add> for Value> { + type Output = Value>; fn add(self, rhs: Value) -> Self::Output { Value { @@ -519,16 +520,16 @@ impl Add> for Value> { } } -impl Add for Value> { - type Output = Value>; +impl Add for Value> { + type Output = Value>; fn add(self, rhs: F) -> Self::Output { self + Value::known(rhs) } } -impl Add> for Value<&Assigned> { - type Output = Value>; +impl Add> for Value<&Rational> { + type Output = Value>; fn add(self, rhs: Value) -> Self::Output { Value { @@ -537,16 +538,16 @@ impl Add> for Value<&Assigned> { } } -impl Add for Value<&Assigned> { - type Output = Value>; +impl Add for Value<&Rational> { + type Output = Value>; fn add(self, rhs: F) -> Self::Output { self + Value::known(rhs) } } -impl Sub> for Value> { - type Output = Value>; +impl Sub> for Value> { + type Output = Value>; fn sub(self, rhs: Value) -> Self::Output { Value { @@ -555,16 +556,16 @@ impl Sub> for Value> { } } -impl Sub for Value> { - type Output = Value>; +impl Sub for Value> { + type Output = Value>; fn sub(self, rhs: F) -> Self::Output { self - Value::known(rhs) } } -impl Sub> for Value<&Assigned> { - type Output = Value>; +impl Sub> for Value<&Rational> { + type Output = Value>; fn sub(self, rhs: Value) -> Self::Output { Value { @@ -573,16 +574,16 @@ impl Sub> for Value<&Assigned> { } } -impl Sub for Value<&Assigned> { - type Output = Value>; +impl Sub for Value<&Rational> { + type Output = Value>; fn sub(self, rhs: F) -> Self::Output { self - Value::known(rhs) } } -impl Mul> for Value> { - type Output = Value>; +impl Mul> for Value> { + type Output = Value>; fn mul(self, rhs: Value) -> Self::Output { Value { @@ -591,16 +592,16 @@ impl Mul> for Value> { } } -impl Mul for Value> { - type Output = Value>; +impl Mul for Value> { + type Output = Value>; fn mul(self, rhs: F) -> Self::Output { self * Value::known(rhs) } } -impl Mul> for Value<&Assigned> { - type Output = Value>; +impl Mul> for Value<&Rational> { + type Output = Value>; fn mul(self, rhs: Value) -> Self::Output { Value { @@ -609,8 +610,8 @@ impl Mul> for Value<&Assigned> { } } -impl Mul for Value<&Assigned> { - type Output = Value>; +impl Mul for Value<&Rational> { + type Output = Value>; fn mul(self, rhs: F) -> Self::Output { self * Value::known(rhs) @@ -619,9 +620,9 @@ impl Mul for Value<&Assigned> { impl Value { /// Returns the field element corresponding to this value. - pub fn to_field(&self) -> Value> + pub fn to_field(&self) -> Value> where - for<'v> Assigned: From<&'v V>, + for<'v> Rational: From<&'v V>, { Value { inner: self.inner.as_ref().map(|v| v.into()), @@ -629,9 +630,9 @@ impl Value { } /// Returns the field element corresponding to this value. - pub fn into_field(self) -> Value> + pub fn into_field(self) -> Value> where - V: Into>, + V: Into>, { Value { inner: self.inner.map(|v| v.into()), @@ -642,18 +643,19 @@ impl Value { /// /// # Examples /// - /// If you have a `Value`, convert it to `Value>` first: + /// If you have a `Value`, convert it to `Value>` first: /// ``` /// # use halo2curves::pasta::pallas::Base as F; - /// use halo2_proofs::{circuit::Value, plonk::Assigned}; + /// use halo2_proofs::{circuit::Value}; + /// use halo2_proofs::rational::Rational; /// /// let v = Value::known(F::from(2)); - /// let v: Value> = v.into(); + /// let v: Value> = v.into(); /// v.double(); /// ``` - pub fn double(&self) -> Value> + pub fn double(&self) -> Value> where - V: Borrow>, + V: Borrow>, { Value { inner: self.inner.as_ref().map(|v| v.borrow().double()), @@ -661,9 +663,9 @@ impl Value { } /// Squares this field element. - pub fn square(&self) -> Value> + pub fn square(&self) -> Value> where - V: Borrow>, + V: Borrow>, { Value { inner: self.inner.as_ref().map(|v| v.borrow().square()), @@ -671,9 +673,9 @@ impl Value { } /// Cubes this field element. - pub fn cube(&self) -> Value> + pub fn cube(&self) -> Value> where - V: Borrow>, + V: Borrow>, { Value { inner: self.inner.as_ref().map(|v| v.borrow().cube()), @@ -681,9 +683,9 @@ impl Value { } /// Inverts this assigned value (taking the inverse of zero to be zero). - pub fn invert(&self) -> Value> + pub fn invert(&self) -> Value> where - V: Borrow>, + V: Borrow>, { Value { inner: self.inner.as_ref().map(|v| v.borrow().invert()), @@ -691,7 +693,7 @@ impl Value { } } -impl Value> { +impl Value> { /// Evaluates this value directly, performing an unbatched inversion if necessary. /// /// If the denominator is zero, the returned value is zero. diff --git a/src/dev.rs b/src/dev.rs index 15575bb8c3..c539b4f883 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -15,8 +15,8 @@ use crate::{ plonk::{ permutation, sealed::{self, SealedPhase}, - Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, - Expression, FirstPhase, Fixed, FloorPlanner, Instance, Phase, Selector, + Advice, Any, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, Expression, + FirstPhase, Fixed, FloorPlanner, Instance, Phase, Selector, }, }; @@ -47,6 +47,7 @@ pub use tfp::TracingFloorPlanner; #[cfg(feature = "dev-graph")] mod graph; +use crate::rational::Rational; #[cfg(feature = "dev-graph")] #[cfg_attr(docsrs, doc(cfg(feature = "dev-graph")))] pub use graph::{circuit_dot_graph, layout::CircuitLayout}; @@ -187,7 +188,7 @@ impl Mul for Value { /// plonk::{Advice, Any, Circuit, Column, ConstraintSystem, Error, Selector}, /// poly::Rotation, /// }; -/// use ff::PrimeField; +/// use ff::{PrimeField}; /// use halo2curves::pasta::Fp; /// const K: u32 = 5; /// @@ -449,7 +450,7 @@ impl Assignment for MockProver { ) -> Result<(), Error> where V: FnOnce() -> circuit::Value, - VR: Into>, + VR: Into>, A: FnOnce() -> AR, AR: Into, { @@ -501,7 +502,7 @@ impl Assignment for MockProver { ) -> Result<(), Error> where V: FnOnce() -> circuit::Value, - VR: Into>, + VR: Into>, A: FnOnce() -> AR, AR: Into, { @@ -563,7 +564,7 @@ impl Assignment for MockProver { &mut self, col: Column, from_row: usize, - to: circuit::Value>, + to: circuit::Value>, ) -> Result<(), Error> { if !self.in_phase(FirstPhase) { return Ok(()); diff --git a/src/dev/cost.rs b/src/dev/cost.rs index e75aba848e..3272315fc6 100644 --- a/src/dev/cost.rs +++ b/src/dev/cost.rs @@ -11,11 +11,12 @@ use std::{ use ff::{Field, PrimeField}; use group::prime::PrimeGroup; +use crate::rational::Rational; use crate::{ circuit::{layouter::RegionColumn, Value}, plonk::{ - Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, - Fixed, FloorPlanner, Instance, Selector, + Advice, Any, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, Fixed, + FloorPlanner, Instance, Selector, }, poly::Rotation, }; @@ -205,7 +206,7 @@ impl Assignment for Layout { ) -> Result<(), Error> where V: FnOnce() -> Value, - VR: Into>, + VR: Into>, A: FnOnce() -> AR, AR: Into, { @@ -222,7 +223,7 @@ impl Assignment for Layout { ) -> Result<(), Error> where V: FnOnce() -> Value, - VR: Into>, + VR: Into>, A: FnOnce() -> AR, AR: Into, { @@ -245,7 +246,7 @@ impl Assignment for Layout { &mut self, _: Column, _: usize, - _: Value>, + _: Value>, ) -> Result<(), Error> { Ok(()) } diff --git a/src/dev/tfp.rs b/src/dev/tfp.rs index 011ba3cac0..804b70dd82 100644 --- a/src/dev/tfp.rs +++ b/src/dev/tfp.rs @@ -3,14 +3,15 @@ use std::{fmt, marker::PhantomData}; use ff::Field; use tracing::{debug, debug_span, span::EnteredSpan}; +use crate::rational::Rational; use crate::{ circuit::{ layouter::{RegionLayouter, SyncDeps}, AssignedCell, Cell, Layouter, Region, Table, Value, }, plonk::{ - Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, - Fixed, FloorPlanner, Instance, Selector, + Advice, Any, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, Fixed, + FloorPlanner, Instance, Selector, }, }; @@ -258,7 +259,7 @@ impl<'r, F: Field> RegionLayouter for TracingRegion<'r, F> { annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result { let _guard = debug_span!("assign_advice", name = annotation(), column = ?column, offset = offset) @@ -274,7 +275,7 @@ impl<'r, F: Field> RegionLayouter for TracingRegion<'r, F> { annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - constant: Assigned, + constant: Rational, ) -> Result { let _guard = debug_span!("assign_advice_from_constant", name = annotation(), @@ -329,7 +330,7 @@ impl<'r, F: Field> RegionLayouter for TracingRegion<'r, F> { annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result { let _guard = debug_span!("assign_fixed", name = annotation(), column = ?column, offset = offset) @@ -340,7 +341,7 @@ impl<'r, F: Field> RegionLayouter for TracingRegion<'r, F> { .map(debug_value_and_return_cell) } - fn constrain_constant(&mut self, cell: Cell, constant: Assigned) -> Result<(), Error> { + fn constrain_constant(&mut self, cell: Cell, constant: Rational) -> Result<(), Error> { debug!(target: "constrain_constant", cell = ?cell, constant = ?constant); self.0.constrain_constant(cell, constant) } @@ -424,7 +425,7 @@ impl<'cs, F: Field, CS: Assignment> Assignment for TracingAssignment<'cs, ) -> Result<(), Error> where V: FnOnce() -> Value, - VR: Into>, + VR: Into>, A: FnOnce() -> AR, AR: Into, { @@ -446,7 +447,7 @@ impl<'cs, F: Field, CS: Assignment> Assignment for TracingAssignment<'cs, ) -> Result<(), Error> where V: FnOnce() -> Value, - VR: Into>, + VR: Into>, A: FnOnce() -> AR, AR: Into, { @@ -481,7 +482,7 @@ impl<'cs, F: Field, CS: Assignment> Assignment for TracingAssignment<'cs, &mut self, column: Column, row: usize, - to: Value>, + to: Value>, ) -> Result<(), Error> { let _guard = debug_span!("positioned").entered(); debug!(target: "fill_from_row", column = ?column, row = row); diff --git a/src/helpers.rs b/src/helpers.rs index a4820b1b27..5abd0fbe07 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -23,6 +23,14 @@ pub enum SerdeFormat { RawBytesUnchecked, } +/// Byte length of an affine curve element according to `format`. +pub fn byte_length(format: SerdeFormat) -> usize { + match format { + SerdeFormat::Processed => T::default().to_raw_bytes().len(), + _ => T::default().to_raw_bytes().len() * 2, + } +} + // Keep this trait for compatibility with IPA serialization pub(crate) trait CurveRead: PrimeCurveAffine { /// Reads a compressed element from the buffer and attempts to parse it diff --git a/src/lib.rs b/src/lib.rs index cc536148cf..d4fa50517b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,15 +9,15 @@ #![deny(unsafe_code)] pub mod arithmetic; -// pub mod circuit; +pub mod circuit; pub use halo2curves; mod multicore; -// pub mod plonk; +pub mod plonk; pub mod poly; pub mod transcript; pub mod rational; -// pub mod dev; +pub mod dev; pub mod helpers; pub use helpers::SerdeFormat; diff --git a/src/plonk.rs b/src/plonk.rs index 9a6aa37827..2e0df76315 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -6,21 +6,19 @@ //! [plonk]: https://eprint.iacr.org/2019/953 use blake2b_simd::Params as Blake2bParams; -use group::ff::{Field, FromUniformBytes, PrimeField}; +use group::ff::FromUniformBytes; -use crate::arithmetic::CurveAffine; use crate::helpers::{ - polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, SerdeCurveAffine, + byte_length, polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, SerdePrimeField, }; use crate::poly::{ Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, PinnedEvaluationDomain, Polynomial, }; -use crate::transcript::{ChallengeScalar, EncodedChallenge, Transcript}; +use crate::transcript::{Hashable, Transcript}; use crate::SerdeFormat; -mod assigned; mod circuit; mod error; mod evaluation; @@ -32,37 +30,41 @@ mod vanishing; mod prover; mod verifier; -pub use assigned::*; pub use circuit::*; pub use error::*; pub use keygen::*; pub use prover::*; pub use verifier::*; +use crate::poly::commitment::PolynomialCommitmentScheme; use evaluation::Evaluator; +use ff::{PrimeField, WithSmallOrderMulGroup}; +use halo2curves::serde::SerdeObject; use std::io; /// This is a verifying key which allows for the verification of proofs for a /// particular circuit. #[derive(Clone, Debug)] -pub struct VerifyingKey { - domain: EvaluationDomain, - fixed_commitments: Vec, - permutation: permutation::VerifyingKey, - cs: ConstraintSystem, +pub struct VerifyingKey> { + domain: EvaluationDomain, + fixed_commitments: Vec, + permutation: permutation::VerifyingKey, + cs: ConstraintSystem, /// Cached maximum degree of `cs` (which doesn't change after construction). cs_degree: usize, /// The representative of this `VerifyingKey` in transcripts. - transcript_repr: C::Scalar, + transcript_repr: F, selectors: Vec>, } // Current version of the VK const VERSION: u8 = 0x03; -impl VerifyingKey +impl VerifyingKey where - C::Scalar: SerdePrimeField + FromUniformBytes<64>, + F: WithSmallOrderMulGroup<3> + SerdePrimeField + FromUniformBytes<64> + SerdeObject, + CS: PolynomialCommitmentScheme, + CS::Commitment: SerdeObject, { /// Writes a verifying key to a buffer. /// @@ -77,12 +79,13 @@ where // Version byte that will be checked on read. writer.write_all(&[VERSION])?; let k = &self.domain.k(); - assert!(*k <= C::Scalar::S); + assert!(*k <= F::S); // k value fits in 1 byte writer.write_all(&[*k as u8])?; writer.write_all(&(self.fixed_commitments.len() as u32).to_le_bytes())?; for commitment in &self.fixed_commitments { - commitment.write(writer, format)?; + // TODO: writting raw here - do we maybe want a wrapper like we had? + commitment.write_raw(writer)?; } self.permutation.write(writer, format)?; @@ -106,7 +109,7 @@ where /// Checks that field elements are less than modulus, and then checks that the point is on the curve. /// - `RawBytesUnchecked`: Reads an uncompressed curve element with coordinates in Montgomery form; /// does not perform any checks - pub fn read>( + pub fn read>( reader: &mut R, format: SerdeFormat, #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, @@ -123,18 +126,14 @@ where let mut k = [0u8; 1]; reader.read_exact(&mut k)?; let k = u8::from_le_bytes(k); - if k as u32 > C::Scalar::S { + if k as u32 > F::S { return Err(io::Error::new( io::ErrorKind::InvalidData, - format!( - "circuit size value (k): {} exceeds maxium: {}", - k, - C::Scalar::S - ), + format!("circuit size value (k): {} exceeds maxium: {}", k, F::S), )); } - let (domain, cs, _) = keygen::create_domain::( + let (domain, cs, _) = keygen::create_domain::( k as u32, #[cfg(feature = "circuit-params")] params, @@ -144,7 +143,8 @@ where let num_fixed_columns = u32::from_le_bytes(num_fixed_columns); let fixed_commitments: Vec<_> = (0..num_fixed_columns) - .map(|_| C::read(reader, format)) + // TODO: Fix FORMAT - wrapper like before? + .map(|_| CS::Commitment::read_raw(reader)) .collect::>()?; let permutation = permutation::VerifyingKey::read(reader, &cs.permutation, format)?; @@ -171,7 +171,7 @@ where } /// Reads a verification key from a slice of bytes using [`Self::read`]. - pub fn from_bytes>( + pub fn from_bytes>( mut bytes: &[u8], format: SerdeFormat, #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, @@ -185,12 +185,9 @@ where } } -impl VerifyingKey { - fn bytes_length(&self, format: SerdeFormat) -> usize - where - C: SerdeCurveAffine, - { - 10 + (self.fixed_commitments.len() * C::byte_length(format)) +impl, CS: PolynomialCommitmentScheme> VerifyingKey { + fn bytes_length(&self, format: SerdeFormat) -> usize { + 10 + (self.fixed_commitments.len() * byte_length::(format)) + self.permutation.bytes_length(format) + self.selectors.len() * (self @@ -201,14 +198,14 @@ impl VerifyingKey { } fn from_parts( - domain: EvaluationDomain, - fixed_commitments: Vec, - permutation: permutation::VerifyingKey, - cs: ConstraintSystem, + domain: EvaluationDomain, + fixed_commitments: Vec, + permutation: permutation::VerifyingKey, + cs: ConstraintSystem, selectors: Vec>, ) -> Self where - C::ScalarExt: FromUniformBytes<64>, + F: FromUniformBytes<64>, { // Compute cached values. let cs_degree = cs.degree(); @@ -220,7 +217,7 @@ impl VerifyingKey { cs, cs_degree, // Temporary, this is not pinned. - transcript_repr: C::Scalar::ZERO, + transcript_repr: F::ZERO, selectors, }; @@ -235,27 +232,25 @@ impl VerifyingKey { hasher.update(s.as_bytes()); // Hash in final Blake2bState - vk.transcript_repr = C::Scalar::from_uniform_bytes(hasher.finalize().as_array()); + vk.transcript_repr = F::from_uniform_bytes(hasher.finalize().as_array()); vk } /// Hashes a verification key into a transcript. - pub fn hash_into, T: Transcript>( - &self, - transcript: &mut T, - ) -> io::Result<()> { - transcript.common_scalar(self.transcript_repr)?; + pub fn hash_into(&self, transcript: &mut T) -> io::Result<()> + where + F: Hashable, + { + transcript.common(&self.transcript_repr)?; Ok(()) } /// Obtains a pinned representation of this verification key that contains /// the minimal information necessary to reconstruct the verification key. - pub fn pinned(&self) -> PinnedVerificationKey<'_, C> { + pub fn pinned(&self) -> PinnedVerificationKey<'_, F, CS> { PinnedVerificationKey { - base_modulus: C::Base::MODULUS, - scalar_modulus: C::Scalar::MODULUS, domain: self.domain.pinned(), fixed_commitments: &self.fixed_commitments, permutation: &self.permutation, @@ -264,22 +259,22 @@ impl VerifyingKey { } /// Returns commitments of fixed polynomials - pub fn fixed_commitments(&self) -> &Vec { + pub fn fixed_commitments(&self) -> &Vec { &self.fixed_commitments } /// Returns `VerifyingKey` of permutation - pub fn permutation(&self) -> &permutation::VerifyingKey { + pub fn permutation(&self) -> &permutation::VerifyingKey { &self.permutation } /// Returns `ConstraintSystem` - pub fn cs(&self) -> &ConstraintSystem { + pub fn cs(&self) -> &ConstraintSystem { &self.cs } /// Returns representative of this `VerifyingKey` in transcripts - pub fn transcript_repr(&self) -> C::Scalar { + pub fn transcript_repr(&self) -> F { self.transcript_repr } } @@ -288,44 +283,39 @@ impl VerifyingKey { /// its active contents. #[allow(dead_code)] #[derive(Debug)] -pub struct PinnedVerificationKey<'a, C: CurveAffine> { - base_modulus: &'static str, - scalar_modulus: &'static str, - domain: PinnedEvaluationDomain<'a, C::Scalar>, - cs: PinnedConstraintSystem<'a, C::Scalar>, - fixed_commitments: &'a Vec, - permutation: &'a permutation::VerifyingKey, +pub struct PinnedVerificationKey<'a, F: PrimeField, CS: PolynomialCommitmentScheme> { + domain: PinnedEvaluationDomain<'a, F>, + cs: PinnedConstraintSystem<'a, F>, + fixed_commitments: &'a Vec, + permutation: &'a permutation::VerifyingKey, } /// This is a proving key which allows for the creation of proofs for a /// particular circuit. #[derive(Clone, Debug)] -pub struct ProvingKey { - vk: VerifyingKey, - l0: Polynomial, - l_last: Polynomial, - l_active_row: Polynomial, - fixed_values: Vec>, - fixed_polys: Vec>, - fixed_cosets: Vec>, - permutation: permutation::ProvingKey, - ev: Evaluator, +pub struct ProvingKey> { + vk: VerifyingKey, + l0: Polynomial, + l_last: Polynomial, + l_active_row: Polynomial, + fixed_values: Vec>, + fixed_polys: Vec>, + fixed_cosets: Vec>, + permutation: permutation::ProvingKey, + ev: Evaluator, } -impl ProvingKey +impl, CS: PolynomialCommitmentScheme> ProvingKey where - C::Scalar: FromUniformBytes<64>, + F: FromUniformBytes<64>, { /// Get the underlying [`VerifyingKey`]. - pub fn get_vk(&self) -> &VerifyingKey { + pub fn get_vk(&self) -> &VerifyingKey { &self.vk } /// Gets the total number of bytes in the serialization of `self` - fn bytes_length(&self, format: SerdeFormat) -> usize - where - C: SerdeCurveAffine, - { - let scalar_len = C::Scalar::default().to_repr().as_ref().len(); + fn bytes_length(&self, format: SerdeFormat) -> usize { + let scalar_len = F::default().to_repr().as_ref().len(); self.vk.bytes_length(format) + 12 + scalar_len * (self.l0.len() + self.l_last.len() + self.l_active_row.len()) @@ -336,9 +326,9 @@ where } } -impl ProvingKey +impl, CS: PolynomialCommitmentScheme> ProvingKey where - C::Scalar: SerdePrimeField + FromUniformBytes<64>, + F: SerdePrimeField + FromUniformBytes<64>, { /// Writes a proving key to a buffer. /// @@ -373,12 +363,12 @@ where /// Checks that field elements are less than modulus, and then checks that the point is on the curve. /// - `RawBytesUnchecked`: Reads an uncompressed curve element with coordinates in Montgomery form; /// does not perform any checks - pub fn read>( + pub fn read>( reader: &mut R, format: SerdeFormat, #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, ) -> io::Result { - let vk = VerifyingKey::::read::( + let vk = VerifyingKey::::read::( reader, format, #[cfg(feature = "circuit-params")] @@ -413,7 +403,7 @@ where } /// Reads a proving key from a slice of bytes using [`Self::read`]. - pub fn from_bytes>( + pub fn from_bytes>( mut bytes: &[u8], format: SerdeFormat, #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, @@ -427,29 +417,9 @@ where } } -impl VerifyingKey { +impl> VerifyingKey { /// Get the underlying [`EvaluationDomain`]. - pub fn get_domain(&self) -> &EvaluationDomain { + pub fn get_domain(&self) -> &EvaluationDomain { &self.domain } } - -#[derive(Clone, Copy, Debug)] -struct Theta; -type ChallengeTheta = ChallengeScalar; - -#[derive(Clone, Copy, Debug)] -struct Beta; -type ChallengeBeta = ChallengeScalar; - -#[derive(Clone, Copy, Debug)] -struct Gamma; -type ChallengeGamma = ChallengeScalar; - -#[derive(Clone, Copy, Debug)] -struct Y; -type ChallengeY = ChallengeScalar; - -#[derive(Clone, Copy, Debug)] -struct X; -type ChallengeX = ChallengeScalar; diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index 7b9e1f4cff..3fc414d2e9 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -1,6 +1,7 @@ -use super::{lookup, permutation, Assigned, Error}; +use super::{lookup, permutation, Error}; use crate::circuit::layouter::SyncDeps; use crate::dev::metadata; +use crate::rational::Rational; use crate::{ circuit::{Layouter, Region, Value}, poly::Rotation, @@ -663,7 +664,7 @@ pub trait Assignment { ) -> Result<(), Error> where V: FnOnce() -> Value, - VR: Into>, + VR: Into>, A: FnOnce() -> AR, AR: Into; @@ -677,7 +678,7 @@ pub trait Assignment { ) -> Result<(), Error> where V: FnOnce() -> Value, - VR: Into>, + VR: Into>, A: FnOnce() -> AR, AR: Into; @@ -695,7 +696,7 @@ pub trait Assignment { &mut self, column: Column, row: usize, - to: Value>, + to: Value>, ) -> Result<(), Error>; /// Queries the value of the given challenge. diff --git a/src/plonk/evaluation.rs b/src/plonk/evaluation.rs index b32547a2f9..fe12c42e37 100644 --- a/src/plonk/evaluation.rs +++ b/src/plonk/evaluation.rs @@ -1,11 +1,13 @@ use crate::multicore; use crate::plonk::{lookup, permutation, Any, ProvingKey}; +use crate::poly::commitment::PolynomialCommitmentScheme; use crate::poly::Basis; use crate::{ - arithmetic::{parallelize, CurveAffine}, + arithmetic::parallelize, poly::{Coeff, ExtendedLagrangeCoeff, Polynomial, Rotation}, }; -use group::ff::{Field, PrimeField, WithSmallOrderMulGroup}; +use ff::{PrimeField, WithSmallOrderMulGroup}; +use group::ff::Field; use super::{ConstraintSystem, Expression}; @@ -164,20 +166,20 @@ impl Calculation { /// Evaluator #[derive(Clone, Default, Debug)] -pub struct Evaluator { +pub struct Evaluator { /// Custom gates evalution - pub custom_gates: GraphEvaluator, + pub custom_gates: GraphEvaluator, /// Lookups evalution - pub lookups: Vec>, + pub lookups: Vec>, /// Shuffle evalution - pub shuffles: Vec>, + pub shuffles: Vec>, } /// GraphEvaluator #[derive(Clone, Debug)] -pub struct GraphEvaluator { +pub struct GraphEvaluator { /// Constants - pub constants: Vec, + pub constants: Vec, /// Rotations pub rotations: Vec, /// Calculations @@ -188,9 +190,9 @@ pub struct GraphEvaluator { /// EvaluationData #[derive(Default, Debug)] -pub struct EvaluationData { +pub struct EvaluationData { /// Intermediates - pub intermediates: Vec, + pub intermediates: Vec, /// Rotations pub rotations: Vec, } @@ -204,9 +206,9 @@ pub struct CalculationInfo { pub target: usize, } -impl Evaluator { +impl> Evaluator { /// Creates a new evaluation structure - pub fn new(cs: &ConstraintSystem) -> Self { + pub fn new(cs: &ConstraintSystem) -> Self { let mut ev = Evaluator::default(); // Custom gates @@ -263,33 +265,33 @@ impl Evaluator { /// Evaluate h poly #[allow(clippy::too_many_arguments)] - pub(in crate::plonk) fn evaluate_h( + pub(in crate::plonk) fn evaluate_h>( &self, - pk: &ProvingKey, - advice_polys: &[&[Polynomial]], - instance_polys: &[&[Polynomial]], - challenges: &[C::ScalarExt], - y: C::ScalarExt, - beta: C::ScalarExt, - gamma: C::ScalarExt, - theta: C::ScalarExt, - lookups: &[Vec>], - permutations: &[permutation::prover::Committed], - ) -> Polynomial { + pk: &ProvingKey, + advice_polys: &[&[Polynomial]], + instance_polys: &[&[Polynomial]], + challenges: &[F], + y: F, + beta: F, + gamma: F, + theta: F, + lookups: &[Vec>], + permutations: &[permutation::prover::Committed], + ) -> Polynomial { let domain = &pk.vk.domain; let size = domain.extended_len(); let rot_scale = 1 << (domain.extended_k() - domain.k()); let fixed = &pk.fixed_cosets[..]; let extended_omega = domain.get_extended_omega(); let isize = size as i32; - let one = C::ScalarExt::ONE; + let one = F::ONE; let l0 = &pk.l0; let l_last = &pk.l_last; let l_active_row = &pk.l_active_row; let p = &pk.vk.cs.permutation; // Calculate the advice and instance cosets - let advice: Vec>> = advice_polys + let advice: Vec>> = advice_polys .iter() .map(|advice_polys| { advice_polys @@ -298,7 +300,7 @@ impl Evaluator { .collect() }) .collect(); - let instance: Vec>> = instance_polys + let instance: Vec>> = instance_polys .iter() .map(|instance_polys| { instance_polys @@ -353,7 +355,7 @@ impl Evaluator { let blinding_factors = pk.vk.cs.blinding_factors(); let last_rotation = Rotation(-((blinding_factors + 1) as i32)); let chunk_len = pk.vk.cs.degree() - 2; - let delta_start = beta * &C::Scalar::ZETA; + let delta_start = beta * &pk.vk.domain.g_coset; let first_set = sets.first().unwrap(); let last_set = sets.last().unwrap(); @@ -419,7 +421,7 @@ impl Evaluator { Any::Instance => &instance[column.index()], }) { right *= values[idx] + current_delta + gamma; - current_delta *= &C::Scalar::DELTA; + current_delta *= &F::DELTA; } *value = *value * y + ((left - right) * l_active_row[idx]); @@ -461,7 +463,7 @@ impl Evaluator { &gamma, &theta, &y, - &C::ScalarExt::ZERO, + &F::ZERO, idx, rot_scale, isize, @@ -508,15 +510,11 @@ impl Evaluator { } } -impl Default for GraphEvaluator { +impl Default for GraphEvaluator { fn default() -> Self { Self { // Fixed positions to allow easy access - constants: vec![ - C::ScalarExt::ZERO, - C::ScalarExt::ONE, - C::ScalarExt::from(2u64), - ], + constants: vec![F::ZERO, F::ONE, F::from(2u64)], rotations: Vec::new(), calculations: Vec::new(), num_intermediates: 0, @@ -524,7 +522,7 @@ impl Default for GraphEvaluator { } } -impl GraphEvaluator { +impl GraphEvaluator { /// Adds a rotation fn add_rotation(&mut self, rotation: &Rotation) -> usize { let position = self.rotations.iter().position(|&c| c == rotation.0); @@ -538,7 +536,7 @@ impl GraphEvaluator { } /// Adds a constant - fn add_constant(&mut self, constant: &C::ScalarExt) -> ValueSource { + fn add_constant(&mut self, constant: &F) -> ValueSource { let position = self.constants.iter().position(|&c| c == *constant); ValueSource::Constant(match position { Some(pos) => pos, @@ -573,7 +571,7 @@ impl GraphEvaluator { } /// Generates an optimized evaluation for the expression - fn add_expression(&mut self, expr: &Expression) -> ValueSource { + fn add_expression(&mut self, expr: &Expression) -> ValueSource { match expr { Expression::Constant(scalar) => self.add_constant(scalar), Expression::Selector(_selector) => unreachable!(), @@ -662,9 +660,9 @@ impl GraphEvaluator { } } Expression::Scaled(a, f) => { - if *f == C::ScalarExt::ZERO { + if *f == F::ZERO { ValueSource::Constant(0) - } else if *f == C::ScalarExt::ONE { + } else if *f == F::ONE { self.add_expression(a) } else { let cst = self.add_constant(f); @@ -676,9 +674,9 @@ impl GraphEvaluator { } /// Creates a new evaluation structure - pub fn instance(&self) -> EvaluationData { + pub fn instance(&self) -> EvaluationData { EvaluationData { - intermediates: vec![C::ScalarExt::ZERO; self.num_intermediates], + intermediates: vec![F::ZERO; self.num_intermediates], rotations: vec![0usize; self.rotations.len()], } } @@ -686,20 +684,20 @@ impl GraphEvaluator { #[allow(clippy::too_many_arguments)] pub fn evaluate( &self, - data: &mut EvaluationData, - fixed: &[Polynomial], - advice: &[Polynomial], - instance: &[Polynomial], - challenges: &[C::ScalarExt], - beta: &C::ScalarExt, - gamma: &C::ScalarExt, - theta: &C::ScalarExt, - y: &C::ScalarExt, - previous_value: &C::ScalarExt, + data: &mut EvaluationData, + fixed: &[Polynomial], + advice: &[Polynomial], + instance: &[Polynomial], + challenges: &[F], + beta: &F, + gamma: &F, + theta: &F, + y: &F, + previous_value: &F, idx: usize, rot_scale: i32, isize: i32, - ) -> C::ScalarExt { + ) -> F { // All rotation index values for (rot_idx, rot) in self.rotations.iter().enumerate() { data.rotations[rot_idx] = get_rotation_idx(idx, *rot, rot_scale, isize); @@ -727,7 +725,7 @@ impl GraphEvaluator { if let Some(calc) = self.calculations.last() { data.intermediates[calc.target] } else { - C::ScalarExt::ZERO + F::ZERO } } } diff --git a/src/plonk/keygen.rs b/src/plonk/keygen.rs index eb8bc5ba3d..7c66f0ede6 100644 --- a/src/plonk/keygen.rs +++ b/src/plonk/keygen.rs @@ -2,8 +2,7 @@ use std::ops::Range; -use ff::{Field, FromUniformBytes}; -use group::Curve; +use ff::{Field, FromUniformBytes, WithSmallOrderMulGroup}; use super::{ circuit::{ @@ -11,29 +10,30 @@ use super::{ Selector, }, evaluation::Evaluator, - permutation, Assigned, Challenge, Error, LagrangeCoeff, Polynomial, ProvingKey, VerifyingKey, + permutation, Challenge, Error, LagrangeCoeff, Polynomial, ProvingKey, VerifyingKey, }; +use crate::circuit::Value; +use crate::poly::batch_invert_rational; +use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; +use crate::rational::Rational; use crate::{ - arithmetic::{parallelize, CurveAffine}, - circuit::Value, - poly::{ - batch_invert_assigned, - commitment::{Blind, Params}, - EvaluationDomain, - }, + arithmetic::parallelize, + // circuit::Value, + poly::EvaluationDomain, }; +// use crate::poly::kzg::commitment::{ParamsKZG, ParamsVerifierKZG}; -pub(crate) fn create_domain( +pub(crate) fn create_domain( k: u32, #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, ) -> ( - EvaluationDomain, - ConstraintSystem, + EvaluationDomain, + ConstraintSystem, ConcreteCircuit::Config, ) where - C: CurveAffine, - ConcreteCircuit: Circuit, + F: WithSmallOrderMulGroup<3>, + ConcreteCircuit: Circuit, { let mut cs = ConstraintSystem::default(); #[cfg(feature = "circuit-params")] @@ -52,7 +52,7 @@ where #[derive(Debug)] struct Assembly { k: u32, - fixed: Vec, LagrangeCoeff>>, + fixed: Vec, LagrangeCoeff>>, permutation: permutation::keygen::Assembly, selectors: Vec>, // A range of available rows for assignment and copies. @@ -105,7 +105,7 @@ impl Assignment for Assembly { ) -> Result<(), Error> where V: FnOnce() -> Value, - VR: Into>, + VR: Into>, A: FnOnce() -> AR, AR: Into, { @@ -122,7 +122,7 @@ impl Assignment for Assembly { ) -> Result<(), Error> where V: FnOnce() -> Value, - VR: Into>, + VR: Into>, A: FnOnce() -> AR, AR: Into, { @@ -158,7 +158,7 @@ impl Assignment for Assembly { &mut self, column: Column, from_row: usize, - to: Value>, + to: Value>, ) -> Result<(), Error> { if !self.usable_rows.contains(&from_row) { return Err(Error::not_enough_rows_available(self.k)); @@ -204,33 +204,16 @@ impl Assignment for Assembly { /// Generate a `VerifyingKey` from an instance of `Circuit`. /// By default, selector compression is turned **off**. -pub fn keygen_vk<'params, C, P, ConcreteCircuit>( - params: &P, - circuit: &ConcreteCircuit, -) -> Result, Error> -where - C: CurveAffine, - P: Params<'params, C>, - ConcreteCircuit: Circuit, - C::Scalar: FromUniformBytes<64>, -{ - keygen_vk_custom(params, circuit) -} - -/// Generate a `VerifyingKey` from an instance of `Circuit`. -/// -/// The selector compression optimization is turned on only if `compress_selectors` is `true`. -pub fn keygen_vk_custom<'params, C, P, ConcreteCircuit>( - params: &P, +pub fn keygen_vk( + params: &CS::VerifierParameters, circuit: &ConcreteCircuit, -) -> Result, Error> +) -> Result, Error> where - C: CurveAffine, - P: Params<'params, C>, - ConcreteCircuit: Circuit, - C::Scalar: FromUniformBytes<64>, + F: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + CS: PolynomialCommitmentScheme, + ConcreteCircuit: Circuit, { - let (domain, cs, config) = create_domain::( + let (domain, cs, config) = create_domain::( params.k(), #[cfg(feature = "circuit-params")] circuit.params(), @@ -240,9 +223,9 @@ where return Err(Error::not_enough_rows_available(params.k())); } - let mut assembly: Assembly = Assembly { + let mut assembly: Assembly = Assembly { k: params.k(), - fixed: vec![domain.empty_lagrange_assigned(); cs.num_fixed_columns], + fixed: vec![domain.empty_lagrange_rational(); cs.num_fixed_columns], permutation: permutation::keygen::Assembly::new(params.n() as usize, &cs.permutation), selectors: vec![vec![false; params.n() as usize]; cs.num_selectors], usable_rows: 0..params.n() as usize - (cs.blinding_factors() + 1), @@ -257,7 +240,7 @@ where cs.constants.clone(), )?; - let mut fixed = batch_invert_assigned(assembly.fixed); + let mut fixed = batch_invert_rational(assembly.fixed); // After this, the ConstraintSystem should not have any selectors: `verify` does not need them, and `keygen_pk` regenerates `cs` from scratch anyways. let selectors = std::mem::take(&mut assembly.selectors); let (cs, selector_polys) = cs.directly_convert_selectors_to_fixed(selectors); @@ -273,7 +256,7 @@ where let fixed_commitments = fixed .iter() - .map(|poly| params.commit_lagrange(poly, Blind::default()).to_affine()) + .map(|poly| params.commit_lagrange(poly)) .collect(); Ok(VerifyingKey::from_parts( @@ -286,15 +269,15 @@ where } /// Generate a `ProvingKey` from a `VerifyingKey` and an instance of `Circuit`. -pub fn keygen_pk<'params, C, P, ConcreteCircuit>( - params: &P, - vk: VerifyingKey, +pub fn keygen_pk( + params: &CS::Parameters, + vk: VerifyingKey, circuit: &ConcreteCircuit, -) -> Result, Error> +) -> Result, Error> where - C: CurveAffine, - P: Params<'params, C>, - ConcreteCircuit: Circuit, + F: WithSmallOrderMulGroup<3>, + CS: PolynomialCommitmentScheme, + ConcreteCircuit: Circuit, { let mut cs = ConstraintSystem::default(); #[cfg(feature = "circuit-params")] @@ -308,9 +291,9 @@ where return Err(Error::not_enough_rows_available(params.k())); } - let mut assembly: Assembly = Assembly { + let mut assembly: Assembly = Assembly { k: params.k(), - fixed: vec![vk.domain.empty_lagrange_assigned(); cs.num_fixed_columns], + fixed: vec![vk.domain.empty_lagrange_rational(); cs.num_fixed_columns], permutation: permutation::keygen::Assembly::new(params.n() as usize, &cs.permutation), selectors: vec![vec![false; params.n() as usize]; cs.num_selectors], usable_rows: 0..params.n() as usize - (cs.blinding_factors() + 1), @@ -325,7 +308,7 @@ where cs.constants.clone(), )?; - let mut fixed = batch_invert_assigned(assembly.fixed); + let mut fixed = batch_invert_rational(assembly.fixed); let (cs, selector_polys) = cs.directly_convert_selectors_to_fixed(assembly.selectors); fixed.extend( selector_polys @@ -343,14 +326,15 @@ where .map(|poly| vk.domain.coeff_to_extended(poly.clone())) .collect(); - let permutation_pk = assembly - .permutation - .build_pk(params, &vk.domain, &cs.permutation); + let permutation_pk = + assembly + .permutation + .build_pk::(params, &vk.domain, &cs.permutation); // Compute l_0(X) // TODO: this can be done more efficiently let mut l0 = vk.domain.empty_lagrange(); - l0[0] = C::Scalar::ONE; + l0[0] = F::ONE; let l0 = vk.domain.lagrange_to_coeff(l0); let l0 = vk.domain.coeff_to_extended(l0); @@ -358,7 +342,7 @@ where // and 0 otherwise over the domain. let mut l_blind = vk.domain.empty_lagrange(); for evaluation in l_blind[..].iter_mut().rev().take(cs.blinding_factors()) { - *evaluation = C::Scalar::ONE; + *evaluation = F::ONE; } let l_blind = vk.domain.lagrange_to_coeff(l_blind); let l_blind = vk.domain.coeff_to_extended(l_blind); @@ -366,12 +350,12 @@ where // Compute l_last(X) which evaluates to 1 on the first inactive row (just // before the blinding factors) and 0 otherwise over the domain let mut l_last = vk.domain.empty_lagrange(); - l_last[params.n() as usize - cs.blinding_factors() - 1] = C::Scalar::ONE; + l_last[params.n() as usize - cs.blinding_factors() - 1] = F::ONE; let l_last = vk.domain.lagrange_to_coeff(l_last); let l_last = vk.domain.coeff_to_extended(l_last); // Compute l_active_row(X) - let one = C::Scalar::ONE; + let one = F::ONE; let mut l_active_row = vk.domain.empty_extended(); parallelize(&mut l_active_row, |values, start| { for (i, value) in values.iter_mut().enumerate() { diff --git a/src/plonk/lookup/prover.rs b/src/plonk/lookup/prover.rs index 9e5005a4b7..82f34e4bb1 100644 --- a/src/plonk/lookup/prover.rs +++ b/src/plonk/lookup/prover.rs @@ -1,51 +1,40 @@ -use super::super::{ - circuit::Expression, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, Error, - ProvingKey, -}; +use super::super::{circuit::Expression, Error, ProvingKey}; use super::Argument; use crate::plonk::evaluation::evaluate; +use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; +use crate::transcript::{Hashable, Transcript}; use crate::{ - arithmetic::{eval_polynomial, parallelize, CurveAffine}, - poly::{ - commitment::{Blind, Params}, - Coeff, EvaluationDomain, LagrangeCoeff, Polynomial, ProverQuery, Rotation, - }, - transcript::{EncodedChallenge, TranscriptWrite}, -}; -use ff::WithSmallOrderMulGroup; -use group::{ - ff::{BatchInvert, Field}, - Curve, -}; -use rand_core::RngCore; -use std::{ - collections::BTreeMap, - iter, - ops::{Mul, MulAssign}, + arithmetic::{eval_polynomial, parallelize}, + poly::{Coeff, EvaluationDomain, LagrangeCoeff, Polynomial, ProverQuery, Rotation}, }; +use ff::{PrimeField, WithSmallOrderMulGroup}; +use group::ff::BatchInvert; +use halo2curves::serde::SerdeObject; +use rand_core::{CryptoRng, RngCore}; +use std::{collections::BTreeMap, iter}; #[derive(Debug)] -pub(in crate::plonk) struct Permuted { - compressed_input_expression: Polynomial, - permuted_input_expression: Polynomial, - permuted_input_poly: Polynomial, - compressed_table_expression: Polynomial, - permuted_table_expression: Polynomial, - permuted_table_poly: Polynomial, +pub(in crate::plonk) struct Permuted { + compressed_input_expression: Polynomial, + permuted_input_expression: Polynomial, + permuted_input_poly: Polynomial, + compressed_table_expression: Polynomial, + permuted_table_expression: Polynomial, + permuted_table_poly: Polynomial, } #[derive(Debug)] -pub(in crate::plonk) struct Committed { - pub(in crate::plonk) permuted_input_poly: Polynomial, - pub(in crate::plonk) permuted_table_poly: Polynomial, - pub(in crate::plonk) product_poly: Polynomial, +pub(in crate::plonk) struct Committed { + pub(in crate::plonk) permuted_input_poly: Polynomial, + pub(in crate::plonk) permuted_table_poly: Polynomial, + pub(in crate::plonk) product_poly: Polynomial, } -pub(in crate::plonk) struct Evaluated { - constructed: Committed, +pub(in crate::plonk) struct Evaluated { + constructed: Committed, } -impl> Argument { +impl + Ord> Argument { /// Given a Lookup with input expressions [A_0, A_1, ..., A_{m-1}] and table expressions /// [S_0, S_1, ..., S_{m-1}], this method /// - constructs A_compressed = \theta^{m-1} A_0 + theta^{m-2} A_1 + ... + \theta A_{m-2} + A_{m-1} @@ -59,30 +48,27 @@ impl> Argument { pub(in crate::plonk) fn commit_permuted< 'a, 'params: 'a, - C, - P: Params<'params, C>, - E: EncodedChallenge, + CS: PolynomialCommitmentScheme, R: RngCore, - T: TranscriptWrite, + T: Transcript, >( &self, - pk: &ProvingKey, - params: &P, - domain: &EvaluationDomain, - theta: ChallengeTheta, - advice_values: &'a [Polynomial], - fixed_values: &'a [Polynomial], - instance_values: &'a [Polynomial], - challenges: &'a [C::Scalar], + pk: &ProvingKey, + params: &'params CS::Parameters, + domain: &EvaluationDomain, + theta: F, + advice_values: &'a [Polynomial], + fixed_values: &'a [Polynomial], + instance_values: &'a [Polynomial], + challenges: &'a [F], mut rng: R, transcript: &mut T, - ) -> Result, Error> + ) -> Result, Error> where - C: CurveAffine, - C::Curve: Mul + MulAssign, + CS::Commitment: Hashable, { // Closure to get values of expressions and compress them - let compress_expressions = |expressions: &[Expression]| { + let compress_expressions = |expressions: &[Expression]| { let compressed_expression = expressions .iter() .map(|expression| { @@ -97,7 +83,7 @@ impl> Argument { )) }) .fold(domain.empty_lagrange(), |acc, expression| { - acc * *theta + &expression + acc * theta + &expression }); compressed_expression }; @@ -119,10 +105,9 @@ impl> Argument { )?; // Closure to construct commitment to vector of values - let mut commit_values = |values: &Polynomial| { + let commit_values = |values: &Polynomial| { let poly = pk.vk.domain.lagrange_to_coeff(values.clone()); - let blind = Blind(C::Scalar::random(&mut rng)); - let commitment = params.commit_lagrange(values, blind).to_affine(); + let commitment = params.commit_lagrange(values); (poly, commitment) }; @@ -135,10 +120,10 @@ impl> Argument { commit_values(&permuted_table_expression); // Hash permuted input commitment - transcript.write_point(permuted_input_commitment)?; + transcript.write(&permuted_input_commitment)?; // Hash permuted table commitment - transcript.write_point(permuted_table_commitment)?; + transcript.write(&permuted_table_commitment)?; Ok(Permuted { compressed_input_expression, @@ -151,27 +136,24 @@ impl> Argument { } } -impl Permuted { +impl> Permuted { /// Given a Lookup with input expressions, table expressions, and the permuted /// input expression and permuted table expression, this method constructs the /// grand product polynomial over the lookup. The grand product polynomial /// is used to populate the Product struct. The Product struct is /// added to the Lookup and finally returned by the method. - pub(in crate::plonk) fn commit_product< - 'params, - P: Params<'params, C>, - E: EncodedChallenge, - R: RngCore, - T: TranscriptWrite, - >( + pub(in crate::plonk) fn commit_product, T: Transcript>( self, - pk: &ProvingKey, - params: &P, - beta: ChallengeBeta, - gamma: ChallengeGamma, - mut rng: R, + pk: &ProvingKey, + params: &CS::Parameters, + beta: F, + gamma: F, + mut rng: impl RngCore + CryptoRng, transcript: &mut T, - ) -> Result, Error> { + ) -> Result, Error> + where + CS::Commitment: Hashable, + { let blinding_factors = pk.vk.cs.blinding_factors(); // Goal is to compute the products of fractions // @@ -184,7 +166,7 @@ impl Permuted { // s_j(X) is the jth table expression in this lookup, // s'(X) is the compression of the permuted table expressions, // and i is the ith row of the expression. - let mut lookup_product = vec![C::Scalar::ZERO; params.n() as usize]; + let mut lookup_product = vec![F::ZERO; params.n() as usize]; // Denominator uses the permuted input expression and permuted table expression parallelize(&mut lookup_product, |lookup_product, start| { for ((lookup_product, permuted_input_value), permuted_table_value) in lookup_product @@ -192,7 +174,7 @@ impl Permuted { .zip(self.permuted_input_expression[start..].iter()) .zip(self.permuted_table_expression[start..].iter()) { - *lookup_product = (*beta + permuted_input_value) * &(*gamma + permuted_table_value); + *lookup_product = (beta + permuted_input_value) * &(gamma + permuted_table_value); } }); @@ -207,8 +189,8 @@ impl Permuted { for (i, product) in product.iter_mut().enumerate() { let i = i + start; - *product *= &(self.compressed_input_expression[i] + &*beta); - *product *= &(self.compressed_table_expression[i] + &*gamma); + *product *= &(self.compressed_input_expression[i] + &beta); + *product *= &(self.compressed_table_expression[i] + &gamma); } }); @@ -227,9 +209,9 @@ impl Permuted { // Compute the evaluations of the lookup product polynomial // over our domain, starting with z[0] = 1 - let z = iter::once(C::Scalar::ONE) + let z = iter::once(F::ONE) .chain(lookup_product) - .scan(C::Scalar::ONE, |state, cur| { + .scan(F::ONE, |state, cur| { *state *= &cur; Some(*state) }) @@ -237,7 +219,7 @@ impl Permuted { // be a boolean (and ideally 1, else soundness is broken) .take(params.n() as usize - blinding_factors) // Chain random blinding factors. - .chain((0..blinding_factors).map(|_| C::Scalar::random(&mut rng))) + .chain((0..blinding_factors).map(|_| F::random(&mut rng))) .collect::>(); assert_eq!(z.len(), params.n() as usize); let z = pk.vk.domain.lagrange_from_vec(z); @@ -250,7 +232,7 @@ impl Permuted { let u = (params.n() as usize) - (blinding_factors + 1); // l_0(X) * (1 - z(X)) = 0 - assert_eq!(z[0], C::Scalar::ONE); + assert_eq!(z[0], F::ONE); // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma) // - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma) @@ -277,17 +259,16 @@ impl Permuted { // l_last(X) * (z(X)^2 - z(X)) = 0 // Assertion will fail only when soundness is broken, in which // case this z[u] value will be zero. (bad!) - assert_eq!(z[u], C::Scalar::ONE); + assert_eq!(z[u], F::ONE); } - let product_blind = Blind(C::Scalar::random(rng)); - let product_commitment = params.commit_lagrange(&z, product_blind).to_affine(); + let product_commitment = params.commit_lagrange(&z); let z = pk.vk.domain.lagrange_to_coeff(z); // Hash product commitment - transcript.write_point(product_commitment)?; + transcript.write(&product_commitment)?; - Ok(Committed:: { + Ok(Committed:: { permuted_input_poly: self.permuted_input_poly, permuted_table_poly: self.permuted_table_poly, product_poly: z, @@ -295,22 +276,25 @@ impl Permuted { } } -impl Committed { - pub(in crate::plonk) fn evaluate, T: TranscriptWrite>( +impl> Committed { + pub(in crate::plonk) fn evaluate>( self, - pk: &ProvingKey, - x: ChallengeX, + pk: &ProvingKey, + x: F, transcript: &mut T, - ) -> Result, Error> { + ) -> Result, Error> + where + F: Hashable + SerdeObject, + { let domain = &pk.vk.domain; - let x_inv = domain.rotate_omega(*x, Rotation::prev()); - let x_next = domain.rotate_omega(*x, Rotation::next()); + let x_inv = domain.rotate_omega(x, Rotation::prev()); + let x_next = domain.rotate_omega(x, Rotation::next()); - let product_eval = eval_polynomial(&self.product_poly, *x); + let product_eval = eval_polynomial(&self.product_poly, x); let product_next_eval = eval_polynomial(&self.product_poly, x_next); - let permuted_input_eval = eval_polynomial(&self.permuted_input_poly, *x); + let permuted_input_eval = eval_polynomial(&self.permuted_input_poly, x); let permuted_input_inv_eval = eval_polynomial(&self.permuted_input_poly, x_inv); - let permuted_table_eval = eval_polynomial(&self.permuted_table_poly, *x); + let permuted_table_eval = eval_polynomial(&self.permuted_table_poly, x); // Hash each advice evaluation for eval in iter::empty() @@ -320,36 +304,36 @@ impl Committed { .chain(Some(permuted_input_inv_eval)) .chain(Some(permuted_table_eval)) { - transcript.write_scalar(eval)?; + transcript.write(&eval)?; } Ok(Evaluated { constructed: self }) } } -impl Evaluated { - pub(in crate::plonk) fn open<'a>( +impl> Evaluated { + pub(in crate::plonk) fn open<'a, CS: PolynomialCommitmentScheme>( &'a self, - pk: &'a ProvingKey, - x: ChallengeX, - ) -> impl Iterator> + Clone { - let x_inv = pk.vk.domain.rotate_omega(*x, Rotation::prev()); - let x_next = pk.vk.domain.rotate_omega(*x, Rotation::next()); + pk: &'a ProvingKey, + x: F, + ) -> impl Iterator> + Clone { + let x_inv = pk.vk.domain.rotate_omega(x, Rotation::prev()); + let x_next = pk.vk.domain.rotate_omega(x, Rotation::next()); iter::empty() // Open lookup product commitments at x .chain(Some(ProverQuery { - point: *x, + point: x, poly: &self.constructed.product_poly, })) // Open lookup input commitments at x .chain(Some(ProverQuery { - point: *x, + point: x, poly: &self.constructed.permuted_input_poly, })) // Open lookup table commitments at x .chain(Some(ProverQuery { - point: *x, + point: x, poly: &self.constructed.permuted_table_poly, })) // Open lookup input commitments at x_inv @@ -373,32 +357,37 @@ type ExpressionPair = (Polynomial, Polynomial, R: RngCore>( - pk: &ProvingKey, - params: &P, - domain: &EvaluationDomain, +fn permute_expression_pair< + F: WithSmallOrderMulGroup<3> + Ord, + CS: PolynomialCommitmentScheme, + R: RngCore, +>( + pk: &ProvingKey, + params: &CS::Parameters, + domain: &EvaluationDomain, mut rng: R, - input_expression: &Polynomial, - table_expression: &Polynomial, -) -> Result, Error> { + input_expression: &Polynomial, + table_expression: &Polynomial, +) -> Result, Error> { let blinding_factors = pk.vk.cs.blinding_factors(); let usable_rows = params.n() as usize - (blinding_factors + 1); - let mut permuted_input_expression: Vec = input_expression.to_vec(); + let mut permuted_input_expression: Vec = input_expression.to_vec(); permuted_input_expression.truncate(usable_rows); // Sort input lookup expression values permuted_input_expression.sort(); // A BTreeMap of each unique element in the table expression and its count - let mut leftover_table_map: BTreeMap = table_expression - .iter() - .take(usable_rows) - .fold(BTreeMap::new(), |mut acc, coeff| { - *acc.entry(*coeff).or_insert(0) += 1; - acc - }); - let mut permuted_table_coeffs = vec![C::Scalar::ZERO; usable_rows]; + let mut leftover_table_map: BTreeMap = + table_expression + .iter() + .take(usable_rows) + .fold(BTreeMap::new(), |mut acc, coeff| { + *acc.entry(*coeff).or_insert(0) += 1; + acc + }); + let mut permuted_table_coeffs = vec![F::ZERO; usable_rows]; let mut repeated_input_rows = permuted_input_expression .iter() @@ -432,9 +421,8 @@ fn permute_expression_pair<'params, C: CurveAffine, P: Params<'params, C>, R: Rn } assert!(repeated_input_rows.is_empty()); - permuted_input_expression - .extend((0..(blinding_factors + 1)).map(|_| C::Scalar::random(&mut rng))); - permuted_table_coeffs.extend((0..(blinding_factors + 1)).map(|_| C::Scalar::random(&mut rng))); + permuted_input_expression.extend((0..(blinding_factors + 1)).map(|_| F::random(&mut rng))); + permuted_table_coeffs.extend((0..(blinding_factors + 1)).map(|_| F::random(&mut rng))); assert_eq!(permuted_input_expression.len(), params.n() as usize); assert_eq!(permuted_table_coeffs.len(), params.n() as usize); diff --git a/src/plonk/lookup/verifier.rs b/src/plonk/lookup/verifier.rs index bbc86c8e9d..92bf9fb21a 100644 --- a/src/plonk/lookup/verifier.rs +++ b/src/plonk/lookup/verifier.rs @@ -1,47 +1,48 @@ use std::iter; -use super::super::{ - circuit::Expression, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, -}; +use super::super::circuit::Expression; use super::Argument; +use crate::poly::commitment::PolynomialCommitmentScheme; +use crate::transcript::{Hashable, Transcript}; use crate::{ - arithmetic::CurveAffine, plonk::{Error, VerifyingKey}, - poly::{commitment::MSM, Rotation, VerifierQuery}, - transcript::{EncodedChallenge, TranscriptRead}, + poly::{Rotation, VerifierQuery}, }; -use ff::Field; +use ff::{PrimeField, WithSmallOrderMulGroup}; +use halo2curves::serde::SerdeObject; -pub struct PermutationCommitments { - permuted_input_commitment: C, - permuted_table_commitment: C, +pub struct PermutationCommitments> { + permuted_input_commitment: CS::Commitment, + permuted_table_commitment: CS::Commitment, } -pub struct Committed { - permuted: PermutationCommitments, - product_commitment: C, +pub struct Committed> { + permuted: PermutationCommitments, + product_commitment: CS::Commitment, } -pub struct Evaluated { - committed: Committed, - product_eval: C::Scalar, - product_next_eval: C::Scalar, - permuted_input_eval: C::Scalar, - permuted_input_inv_eval: C::Scalar, - permuted_table_eval: C::Scalar, +pub struct Evaluated> { + committed: Committed, + product_eval: F, + product_next_eval: F, + permuted_input_eval: F, + permuted_input_inv_eval: F, + permuted_table_eval: F, } -impl Argument { +impl Argument { pub(in crate::plonk) fn read_permuted_commitments< - C: CurveAffine, - E: EncodedChallenge, - T: TranscriptRead, + CS: PolynomialCommitmentScheme, + T: Transcript, >( &self, transcript: &mut T, - ) -> Result, Error> { - let permuted_input_commitment = transcript.read_point()?; - let permuted_table_commitment = transcript.read_point()?; + ) -> Result, Error> + where + CS::Commitment: Hashable, + { + let permuted_input_commitment = transcript.read()?; + let permuted_table_commitment = transcript.read()?; Ok(PermutationCommitments { permuted_input_commitment, @@ -50,15 +51,15 @@ impl Argument { } } -impl PermutationCommitments { - pub(in crate::plonk) fn read_product_commitment< - E: EncodedChallenge, - T: TranscriptRead, - >( +impl> PermutationCommitments { + pub(in crate::plonk) fn read_product_commitment( self, transcript: &mut T, - ) -> Result, Error> { - let product_commitment = transcript.read_point()?; + ) -> Result, Error> + where + CS::Commitment: Hashable + SerdeObject, + { + let product_commitment = transcript.read()?; Ok(Committed { permuted: self, @@ -67,16 +68,19 @@ impl PermutationCommitments { } } -impl Committed { - pub(crate) fn evaluate, T: TranscriptRead>( +impl> Committed { + pub(crate) fn evaluate( self, transcript: &mut T, - ) -> Result, Error> { - let product_eval = transcript.read_scalar()?; - let product_next_eval = transcript.read_scalar()?; - let permuted_input_eval = transcript.read_scalar()?; - let permuted_input_inv_eval = transcript.read_scalar()?; - let permuted_table_eval = transcript.read_scalar()?; + ) -> Result, Error> + where + F: Hashable + SerdeObject, + { + let product_eval = transcript.read()?; + let product_next_eval = transcript.read()?; + let permuted_input_eval = transcript.read()?; + let permuted_input_inv_eval = transcript.read()?; + let permuted_table_eval = transcript.read()?; Ok(Evaluated { committed: self, @@ -89,32 +93,32 @@ impl Committed { } } -impl Evaluated { +impl, CS: PolynomialCommitmentScheme> Evaluated { #[allow(clippy::too_many_arguments)] pub(in crate::plonk) fn expressions<'a>( &'a self, - l_0: C::Scalar, - l_last: C::Scalar, - l_blind: C::Scalar, - argument: &'a Argument, - theta: ChallengeTheta, - beta: ChallengeBeta, - gamma: ChallengeGamma, - advice_evals: &[C::Scalar], - fixed_evals: &[C::Scalar], - instance_evals: &[C::Scalar], - challenges: &[C::Scalar], - ) -> impl Iterator + 'a { - let active_rows = C::Scalar::ONE - (l_last + l_blind); + l_0: F, + l_last: F, + l_blind: F, + argument: &'a Argument, + theta: F, + beta: F, + gamma: F, + advice_evals: &[F], + fixed_evals: &[F], + instance_evals: &[F], + challenges: &[F], + ) -> impl Iterator + 'a { + let active_rows = F::ONE - (l_last + l_blind); let product_expression = || { // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma) // - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma) let left = self.product_next_eval - * &(self.permuted_input_eval + &*beta) - * &(self.permuted_table_eval + &*gamma); + * &(self.permuted_input_eval + &beta) + * &(self.permuted_table_eval + &gamma); - let compress_expressions = |expressions: &[Expression]| { + let compress_expressions = |expressions: &[Expression]| { expressions .iter() .map(|expression| { @@ -131,11 +135,11 @@ impl Evaluated { &|a, scalar| a * &scalar, ) }) - .fold(C::Scalar::ZERO, |acc, eval| acc * &*theta + &eval) + .fold(F::ZERO, |acc, eval| acc * &theta + &eval) }; let right = self.product_eval - * &(compress_expressions(&argument.input_expressions) + &*beta) - * &(compress_expressions(&argument.table_expressions) + &*gamma); + * &(compress_expressions(&argument.input_expressions) + &beta) + * &(compress_expressions(&argument.table_expressions) + &gamma); (left - &right) * &active_rows }; @@ -143,7 +147,7 @@ impl Evaluated { std::iter::empty() .chain( // l_0(X) * (1 - z(X)) = 0 - Some(l_0 * &(C::Scalar::ONE - &self.product_eval)), + Some(l_0 * &(F::ONE - &self.product_eval)), ) .chain( // l_last(X) * (z(X)^2 - z(X)) = 0 @@ -168,43 +172,44 @@ impl Evaluated { )) } - pub(in crate::plonk) fn queries<'r, M: MSM + 'r>( - &'r self, - vk: &'r VerifyingKey, - x: ChallengeX, - ) -> impl Iterator> + Clone { - let x_inv = vk.domain.rotate_omega(*x, Rotation::prev()); - let x_next = vk.domain.rotate_omega(*x, Rotation::next()); + // todo: Removing trick from MSMs here + pub(in crate::plonk) fn queries( + &self, + vk: &VerifyingKey, + x: F, + ) -> impl Iterator> + Clone { + let x_inv = vk.domain.rotate_omega(x, Rotation::prev()); + let x_next = vk.domain.rotate_omega(x, Rotation::next()); iter::empty() // Open lookup product commitment at x - .chain(Some(VerifierQuery::new_commitment( - &self.committed.product_commitment, - *x, + .chain(Some(VerifierQuery::new( + x, + self.committed.product_commitment, self.product_eval, ))) // Open lookup input commitments at x - .chain(Some(VerifierQuery::new_commitment( - &self.committed.permuted.permuted_input_commitment, - *x, + .chain(Some(VerifierQuery::new( + x, + self.committed.permuted.permuted_input_commitment, self.permuted_input_eval, ))) // Open lookup table commitments at x - .chain(Some(VerifierQuery::new_commitment( - &self.committed.permuted.permuted_table_commitment, - *x, + .chain(Some(VerifierQuery::new( + x, + self.committed.permuted.permuted_table_commitment, self.permuted_table_eval, ))) // Open lookup input commitments at \omega^{-1} x - .chain(Some(VerifierQuery::new_commitment( - &self.committed.permuted.permuted_input_commitment, + .chain(Some(VerifierQuery::new( x_inv, + self.committed.permuted.permuted_input_commitment, self.permuted_input_inv_eval, ))) // Open lookup product commitment at \omega x - .chain(Some(VerifierQuery::new_commitment( - &self.committed.product_commitment, + .chain(Some(VerifierQuery::new( x_next, + self.committed.product_commitment, self.product_next_eval, ))) } diff --git a/src/plonk/permutation.rs b/src/plonk/permutation.rs index 22c1fad6c3..7d569295c8 100644 --- a/src/plonk/permutation.rs +++ b/src/plonk/permutation.rs @@ -2,10 +2,8 @@ use super::circuit::{Any, Column}; use crate::{ - arithmetic::CurveAffine, helpers::{ - polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, - SerdeCurveAffine, SerdePrimeField, + polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, SerdePrimeField, }, poly::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial}, SerdeFormat, @@ -17,6 +15,10 @@ pub(crate) mod verifier; pub use keygen::Assembly; +use crate::helpers::byte_length; +use crate::poly::commitment::PolynomialCommitmentScheme; +use ff::PrimeField; +use halo2curves::serde::SerdeObject; use std::io; /// A permutation argument. @@ -83,22 +85,23 @@ impl Argument { /// The verifying key for a single permutation argument. #[derive(Clone, Debug)] -pub struct VerifyingKey { - commitments: Vec, +pub struct VerifyingKey> { + commitments: Vec, } -impl VerifyingKey { +impl> VerifyingKey { /// Returns commitments of sigma polynomials - pub fn commitments(&self) -> &Vec { + pub fn commitments(&self) -> &Vec { &self.commitments } - pub(crate) fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> + pub(crate) fn write(&self, writer: &mut W, _format: SerdeFormat) -> io::Result<()> where - C: SerdeCurveAffine, + CS::Commitment: SerdeObject, { + // todo: How to handle formats? for commitment in &self.commitments { - commitment.write(writer, format)?; + commitment.write_raw(writer)?; } Ok(()) } @@ -106,37 +109,34 @@ impl VerifyingKey { pub(crate) fn read( reader: &mut R, argument: &Argument, - format: SerdeFormat, + _format: SerdeFormat, ) -> io::Result where - C: SerdeCurveAffine, + CS::Commitment: SerdeObject, { let commitments = (0..argument.columns.len()) - .map(|_| C::read(reader, format)) + .map(|_| CS::Commitment::read_raw(reader)) .collect::, _>>()?; Ok(VerifyingKey { commitments }) } pub(crate) fn bytes_length(&self, format: SerdeFormat) -> usize where - C: SerdeCurveAffine, + CS::Commitment: SerdeObject, { - self.commitments.len() * C::byte_length(format) + self.commitments.len() * byte_length::(format) } } /// The proving key for a single permutation argument. #[derive(Clone, Debug)] -pub(crate) struct ProvingKey { - permutations: Vec>, - polys: Vec>, - pub(super) cosets: Vec>, +pub(crate) struct ProvingKey { + permutations: Vec>, + polys: Vec>, + pub(super) cosets: Vec>, } -impl ProvingKey -where - C::Scalar: SerdePrimeField, -{ +impl ProvingKey { /// Reads proving key for a single permutation argument from buffer using `Polynomial::read`. pub(super) fn read(reader: &mut R, format: SerdeFormat) -> io::Result { let permutations = read_polynomial_vec(reader, format)?; @@ -162,7 +162,7 @@ where } } -impl ProvingKey { +impl ProvingKey { /// Gets the total number of bytes in the serialization of `self` pub(super) fn bytes_length(&self) -> usize { polynomial_slice_byte_length(&self.permutations) diff --git a/src/plonk/permutation/keygen.rs b/src/plonk/permutation/keygen.rs index 0d78f00ac5..3adb2de769 100644 --- a/src/plonk/permutation/keygen.rs +++ b/src/plonk/permutation/keygen.rs @@ -1,14 +1,10 @@ -use ff::{Field, PrimeField}; -use group::Curve; +use ff::WithSmallOrderMulGroup; use super::{Argument, ProvingKey, VerifyingKey}; use crate::{ - arithmetic::{parallelize, CurveAffine}, + arithmetic::parallelize, plonk::{Any, Column, Error}, - poly::{ - commitment::{Blind, Params}, - EvaluationDomain, - }, + poly::EvaluationDomain, }; #[cfg(feature = "thread-safe-region")] @@ -17,6 +13,7 @@ use crate::multicore::{IndexedParallelIterator, IntoParallelIterator, ParallelIt #[cfg(not(feature = "thread-safe-region"))] use crate::multicore::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; +use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; #[cfg(feature = "thread-safe-region")] use std::collections::{BTreeSet, HashMap}; @@ -113,22 +110,22 @@ impl Assembly { Ok(()) } - pub(crate) fn build_vk<'params, C: CurveAffine, P: Params<'params, C>>( + pub(crate) fn build_vk, CS: PolynomialCommitmentScheme>( self, - params: &P, - domain: &EvaluationDomain, + params: &CS::VerifierParameters, + domain: &EvaluationDomain, p: &Argument, - ) -> VerifyingKey { + ) -> VerifyingKey { build_vk(params, domain, p, |i, j| self.mapping[i][j]) } - pub(crate) fn build_pk<'params, C: CurveAffine, P: Params<'params, C>>( + pub(crate) fn build_pk, CS: PolynomialCommitmentScheme>( self, - params: &P, - domain: &EvaluationDomain, + params: &CS::Parameters, + domain: &EvaluationDomain, p: &Argument, - ) -> ProvingKey { - build_pk(params, domain, p, |i, j| self.mapping[i][j]) + ) -> ProvingKey { + build_pk::<_, CS>(params, domain, p, |i, j| self.mapping[i][j]) } /// Returns columns that participate in the permutation argument. @@ -284,22 +281,22 @@ impl Assembly { } } - pub(crate) fn build_vk<'params, C: CurveAffine, P: Params<'params, C>>( + pub(crate) fn build_vk>( &mut self, - params: &P, - domain: &EvaluationDomain, + params: &CS::VerifierParameters, + domain: &EvaluationDomain, p: &Argument, - ) -> VerifyingKey { + ) -> VerifyingKey { self.build_ordered_mapping(); build_vk(params, domain, p, |i, j| self.mapping_at_idx(i, j)) } - pub(crate) fn build_pk<'params, C: CurveAffine, P: Params<'params, C>>( + pub(crate) fn build_pk>( &mut self, - params: &P, - domain: &EvaluationDomain, + params: &CS::Parameters, + domain: &EvaluationDomain, p: &Argument, - ) -> ProvingKey { + ) -> ProvingKey { self.build_ordered_mapping(); build_pk(params, domain, p, |i, j| self.mapping_at_idx(i, j)) } @@ -321,14 +318,14 @@ impl Assembly { } } -pub(crate) fn build_pk<'params, C: CurveAffine, P: Params<'params, C>>( - params: &P, - domain: &EvaluationDomain, +pub(crate) fn build_pk, CS: PolynomialCommitmentScheme>( + params: &CS::Parameters, + domain: &EvaluationDomain, p: &Argument, mapping: impl Fn(usize, usize) -> (usize, usize) + Sync, -) -> ProvingKey { +) -> ProvingKey { // Compute [omega^0, omega^1, ..., omega^{params.n - 1}] - let mut omega_powers = vec![C::Scalar::ZERO; params.n() as usize]; + let mut omega_powers = vec![F::ZERO; params.n() as usize]; { let omega = domain.get_omega(); parallelize(&mut omega_powers, |o, start| { @@ -344,12 +341,12 @@ pub(crate) fn build_pk<'params, C: CurveAffine, P: Params<'params, C>>( let mut deltaomega = vec![omega_powers; p.columns.len()]; { parallelize(&mut deltaomega, |o, start| { - let mut cur = C::Scalar::DELTA.pow_vartime([start as u64]); + let mut cur = F::DELTA.pow_vartime([start as u64]); for omega_powers in o.iter_mut() { for v in omega_powers { *v *= &cur; } - cur *= &C::Scalar::DELTA; + cur *= &F::DELTA; } }); } @@ -397,14 +394,14 @@ pub(crate) fn build_pk<'params, C: CurveAffine, P: Params<'params, C>>( } } -pub(crate) fn build_vk<'params, C: CurveAffine, P: Params<'params, C>>( - params: &P, - domain: &EvaluationDomain, +pub(crate) fn build_vk, CS: PolynomialCommitmentScheme>( + params: &CS::VerifierParameters, + domain: &EvaluationDomain, p: &Argument, mapping: impl Fn(usize, usize) -> (usize, usize) + Sync, -) -> VerifyingKey { +) -> VerifyingKey { // Compute [omega^0, omega^1, ..., omega^{params.n - 1}] - let mut omega_powers = vec![C::Scalar::ZERO; params.n() as usize]; + let mut omega_powers = vec![F::ZERO; params.n() as usize]; { let omega = domain.get_omega(); parallelize(&mut omega_powers, |o, start| { @@ -420,12 +417,12 @@ pub(crate) fn build_vk<'params, C: CurveAffine, P: Params<'params, C>>( let mut deltaomega = vec![omega_powers; p.columns.len()]; { parallelize(&mut deltaomega, |o, start| { - let mut cur = C::Scalar::DELTA.pow_vartime([start as u64]); + let mut cur = F::DELTA.pow_vartime([start as u64]); for omega_powers in o.iter_mut() { for v in omega_powers { *v *= &cur; } - cur *= &::DELTA; + cur *= &F::DELTA; } }); } @@ -449,11 +446,7 @@ pub(crate) fn build_vk<'params, C: CurveAffine, P: Params<'params, C>>( let mut commitments = Vec::with_capacity(p.columns.len()); for permutation in &permutations { // Compute commitment to permutation polynomial - commitments.push( - params - .commit_lagrange(permutation, Blind::default()) - .to_affine(), - ); + commitments.push(params.commit_lagrange(permutation)); } VerifyingKey { commitments } diff --git a/src/plonk/permutation/prover.rs b/src/plonk/permutation/prover.rs index b6ecb06a1c..cdff66c457 100644 --- a/src/plonk/permutation/prover.rs +++ b/src/plonk/permutation/prover.rs @@ -1,66 +1,63 @@ -use ff::PrimeField; -use group::{ - ff::{BatchInvert, Field}, - Curve, -}; +use ff::{PrimeField, WithSmallOrderMulGroup}; +use group::ff::BatchInvert; +use halo2curves::serde::SerdeObject; use rand_core::RngCore; use std::iter::{self, ExactSizeIterator}; -use super::super::{circuit::Any, ChallengeBeta, ChallengeGamma, ChallengeX}; +use super::super::circuit::Any; use super::{Argument, ProvingKey}; +use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; +use crate::transcript::{Hashable, Transcript}; use crate::{ - arithmetic::{eval_polynomial, parallelize, CurveAffine}, + arithmetic::{eval_polynomial, parallelize}, plonk::{self, Error}, - poly::{ - commitment::{Blind, Params}, - Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, ProverQuery, Rotation, - }, - transcript::{EncodedChallenge, TranscriptWrite}, + poly::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, ProverQuery, Rotation}, }; -pub(crate) struct CommittedSet { - pub(crate) permutation_product_poly: Polynomial, - pub(crate) permutation_product_coset: Polynomial, +pub(crate) struct CommittedSet { + pub(crate) permutation_product_poly: Polynomial, + pub(crate) permutation_product_coset: Polynomial, } -pub(crate) struct Committed { - pub(crate) sets: Vec>, +pub(crate) struct Committed { + pub(crate) sets: Vec>, } -pub struct ConstructedSet { - permutation_product_poly: Polynomial, +pub struct ConstructedSet { + permutation_product_poly: Polynomial, } -pub(crate) struct Constructed { - sets: Vec>, +pub(crate) struct Constructed { + sets: Vec>, } -pub(crate) struct Evaluated { - constructed: Constructed, +pub(crate) struct Evaluated { + constructed: Constructed, } impl Argument { #[allow(clippy::too_many_arguments)] pub(in crate::plonk) fn commit< - 'params, - C: CurveAffine, - P: Params<'params, C>, - E: EncodedChallenge, + F: WithSmallOrderMulGroup<3>, + CS: PolynomialCommitmentScheme, R: RngCore, - T: TranscriptWrite, + T: Transcript, >( &self, - params: &P, - pk: &plonk::ProvingKey, - pkey: &ProvingKey, - advice: &[Polynomial], - fixed: &[Polynomial], - instance: &[Polynomial], - beta: ChallengeBeta, - gamma: ChallengeGamma, + params: &CS::Parameters, + pk: &plonk::ProvingKey, + pkey: &ProvingKey, + advice: &[Polynomial], + fixed: &[Polynomial], + instance: &[Polynomial], + beta: F, + gamma: F, mut rng: R, transcript: &mut T, - ) -> Result, Error> { + ) -> Result, Error> + where + CS::Commitment: Hashable, + { let domain = &pk.vk.domain; // How many columns can be included in a single permutation polynomial? @@ -72,10 +69,10 @@ impl Argument { let blinding_factors = pk.vk.cs.blinding_factors(); // Each column gets its own delta power. - let mut deltaomega = C::Scalar::ONE; + let mut deltaomega = F::ONE; // Track the "last" value from the previous column set - let mut last_z = C::Scalar::ONE; + let mut last_z = F::ONE; let mut sets = vec![]; @@ -92,7 +89,7 @@ impl Argument { // where p_j(X) is the jth column in this permutation, // and i is the ith row of the column. - let mut modified_values = vec![C::Scalar::ONE; params.n() as usize]; + let mut modified_values = vec![F::ONE; params.n() as usize]; // Iterate over each column of the permutation for (&column, permuted_column_values) in columns.iter().zip(permutations.iter()) { @@ -107,7 +104,7 @@ impl Argument { .zip(values[column.index()][start..].iter()) .zip(permuted_column_values[start..].iter()) { - *modified_values *= &(*beta * permuted_value + &*gamma + value); + *modified_values *= &(beta * permuted_value + &gamma + value); } }); } @@ -131,11 +128,11 @@ impl Argument { .zip(values[column.index()][start..].iter()) { // Multiply by p_j(\omega^i) + \delta^j \omega^i \beta - *modified_values *= &(deltaomega * &*beta + &*gamma + value); + *modified_values *= &(deltaomega * &beta + &gamma + value); deltaomega *= ω } }); - deltaomega *= &::DELTA; + deltaomega *= &F::DELTA; } // The modified_values vector is a vector of products of fractions @@ -159,24 +156,19 @@ impl Argument { let mut z = domain.lagrange_from_vec(z); // Set blinding factors for z in &mut z[params.n() as usize - blinding_factors..] { - *z = C::Scalar::random(&mut rng); + *z = F::random(&mut rng); } // Set new last_z last_z = z[params.n() as usize - (blinding_factors + 1)]; - let blind = Blind(C::Scalar::random(&mut rng)); - - let permutation_product_commitment_projective = params.commit_lagrange(&z, blind); + let permutation_product_commitment = params.commit_lagrange(&z); let z = domain.lagrange_to_coeff(z); let permutation_product_poly = z.clone(); let permutation_product_coset = domain.coeff_to_extended(z.clone()); - let permutation_product_commitment = - permutation_product_commitment_projective.to_affine(); - // Hash the permutation product commitment - transcript.write_point(permutation_product_commitment)?; + transcript.write(&permutation_product_commitment)?; sets.push(CommittedSet { permutation_product_poly, @@ -188,8 +180,8 @@ impl Argument { } } -impl Committed { - pub(in crate::plonk) fn construct(self) -> Constructed { +impl Committed { + pub(in crate::plonk) fn construct(self) -> Constructed { Constructed { sets: self .sets @@ -202,37 +194,41 @@ impl Committed { } } -impl super::ProvingKey { - pub(in crate::plonk) fn open( - &self, - x: ChallengeX, - ) -> impl Iterator> + Clone { +impl super::ProvingKey { + pub(in crate::plonk) fn open(&self, x: F) -> impl Iterator> + Clone { self.polys .iter() - .map(move |poly| ProverQuery { point: *x, poly }) + .map(move |poly| ProverQuery { point: x, poly }) } - pub(in crate::plonk) fn evaluate, T: TranscriptWrite>( + pub(in crate::plonk) fn evaluate( &self, - x: ChallengeX, + x: F, transcript: &mut T, - ) -> Result<(), Error> { + ) -> Result<(), Error> + where + F: Hashable + SerdeObject, + { // Hash permutation evals - for eval in self.polys.iter().map(|poly| eval_polynomial(poly, *x)) { - transcript.write_scalar(eval)?; + for eval in self.polys.iter().map(|poly| eval_polynomial(poly, x)) { + transcript.write(&eval)?; } Ok(()) } } -impl Constructed { - pub(in crate::plonk) fn evaluate, T: TranscriptWrite>( +impl> Constructed { + // TODO: I don't quite like the PCS in the function + pub(in crate::plonk) fn evaluate>( self, - pk: &plonk::ProvingKey, - x: ChallengeX, + pk: &plonk::ProvingKey, + x: F, transcript: &mut T, - ) -> Result, Error> { + ) -> Result, Error> + where + F: Hashable + SerdeObject, + { let domain = &pk.vk.domain; let blinding_factors = pk.vk.cs.blinding_factors(); @@ -240,11 +236,11 @@ impl Constructed { let mut sets = self.sets.iter(); while let Some(set) = sets.next() { - let permutation_product_eval = eval_polynomial(&set.permutation_product_poly, *x); + let permutation_product_eval = eval_polynomial(&set.permutation_product_poly, x); let permutation_product_next_eval = eval_polynomial( &set.permutation_product_poly, - domain.rotate_omega(*x, Rotation::next()), + domain.rotate_omega(x, Rotation::next()), ); // Hash permutation product evals @@ -252,7 +248,7 @@ impl Constructed { .chain(Some(&permutation_product_eval)) .chain(Some(&permutation_product_next_eval)) { - transcript.write_scalar(*eval)?; + transcript.write(eval)?; } // If we have any remaining sets to process, evaluate this set at omega^u @@ -261,10 +257,10 @@ impl Constructed { if sets.len() > 0 { let permutation_product_last_eval = eval_polynomial( &set.permutation_product_poly, - domain.rotate_omega(*x, Rotation(-((blinding_factors + 1) as i32))), + domain.rotate_omega(x, Rotation(-((blinding_factors + 1) as i32))), ); - transcript.write_scalar(permutation_product_last_eval)?; + transcript.write(&permutation_product_last_eval)?; } } } @@ -273,25 +269,26 @@ impl Constructed { } } -impl Evaluated { - pub(in crate::plonk) fn open<'a>( +impl> Evaluated { + // todo: ditto - I don't like the PCS here + pub(in crate::plonk) fn open<'a, CS: PolynomialCommitmentScheme>( &'a self, - pk: &'a plonk::ProvingKey, - x: ChallengeX, - ) -> impl Iterator> + Clone { + pk: &'a plonk::ProvingKey, + x: F, + ) -> impl Iterator> + Clone { let blinding_factors = pk.vk.cs.blinding_factors(); - let x_next = pk.vk.domain.rotate_omega(*x, Rotation::next()); + let x_next = pk.vk.domain.rotate_omega(x, Rotation::next()); let x_last = pk .vk .domain - .rotate_omega(*x, Rotation(-((blinding_factors + 1) as i32))); + .rotate_omega(x, Rotation(-((blinding_factors + 1) as i32))); iter::empty() .chain(self.constructed.sets.iter().flat_map(move |set| { iter::empty() // Open permutation product commitments at x and \omega x .chain(Some(ProverQuery { - point: *x, + point: x, poly: &set.permutation_product_poly, })) .chain(Some(ProverQuery { diff --git a/src/plonk/permutation/verifier.rs b/src/plonk/permutation/verifier.rs index a4637422ae..5b2eac24cb 100644 --- a/src/plonk/permutation/verifier.rs +++ b/src/plonk/permutation/verifier.rs @@ -1,50 +1,54 @@ -use ff::{Field, PrimeField}; +use ff::{PrimeField, WithSmallOrderMulGroup}; +use halo2curves::serde::SerdeObject; use std::iter; -use super::super::{circuit::Any, ChallengeBeta, ChallengeGamma, ChallengeX}; +use super::super::circuit::Any; use super::{Argument, VerifyingKey}; +use crate::poly::commitment::PolynomialCommitmentScheme; +use crate::transcript::{Hashable, Transcript}; use crate::{ - arithmetic::CurveAffine, plonk::{self, Error}, - poly::{commitment::MSM, Rotation, VerifierQuery}, - transcript::{EncodedChallenge, TranscriptRead}, + poly::{Rotation, VerifierQuery}, }; -pub struct Committed { - permutation_product_commitments: Vec, +pub struct Committed> { + permutation_product_commitments: Vec, } -pub struct EvaluatedSet { - permutation_product_commitment: C, - permutation_product_eval: C::Scalar, - permutation_product_next_eval: C::Scalar, - permutation_product_last_eval: Option, +pub struct EvaluatedSet> { + permutation_product_commitment: CS::Commitment, + permutation_product_eval: F, + permutation_product_next_eval: F, + permutation_product_last_eval: Option, } -pub struct CommonEvaluated { - permutation_evals: Vec, +pub struct CommonEvaluated { + permutation_evals: Vec, } -pub struct Evaluated { - sets: Vec>, +pub struct Evaluated> { + sets: Vec>, } impl Argument { pub(crate) fn read_product_commitments< - C: CurveAffine, - E: EncodedChallenge, - T: TranscriptRead, + F: PrimeField, + CS: PolynomialCommitmentScheme, + T: Transcript, >( &self, - vk: &plonk::VerifyingKey, + vk: &plonk::VerifyingKey, transcript: &mut T, - ) -> Result, Error> { + ) -> Result, Error> + where + CS::Commitment: Hashable + SerdeObject, + { let chunk_len = vk.cs_degree - 2; let permutation_product_commitments = self .columns .chunks(chunk_len) - .map(|_| transcript.read_point()) + .map(|_| transcript.read()) .collect::, _>>()?; Ok(Committed { @@ -53,35 +57,42 @@ impl Argument { } } -impl VerifyingKey { - pub(in crate::plonk) fn evaluate, T: TranscriptRead>( +impl> VerifyingKey { + pub(in crate::plonk) fn evaluate( &self, transcript: &mut T, - ) -> Result, Error> { + ) -> Result, Error> + where + F: Hashable + SerdeObject, + { let permutation_evals = self .commitments .iter() - .map(|_| transcript.read_scalar()) + .map(|_| transcript.read()) .collect::, _>>()?; Ok(CommonEvaluated { permutation_evals }) } } -impl Committed { - pub(crate) fn evaluate, T: TranscriptRead>( +impl> Committed { + pub(crate) fn evaluate( self, transcript: &mut T, - ) -> Result, Error> { + ) -> Result, Error> + where + CS::Commitment: Hashable + SerdeObject, + F: Hashable + SerdeObject, + { let mut sets = vec![]; let mut iter = self.permutation_product_commitments.into_iter(); while let Some(permutation_product_commitment) = iter.next() { - let permutation_product_eval = transcript.read_scalar()?; - let permutation_product_next_eval = transcript.read_scalar()?; + let permutation_product_eval = transcript.read()?; + let permutation_product_next_eval = transcript.read()?; let permutation_product_last_eval = if iter.len() > 0 { - Some(transcript.read_scalar()?) + Some(transcript.read()?) } else { None }; @@ -98,23 +109,23 @@ impl Committed { } } -impl Evaluated { +impl, CS: PolynomialCommitmentScheme> Evaluated { #[allow(clippy::too_many_arguments)] pub(in crate::plonk) fn expressions<'a>( &'a self, - vk: &'a plonk::VerifyingKey, + vk: &'a plonk::VerifyingKey, p: &'a Argument, - common: &'a CommonEvaluated, - advice_evals: &'a [C::Scalar], - fixed_evals: &'a [C::Scalar], - instance_evals: &'a [C::Scalar], - l_0: C::Scalar, - l_last: C::Scalar, - l_blind: C::Scalar, - beta: ChallengeBeta, - gamma: ChallengeGamma, - x: ChallengeX, - ) -> impl Iterator + 'a { + common: &'a CommonEvaluated, + advice_evals: &'a [F], + fixed_evals: &'a [F], + instance_evals: &'a [F], + l_0: F, + l_last: F, + l_blind: F, + beta: F, + gamma: F, + x: F, + ) -> impl Iterator + 'a { let chunk_len = vk.cs_degree - 2; iter::empty() // Enforce only for the first set. @@ -122,7 +133,7 @@ impl Evaluated { .chain( self.sets .first() - .map(|first_set| l_0 * &(C::Scalar::ONE - &first_set.permutation_product_eval)), + .map(|first_set| l_0 * &(F::ONE - &first_set.permutation_product_eval)), ) // Enforce only for the last set. // l_last(X) * (z_l(X)^2 - z_l(X)) = 0 @@ -174,12 +185,12 @@ impl Evaluated { }) .zip(permutation_evals.iter()) { - left *= &(eval + &(*beta * permutation_eval) + &*gamma); + left *= &(eval + &(beta * permutation_eval) + &gamma); } let mut right = set.permutation_product_eval; - let mut current_delta = (*beta * &*x) - * &(::DELTA + let mut current_delta = (beta * &x) + * &(::DELTA .pow_vartime([(chunk_index * chunk_len) as u64])); for eval in columns.iter().map(|&column| match column.column_type() { Any::Advice(_) => { @@ -192,63 +203,65 @@ impl Evaluated { instance_evals[vk.cs.get_any_query_index(column, Rotation::cur())] } }) { - right *= &(eval + ¤t_delta + &*gamma); - current_delta *= &C::Scalar::DELTA; + right *= &(eval + ¤t_delta + &gamma); + current_delta *= &F::DELTA; } - (left - &right) * (C::Scalar::ONE - &(l_last + &l_blind)) + (left - &right) * (F::ONE - &(l_last + &l_blind)) }), ) } - pub(in crate::plonk) fn queries<'r, M: MSM + 'r>( + // todo: Removing the MSM tricks here. Bring them back + pub(in crate::plonk) fn queries<'r>( &'r self, - vk: &'r plonk::VerifyingKey, - x: ChallengeX, - ) -> impl Iterator> + Clone { + vk: &'r plonk::VerifyingKey, + x: F, + ) -> impl Iterator> + Clone + 'r { let blinding_factors = vk.cs.blinding_factors(); - let x_next = vk.domain.rotate_omega(*x, Rotation::next()); + let x_next = vk.domain.rotate_omega(x, Rotation::next()); let x_last = vk .domain - .rotate_omega(*x, Rotation(-((blinding_factors + 1) as i32))); + .rotate_omega(x, Rotation(-((blinding_factors + 1) as i32))); iter::empty() .chain(self.sets.iter().flat_map(move |set| { iter::empty() // Open permutation product commitments at x and \omega^{-1} x // Open permutation product commitments at x and \omega x - .chain(Some(VerifierQuery::new_commitment( - &set.permutation_product_commitment, - *x, + .chain(Some(VerifierQuery::new( + x, + set.permutation_product_commitment, set.permutation_product_eval, ))) - .chain(Some(VerifierQuery::new_commitment( - &set.permutation_product_commitment, + .chain(Some(VerifierQuery::new( x_next, + set.permutation_product_commitment, set.permutation_product_next_eval, ))) })) // Open it at \omega^{last} x for all but the last set .chain(self.sets.iter().rev().skip(1).flat_map(move |set| { - Some(VerifierQuery::new_commitment( - &set.permutation_product_commitment, + Some(VerifierQuery::new( x_last, + set.permutation_product_commitment, set.permutation_product_last_eval.unwrap(), )) })) } } -impl CommonEvaluated { - pub(in crate::plonk) fn queries<'r, M: MSM + 'r>( +impl CommonEvaluated { + // todo: MSM trick here + pub(in crate::plonk) fn queries<'r, CS: PolynomialCommitmentScheme>( &'r self, - vkey: &'r VerifyingKey, - x: ChallengeX, - ) -> impl Iterator> + Clone { + vkey: &'r VerifyingKey, + x: F, + ) -> impl Iterator> + Clone + 'r { // Open permutation commitments for each permutation argument at x vkey.commitments .iter() .zip(self.permutation_evals.iter()) - .map(move |(commitment, &eval)| VerifierQuery::new_commitment(commitment, *x, eval)) + .map(move |(&commitment, &eval)| VerifierQuery::new(x, commitment, eval)) } } diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 50cbd10ab7..bf7cb13a36 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -1,6 +1,5 @@ -use ff::{Field, FromUniformBytes, WithSmallOrderMulGroup}; -use group::Curve; -use rand_core::RngCore; +use ff::{Field, PrimeField, WithSmallOrderMulGroup}; +use rand_core::{CryptoRng, RngCore}; use std::collections::{BTreeSet, HashSet}; use std::ops::RangeTo; use std::{collections::HashMap, iter}; @@ -11,47 +10,43 @@ use super::{ Advice, Any, Assignment, Challenge, Circuit, Column, ConstraintSystem, Fixed, FloorPlanner, Instance, Selector, }, - lookup, permutation, vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, - ChallengeY, Error, ProvingKey, + lookup, permutation, vanishing, Error, ProvingKey, }; use crate::{ - arithmetic::{eval_polynomial, CurveAffine}, - circuit::Value, - plonk::Assigned, - poly::{ - commitment::{Blind, CommitmentScheme, Params, Prover}, - Basis, Coeff, LagrangeCoeff, Polynomial, ProverQuery, - }, -}; -use crate::{ - poly::batch_invert_assigned, - transcript::{EncodedChallenge, TranscriptWrite}, + arithmetic::eval_polynomial, + // circuit::Value, + // plonk::Assigned, + poly::{Basis, Coeff, LagrangeCoeff, Polynomial, ProverQuery}, }; -use group::prime::PrimeCurveAffine; + +use crate::circuit::Value; +use crate::poly::batch_invert_rational; +use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; +use crate::rational::Rational; +use crate::transcript::{Hashable, Sampleable, Transcript}; +use halo2curves::serde::SerdeObject; /// This creates a proof for the provided `circuit` when given the public /// parameters `params` and the proving key [`ProvingKey`] that was /// generated previously for the same circuit. The provided `instances` /// are zero-padded internally. pub fn create_proof< - 'params, - Scheme: CommitmentScheme, - P: Prover<'params, Scheme>, - E: EncodedChallenge, - R: RngCore, - T: TranscriptWrite, - ConcreteCircuit: Circuit, + F: WithSmallOrderMulGroup<3>, + CS: PolynomialCommitmentScheme, + T: Transcript, + ConcreteCircuit: Circuit, >( - params: &'params Scheme::ParamsProver, - pk: &ProvingKey, + params: &CS::Parameters, + pk: &ProvingKey, circuits: &[ConcreteCircuit], - instances: &[&[&[Scheme::Scalar]]], - mut rng: R, + instances: &[&[&[F]]], + mut rng: impl RngCore + CryptoRng, transcript: &mut T, ) -> Result<(), Error> where - Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + CS::Commitment: Hashable + SerdeObject, + F: Sampleable + Hashable + SerdeObject + Ord, { if circuits.len() != instances.len() { return Err(Error::InvalidInstances); @@ -77,14 +72,14 @@ where // from the verification key. let meta = &pk.vk.cs; - struct InstanceSingle { - pub instance_values: Vec>, - pub instance_polys: Vec>, + struct InstanceSingle { + pub instance_values: Vec>, + pub instance_polys: Vec>, } - let instance: Vec> = instances + let instance: Vec> = instances .iter() - .map(|instance| -> Result, Error> { + .map(|instance| -> Result, Error> { let instance_values = instance .iter() .map(|values| { @@ -94,34 +89,13 @@ where return Err(Error::InstanceTooLarge); } for (poly, value) in poly.iter_mut().zip(values.iter()) { - if !P::QUERY_INSTANCE { - transcript.common_scalar(*value)?; - } + transcript.common(value)?; *poly = *value; } Ok(poly) }) .collect::, _>>()?; - if P::QUERY_INSTANCE { - let instance_commitments_projective: Vec<_> = instance_values - .iter() - .map(|poly| params.commit_lagrange(poly, Blind::default())) - .collect(); - let mut instance_commitments = - vec![Scheme::Curve::identity(); instance_commitments_projective.len()]; - ::CurveExt::batch_normalize( - &instance_commitments_projective, - &mut instance_commitments, - ); - let instance_commitments = instance_commitments; - drop(instance_commitments_projective); - - for commitment in &instance_commitments { - transcript.common_point(*commitment)?; - } - } - let instance_polys: Vec<_> = instance_values .iter() .map(|poly| { @@ -138,15 +112,14 @@ where .collect::, _>>()?; #[derive(Clone)] - struct AdviceSingle { - pub advice_polys: Vec>, - pub advice_blinds: Vec>, + struct AdviceSingle { + pub advice_polys: Vec>, } struct WitnessCollection<'a, F: Field> { k: u32, current_phase: sealed::Phase, - advice: Vec, LagrangeCoeff>>, + advice: Vec, LagrangeCoeff>>, unblinded_advice: HashSet, challenges: &'a HashMap, instances: &'a [&'a [F]], @@ -206,7 +179,7 @@ where ) -> Result<(), Error> where V: FnOnce() -> Value, - VR: Into>, + VR: Into>, A: FnOnce() -> AR, AR: Into, { @@ -237,7 +210,7 @@ where ) -> Result<(), Error> where V: FnOnce() -> Value, - VR: Into>, + VR: Into>, A: FnOnce() -> AR, AR: Into, { @@ -262,7 +235,7 @@ where &mut self, _: Column, _: usize, - _: Value>, + _: Value>, ) -> Result<(), Error> { Ok(()) } @@ -290,13 +263,12 @@ where let (advice, challenges) = { let mut advice = vec![ - AdviceSingle:: { + AdviceSingle:: { advice_polys: vec![domain.empty_lagrange(); meta.num_advice_columns], - advice_blinds: vec![Blind::default(); meta.num_advice_columns], }; instances.len() ]; - let mut challenges = HashMap::::with_capacity(meta.num_challenges); + let mut challenges = HashMap::::with_capacity(meta.num_challenges); let unusable_rows_start = params.n() as usize - (meta.blinding_factors() + 1); for current_phase in pk.vk.cs.phases() { @@ -319,7 +291,7 @@ where let mut witness = WitnessCollection { k: params.k(), current_phase, - advice: vec![domain.empty_lagrange_assigned(); meta.num_advice_columns], + advice: vec![domain.empty_lagrange_rational(); meta.num_advice_columns], unblinded_advice: HashSet::from_iter(meta.unblinded_advice_columns.clone()), instances, challenges: &challenges, @@ -339,7 +311,7 @@ where meta.constants.clone(), )?; - let mut advice_values = batch_invert_assigned::( + let mut advice_values = batch_invert_rational::( witness .advice .into_iter() @@ -354,60 +326,35 @@ where .collect(), ); - // Add blinding factors to advice columns for (column_index, advice_values) in column_indices.iter().zip(&mut advice_values) { if !witness.unblinded_advice.contains(column_index) { for cell in &mut advice_values[unusable_rows_start..] { - *cell = Scheme::Scalar::random(&mut rng); + *cell = F::random(&mut rng); } } else { #[cfg(feature = "sanity-checks")] for cell in &advice_values[unusable_rows_start..] { - assert_eq!(*cell, Scheme::Scalar::ZERO); + assert_eq!(*cell, F::ZERO); } } } - // Compute commitments to advice column polynomials - let blinds: Vec<_> = column_indices + let advice_commitments: Vec<_> = advice_values .iter() - .map(|i| { - if witness.unblinded_advice.contains(i) { - Blind::default() - } else { - Blind(Scheme::Scalar::random(&mut rng)) - } - }) + .map(|poly| params.commit_lagrange(poly)) .collect(); - let advice_commitments_projective: Vec<_> = advice_values - .iter() - .zip(blinds.iter()) - .map(|(poly, blind)| params.commit_lagrange(poly, *blind)) - .collect(); - let mut advice_commitments = - vec![Scheme::Curve::identity(); advice_commitments_projective.len()]; - ::CurveExt::batch_normalize( - &advice_commitments_projective, - &mut advice_commitments, - ); - let advice_commitments = advice_commitments; - drop(advice_commitments_projective); for commitment in &advice_commitments { - transcript.write_point(*commitment)?; + transcript.write(commitment)?; } - for ((column_index, advice_values), blind) in - column_indices.iter().zip(advice_values).zip(blinds) - { + for (column_index, advice_values) in column_indices.iter().zip(advice_values) { advice.advice_polys[*column_index] = advice_values; - advice.advice_blinds[*column_index] = blind; } } for (index, phase) in meta.challenge_phase.iter().enumerate() { if current_phase == *phase { - let existing = - challenges.insert(index, *transcript.squeeze_challenge_scalar::<()>()); + let existing = challenges.insert(index, transcript.squeeze_challenge()); assert!(existing.is_none()); } } @@ -422,9 +369,9 @@ where }; // Sample theta challenge for keeping lookup columns linearly independent - let theta: ChallengeTheta<_> = transcript.squeeze_challenge_scalar(); + let theta: F = transcript.squeeze_challenge(); - let lookups: Vec>> = instance + let lookups: Vec>> = instance .iter() .zip(advice.iter()) .map(|(instance, advice)| -> Result, Error> { @@ -452,13 +399,13 @@ where .collect::, _>>()?; // Sample beta challenge - let beta: ChallengeBeta<_> = transcript.squeeze_challenge_scalar(); + let beta: F = transcript.squeeze_challenge(); // Sample gamma challenge - let gamma: ChallengeGamma<_> = transcript.squeeze_challenge_scalar(); + let gamma: F = transcript.squeeze_challenge(); // Commit to permutations. - let permutations: Vec> = instance + let permutations: Vec> = instance .iter() .zip(advice.iter()) .map(|(instance, advice)| { @@ -477,7 +424,7 @@ where }) .collect::, _>>()?; - let lookups: Vec>> = lookups + let lookups: Vec>> = lookups .into_iter() .map(|lookups| -> Result, _> { // Construct and commit to products for each lookup @@ -489,28 +436,20 @@ where .collect::, _>>()?; // Commit to the vanishing argument's random polynomial for blinding h(x_3) - let vanishing = vanishing::Argument::commit(params, domain, &mut rng, transcript)?; + let vanishing = vanishing::Argument::::commit(params, domain, &mut rng, transcript)?; // Obtain challenge for keeping all separate gates linearly independent - let y: ChallengeY<_> = transcript.squeeze_challenge_scalar(); + let y: F = transcript.squeeze_challenge(); // Calculate the advice polys - let advice: Vec> = advice + let advice: Vec> = advice .into_iter() - .map( - |AdviceSingle { - advice_polys, - advice_blinds, - }| { - AdviceSingle { - advice_polys: advice_polys - .into_iter() - .map(|poly| domain.lagrange_to_coeff(poly)) - .collect::>(), - advice_blinds, - } - }, - ) + .map(|AdviceSingle { advice_polys }| AdviceSingle { + advice_polys: advice_polys + .into_iter() + .map(|poly| domain.lagrange_to_coeff(poly)) + .collect::>(), + }) .collect(); // Evaluate the h(X) polynomial @@ -525,42 +464,20 @@ where .map(|i| i.instance_polys.as_slice()) .collect::>(), &challenges, - *y, - *beta, - *gamma, - *theta, + y, + beta, + gamma, + theta, &lookups, &permutations, ); // Construct the vanishing argument's h(X) commitments - let vanishing = vanishing.construct(params, domain, h_poly, &mut rng, transcript)?; + let vanishing = vanishing.construct::(params, domain, h_poly, transcript)?; - let x: ChallengeX<_> = transcript.squeeze_challenge_scalar(); + let x: F = transcript.squeeze_challenge(); let xn = x.pow([params.n()]); - if P::QUERY_INSTANCE { - // Compute and hash instance evals for each circuit instance - for instance in instance.iter() { - // Evaluate polynomials at omega^i x - let instance_evals: Vec<_> = meta - .instance_queries - .iter() - .map(|&(column, at)| { - eval_polynomial( - &instance.instance_polys[column.index()], - domain.rotate_omega(*x, at), - ) - }) - .collect(); - - // Hash each instance column evaluation - for eval in instance_evals.iter() { - transcript.write_scalar(*eval)?; - } - } - } - // Compute and hash advice evals for each circuit instance for advice in advice.iter() { // Evaluate polynomials at omega^i x @@ -570,14 +487,14 @@ where .map(|&(column, at)| { eval_polynomial( &advice.advice_polys[column.index()], - domain.rotate_omega(*x, at), + domain.rotate_omega(x, at), ) }) .collect(); // Hash each advice column evaluation for eval in advice_evals.iter() { - transcript.write_scalar(*eval)?; + transcript.write(eval)?; } } @@ -586,13 +503,13 @@ where .fixed_queries .iter() .map(|&(column, at)| { - eval_polynomial(&pk.fixed_polys[column.index()], domain.rotate_omega(*x, at)) + eval_polynomial(&pk.fixed_polys[column.index()], domain.rotate_omega(x, at)) }) .collect(); // Hash each fixed column evaluation for eval in fixed_evals.iter() { - transcript.write_scalar(*eval)?; + transcript.write(eval)?; } let vanishing = vanishing.evaluate(x, xn, domain, transcript)?; @@ -601,13 +518,13 @@ where pk.permutation.evaluate(x, transcript)?; // Evaluate the permutations, if any, at omega^i x. - let permutations: Vec> = permutations + let permutations: Vec> = permutations .into_iter() .map(|permutation| -> Result<_, _> { permutation.construct().evaluate(pk, x, transcript) }) .collect::, _>>()?; // Evaluate the lookups, if any, at omega^i x. - let lookups: Vec>> = lookups + let lookups: Vec>> = lookups .into_iter() .map(|lookups| -> Result, _> { lookups @@ -617,31 +534,19 @@ where }) .collect::, _>>()?; - let instances = instance + let instances = advice .iter() - .zip(advice.iter()) .zip(permutations.iter()) .zip(lookups.iter()) - .flat_map(|(((instance, advice), permutation), lookups)| { + .flat_map(|((advice, permutation), lookups)| { iter::empty() - .chain( - P::QUERY_INSTANCE - .then_some(pk.vk.cs.instance_queries.iter().map(move |&(column, at)| { - ProverQuery { - point: domain.rotate_omega(*x, at), - poly: &instance.instance_polys[column.index()], - } - })) - .into_iter() - .flatten(), - ) .chain( pk.vk .cs .advice_queries .iter() .map(move |&(column, at)| ProverQuery { - point: domain.rotate_omega(*x, at), + point: domain.rotate_omega(x, at), poly: &advice.advice_polys[column.index()], }), ) @@ -654,7 +559,7 @@ where .fixed_queries .iter() .map(|&(column, at)| ProverQuery { - point: domain.rotate_omega(*x, at), + point: domain.rotate_omega(x, at), poly: &pk.fixed_polys[column.index()], }), ) @@ -662,10 +567,7 @@ where // We query the h(X) polynomial at x .chain(vanishing.open(x)); - let prover = P::new(params); - prover - .create_proof(rng, transcript, instances) - .map_err(|_| Error::ConstraintSystemFailure) + CS::open(params, instances, transcript).map_err(|_| Error::ConstraintSystemFailure) } #[test] @@ -673,13 +575,11 @@ fn test_create_proof() { use crate::{ circuit::SimpleFloorPlanner, plonk::{keygen_pk, keygen_vk}, - poly::kzg::{ - commitment::{KZGCommitmentScheme, ParamsKZG}, - gwc::ProverGWC, - }, - transcript::{Blake2bWrite, Challenge255, TranscriptWriterBuffer}, + poly::kzg::{params::ParamsKZG, KZGCommitmentScheme}, + transcript::CircuitTranscript, }; use halo2curves::bn256::Bn256; + use halo2curves::bn256::Fr; use rand_core::OsRng; #[derive(Clone, Copy)] @@ -706,13 +606,13 @@ fn test_create_proof() { } } - let params: ParamsKZG = ParamsKZG::setup(3, OsRng); + let params: ParamsKZG = KZGCommitmentScheme::setup(3); let vk = keygen_vk(¶ms, &MyCircuit).expect("keygen_vk should not fail"); let pk = keygen_pk(¶ms, vk, &MyCircuit).expect("keygen_pk should not fail"); - let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + let mut transcript = CircuitTranscript::<_>::init(); // Create proof with wrong number of instances - let proof = create_proof::, ProverGWC<_>, _, _, _, _>( + let proof = create_proof::, _, _>( ¶ms, &pk, &[MyCircuit, MyCircuit], @@ -723,7 +623,7 @@ fn test_create_proof() { assert!(matches!(proof.unwrap_err(), Error::InvalidInstances)); // Create proof with correct number of instances - create_proof::, ProverGWC<_>, _, _, _, _>( + create_proof::, _, _>( ¶ms, &pk, &[MyCircuit, MyCircuit], diff --git a/src/plonk/vanishing.rs b/src/plonk/vanishing.rs index 81f86b02e2..c1cc09b3fd 100644 --- a/src/plonk/vanishing.rs +++ b/src/plonk/vanishing.rs @@ -1,11 +1,13 @@ +use ff::PrimeField; use std::marker::PhantomData; -use crate::arithmetic::CurveAffine; +use crate::poly::commitment::PolynomialCommitmentScheme; mod prover; mod verifier; /// A vanishing argument. -pub(crate) struct Argument { - _marker: PhantomData, +/// todo: do we need to have the PCS here? +pub(crate) struct Argument> { + _marker: PhantomData<(F, CS)>, } diff --git a/src/plonk/vanishing/prover.rs b/src/plonk/vanishing/prover.rs index aff2595a46..52f7f7a7b0 100644 --- a/src/plonk/vanishing/prover.rs +++ b/src/plonk/vanishing/prover.rs @@ -1,52 +1,48 @@ use std::{collections::HashMap, iter}; -use ff::Field; -use group::Curve; +use ff::{PrimeField, WithSmallOrderMulGroup}; +use halo2curves::serde::SerdeObject; use rand_chacha::ChaCha20Rng; use rand_core::{RngCore, SeedableRng}; use super::Argument; +use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; +use crate::transcript::{Hashable, Transcript}; use crate::{ - arithmetic::{eval_polynomial, parallelize, CurveAffine}, + arithmetic::{eval_polynomial, parallelize}, multicore::current_num_threads, - plonk::{ChallengeX, Error}, - poly::{ - commitment::{Blind, ParamsProver}, - Coeff, EvaluationDomain, ExtendedLagrangeCoeff, Polynomial, ProverQuery, - }, - transcript::{EncodedChallenge, TranscriptWrite}, + plonk::Error, + poly::{Coeff, EvaluationDomain, ExtendedLagrangeCoeff, Polynomial, ProverQuery}, }; -pub(in crate::plonk) struct Committed { - random_poly: Polynomial, +pub(in crate::plonk) struct Committed { + random_poly: Polynomial, } -pub(in crate::plonk) struct Constructed { - h_pieces: Vec>, - committed: Committed, +pub(in crate::plonk) struct Constructed { + h_pieces: Vec>, + committed: Committed, } -pub(in crate::plonk) struct Evaluated { - h_poly: Polynomial, - committed: Committed, +pub(in crate::plonk) struct Evaluated { + h_pieces: Vec>, + committed: Committed, } -impl Argument { - pub(in crate::plonk) fn commit< - 'params, - P: ParamsProver<'params, C>, - E: EncodedChallenge, - R: RngCore, - T: TranscriptWrite, - >( - params: &P, - domain: &EvaluationDomain, +impl, CS: PolynomialCommitmentScheme> Argument { + pub(in crate::plonk) fn commit( + params: &CS::Parameters, + domain: &EvaluationDomain, mut rng: R, transcript: &mut T, - ) -> Result, Error> { + ) -> Result, Error> + where + CS::Commitment: Hashable + SerdeObject, + F: Hashable + SerdeObject, + { // Sample a random polynomial of degree n - 1 let n = 1usize << domain.k() as usize; - let mut rand_vec = vec![C::Scalar::ZERO; n]; + let mut rand_vec = vec![F::ZERO; n]; let num_threads = current_num_threads(); let chunk_size = n / num_threads; @@ -69,39 +65,31 @@ impl Argument { parallelize(&mut rand_vec, |chunk, offset| { let mut rng = thread_seeds[&offset].clone(); - chunk - .iter_mut() - .for_each(|v| *v = C::Scalar::random(&mut rng)); + chunk.iter_mut().for_each(|v| *v = F::random(&mut rng)); }); - let random_poly: Polynomial = domain.coeff_from_vec(rand_vec); - - // Sample a random blinding factor - let random_blind = Blind(C::Scalar::random(rng)); + let random_poly: Polynomial = domain.coeff_from_vec(rand_vec); // Commit - let c = params.commit(&random_poly, random_blind).to_affine(); - transcript.write_point(c)?; + let c = CS::commit(params, &random_poly); + transcript.write(&c)?; Ok(Committed { random_poly }) } } -impl Committed { - pub(in crate::plonk) fn construct< - 'params, - P: ParamsProver<'params, C>, - E: EncodedChallenge, - R: RngCore, - T: TranscriptWrite, - >( +impl> Committed { + pub(in crate::plonk) fn construct, T: Transcript>( self, - params: &P, - domain: &EvaluationDomain, - h_poly: Polynomial, - mut rng: R, + params: &CS::Parameters, + domain: &EvaluationDomain, + h_poly: Polynomial, transcript: &mut T, - ) -> Result, Error> { + ) -> Result, Error> + where + CS::Commitment: Hashable + SerdeObject, + F: Hashable + SerdeObject, + { // Divide by t(X) = X^{params.n} - 1. let h_poly = domain.divide_by_vanishing_poly(h_poly); @@ -114,24 +102,16 @@ impl Committed { .map(|v| domain.coeff_from_vec(v.to_vec())) .collect::>(); drop(h_poly); - let h_blinds: Vec<_> = h_pieces - .iter() - .map(|_| Blind(C::Scalar::random(&mut rng))) - .collect(); // Compute commitments to each h(X) piece - let h_commitments_projective: Vec<_> = h_pieces + let h_commitments: Vec<_> = h_pieces .iter() - .zip(h_blinds.iter()) - .map(|(h_piece, blind)| params.commit(h_piece, *blind)) + .map(|h_piece| CS::commit(params, h_piece)) .collect(); - let mut h_commitments = vec![C::identity(); h_commitments_projective.len()]; - C::Curve::batch_normalize(&h_commitments_projective, &mut h_commitments); - let h_commitments = h_commitments; // Hash each h(X) piece for c in h_commitments.iter() { - transcript.write_point(*c)?; + transcript.write(c)?; } Ok(Constructed { @@ -141,42 +121,46 @@ impl Committed { } } -impl Constructed { - pub(in crate::plonk) fn evaluate, T: TranscriptWrite>( +impl Constructed { + pub(in crate::plonk) fn evaluate( self, - x: ChallengeX, - xn: C::Scalar, - domain: &EvaluationDomain, + x: F, + _xn: F, + _domain: &EvaluationDomain, transcript: &mut T, - ) -> Result, Error> { - let h_poly = self - .h_pieces + ) -> Result, Error> + where + F: Hashable + SerdeObject, + { + self.h_pieces .iter() - .rev() - .fold(domain.empty_coeff(), |acc, eval| acc * xn + eval); + // .fold(domain.empty_coeff(), |acc, eval| acc * xn + eval); + .try_for_each(|p| { + let random_eval = eval_polynomial(p, x); + transcript.write(&random_eval) + })?; - let random_eval = eval_polynomial(&self.committed.random_poly, *x); - transcript.write_scalar(random_eval)?; + let random_eval = eval_polynomial(&self.committed.random_poly, x); + transcript.write(&random_eval)?; Ok(Evaluated { - h_poly, + // h_poly, + h_pieces: self.h_pieces, committed: self.committed, }) } } -impl Evaluated { - pub(in crate::plonk) fn open( - &self, - x: ChallengeX, - ) -> impl Iterator> + Clone { +impl Evaluated { + pub(in crate::plonk) fn open(&self, x: F) -> impl Iterator> + Clone { iter::empty() + .chain( + self.h_pieces + .iter() + .map(move |p| ProverQuery { point: x, poly: p }), + ) .chain(Some(ProverQuery { - point: *x, - poly: &self.h_poly, - })) - .chain(Some(ProverQuery { - point: *x, + point: x, poly: &self.committed.random_poly, })) } diff --git a/src/plonk/vanishing/verifier.rs b/src/plonk/vanishing/verifier.rs index 0881dfb2c0..687993303b 100644 --- a/src/plonk/vanishing/verifier.rs +++ b/src/plonk/vanishing/verifier.rs @@ -1,50 +1,41 @@ use std::iter; -use ff::Field; +use ff::{PrimeField, WithSmallOrderMulGroup}; +use halo2curves::serde::SerdeObject; +use crate::poly::commitment::PolynomialCommitmentScheme; +use crate::transcript::{read_n, Hashable, Transcript}; use crate::{ - arithmetic::CurveAffine, plonk::{Error, VerifyingKey}, - poly::{ - commitment::{Params, MSM}, - VerifierQuery, - }, - transcript::{read_n_points, EncodedChallenge, TranscriptRead}, + poly::VerifierQuery, }; -use super::super::{ChallengeX, ChallengeY}; use super::Argument; -pub struct Committed { - random_poly_commitment: C, +pub struct Committed> { + random_poly_commitment: CS::Commitment, } -pub struct Constructed { - h_commitments: Vec, - random_poly_commitment: C, +pub struct Constructed> { + h_commitments: Vec, + random_poly_commitment: CS::Commitment, } -pub struct PartiallyEvaluated { - h_commitments: Vec, - random_poly_commitment: C, - random_eval: C::Scalar, +pub struct Evaluated> { + h_commitments: Vec, + random_poly_commitment: CS::Commitment, + h_evals: Vec, + random_eval: F, } -pub struct Evaluated> { - h_commitment: M, - random_poly_commitment: C, - expected_h_eval: C::Scalar, - random_eval: C::Scalar, -} - -impl Argument { - pub(in crate::plonk) fn read_commitments_before_y< - E: EncodedChallenge, - T: TranscriptRead, - >( +impl> Argument { + pub(in crate::plonk) fn read_commitments_before_y( transcript: &mut T, - ) -> Result, Error> { - let random_poly_commitment = transcript.read_point()?; + ) -> Result, Error> + where + CS::Commitment: Hashable + SerdeObject, + { + let random_poly_commitment = transcript.read()?; Ok(Committed { random_poly_commitment, @@ -52,17 +43,17 @@ impl Argument { } } -impl Committed { - pub(in crate::plonk) fn read_commitments_after_y< - E: EncodedChallenge, - T: TranscriptRead, - >( +impl, CS: PolynomialCommitmentScheme> Committed { + pub(in crate::plonk) fn read_commitments_after_y( self, - vk: &VerifyingKey, + vk: &VerifyingKey, transcript: &mut T, - ) -> Result, Error> { + ) -> Result, Error> + where + CS::Commitment: Hashable + SerdeObject, + { // Obtain a commitment to h(X) in the form of multiple pieces of degree n - 1 - let h_commitments = read_n_points(transcript, vk.domain.get_quotient_poly_degree())?; + let h_commitments = read_n(transcript, vk.domain.get_quotient_poly_degree())?; Ok(Constructed { h_commitments, @@ -71,67 +62,64 @@ impl Committed { } } -impl Constructed { - pub(in crate::plonk) fn evaluate_after_x, T: TranscriptRead>( +impl, CS: PolynomialCommitmentScheme> Constructed { + pub(in crate::plonk) fn evaluate_after_x( self, + vk: &VerifyingKey, transcript: &mut T, - ) -> Result, Error> { - let random_eval = transcript.read_scalar()?; - - Ok(PartiallyEvaluated { + ) -> Result, Error> + where + F: Hashable + SerdeObject, + { + let h_evals = read_n(transcript, vk.domain.get_quotient_poly_degree())?; + let random_eval = transcript.read()?; + + Ok(Evaluated { h_commitments: self.h_commitments, random_poly_commitment: self.random_poly_commitment, + h_evals, random_eval, }) } } -impl PartiallyEvaluated { - pub(in crate::plonk) fn verify<'params, P: Params<'params, C>>( +impl> Evaluated { + pub(in crate::plonk) fn verify( self, - params: &'params P, - expressions: impl Iterator, - y: ChallengeY, - xn: C::Scalar, - ) -> Evaluated { - let expected_h_eval = expressions.fold(C::Scalar::ZERO, |h_eval, v| h_eval * &*y + &v); - let expected_h_eval = expected_h_eval * ((xn - C::Scalar::ONE).invert().unwrap()); - - let h_commitment = - self.h_commitments - .iter() - .rev() - .fold(params.empty_msm(), |mut acc, commitment| { - acc.scale(xn); - let commitment: C::CurveExt = (*commitment).into(); - acc.append_term(C::Scalar::ONE, commitment); - - acc - }); - - Evaluated { - expected_h_eval, - h_commitment, - random_poly_commitment: self.random_poly_commitment, - random_eval: self.random_eval, - } + expressions: impl Iterator, + y: F, + xn: F, + ) -> Evaluated { + let committed_h_eval = self + .h_evals + .iter() + .rev() + .fold(F::ZERO, |acc, eval| acc * xn + eval); + + let expected_h_eval = expressions.fold(F::ZERO, |h_eval, v| h_eval * &y + &v); + let expected_h_eval = expected_h_eval * ((xn - F::ONE).invert().unwrap()); + + assert_eq!(committed_h_eval, expected_h_eval); + + self } } -impl> Evaluated { +impl> Evaluated { pub(in crate::plonk) fn queries( &self, - x: ChallengeX, - ) -> impl Iterator> + Clone { + x: F, + ) -> impl Iterator> + Clone + '_ { iter::empty() - .chain(Some(VerifierQuery::new_msm( - &self.h_commitment, - *x, - self.expected_h_eval, - ))) - .chain(Some(VerifierQuery::new_commitment( - &self.random_poly_commitment, - *x, + .chain( + self.h_commitments + .iter() + .zip(self.h_evals.iter()) + .map(move |(&c, e)| VerifierQuery::new(x, c, *e)), + ) + .chain(Some(VerifierQuery::new( + x, + self.random_poly_commitment, self.random_eval, ))) } diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 587fa8186f..9378e9cb4e 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -1,37 +1,26 @@ -use ff::{Field, FromUniformBytes, WithSmallOrderMulGroup}; -use group::Curve; +use ff::WithSmallOrderMulGroup; +use halo2curves::serde::SerdeObject; use std::iter; -use super::{ - vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, ChallengeY, Error, - VerifyingKey, -}; +use super::{vanishing, Error, VerifyingKey}; use crate::arithmetic::compute_inner_product; -use crate::poly::commitment::{CommitmentScheme, Verifier}; -use crate::poly::VerificationStrategy; -use crate::poly::{ - commitment::{Blind, Params}, - VerifierQuery, -}; -use crate::transcript::{read_n_scalars, EncodedChallenge, TranscriptRead}; +use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; +use crate::poly::VerifierQuery; +use crate::transcript::{read_n, Hashable, Sampleable, Transcript}; /// Returns a boolean indicating whether or not the proof is valid pub fn verify_proof< - 'params, - Scheme: CommitmentScheme, - V: Verifier<'params, Scheme>, - E: EncodedChallenge, - T: TranscriptRead, - Strategy: VerificationStrategy<'params, Scheme, V>, + F: WithSmallOrderMulGroup<3> + Hashable + Sampleable + SerdeObject, + CS: PolynomialCommitmentScheme, + T: Transcript, >( - params: &'params Scheme::ParamsVerifier, - vk: &VerifyingKey, - strategy: Strategy, - instances: &[&[&[Scheme::Scalar]]], + params: &CS::VerifierParameters, + vk: &VerifyingKey, + instances: &[&[&[F]]], transcript: &mut T, -) -> Result +) -> Result<(), Error> where - Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + CS::Commitment: Hashable + SerdeObject, { // Check that instances matches the expected number of instance columns for instances in instances.iter() { @@ -40,47 +29,15 @@ where } } - let instance_commitments = if V::QUERY_INSTANCE { - instances - .iter() - .map(|instance| { - instance - .iter() - .map(|instance| { - if instance.len() > params.n() as usize - (vk.cs.blinding_factors() + 1) { - return Err(Error::InstanceTooLarge); - } - let mut poly = instance.to_vec(); - poly.resize(params.n() as usize, Scheme::Scalar::ZERO); - let poly = vk.domain.lagrange_from_vec(poly); - - Ok(params.commit_lagrange(&poly, Blind::default()).to_affine()) - }) - .collect::, _>>() - }) - .collect::, _>>()? - } else { - vec![vec![]; instances.len()] - }; - - let num_proofs = instance_commitments.len(); + let num_proofs = instances.len(); // Hash verification key into transcript vk.hash_into(transcript)?; - if V::QUERY_INSTANCE { - for instance_commitments in instance_commitments.iter() { - // Hash the instance (external) commitments into the transcript - for commitment in instance_commitments { - transcript.common_point(*commitment)? - } - } - } else { - for instance in instances.iter() { - for instance in instance.iter() { - for value in instance.iter() { - transcript.common_scalar(*value)?; - } + for instance in instances.iter() { + for instance in instance.iter() { + for value in instance.iter() { + transcript.common(value)?; } } } @@ -88,8 +45,8 @@ where // Hash the prover's advice commitments into the transcript and squeeze challenges let (advice_commitments, challenges) = { let mut advice_commitments = - vec![vec![Scheme::Curve::default(); vk.cs.num_advice_columns]; num_proofs]; - let mut challenges = vec![Scheme::Scalar::ZERO; vk.cs.num_challenges]; + vec![vec![CS::Commitment::default(); vk.cs.num_advice_columns]; num_proofs]; + let mut challenges = vec![F::ZERO; vk.cs.num_challenges]; for current_phase in vk.cs.phases() { for advice_commitments in advice_commitments.iter_mut() { @@ -100,13 +57,13 @@ where .zip(advice_commitments.iter_mut()) { if current_phase == *phase { - *commitment = transcript.read_point()?; + *commitment = transcript.read()?; } } } for (phase, challenge) in vk.cs.challenge_phase.iter().zip(challenges.iter_mut()) { if current_phase == *phase { - *challenge = *transcript.squeeze_challenge_scalar::<()>(); + *challenge = transcript.squeeze_challenge(); } } } @@ -115,7 +72,7 @@ where }; // Sample theta challenge for keeping lookup columns linearly independent - let theta: ChallengeTheta<_> = transcript.squeeze_challenge_scalar(); + let theta: F = transcript.squeeze_challenge(); let lookups_permuted = (0..num_proofs) .map(|_| -> Result, _> { @@ -129,10 +86,10 @@ where .collect::, _>>()?; // Sample beta challenge - let beta: ChallengeBeta<_> = transcript.squeeze_challenge_scalar(); + let beta: F = transcript.squeeze_challenge(); // Sample gamma challenge - let gamma: ChallengeGamma<_> = transcript.squeeze_challenge_scalar(); + let gamma: F = transcript.squeeze_challenge(); let permutations_committed = (0..num_proofs) .map(|_| { @@ -155,20 +112,14 @@ where let vanishing = vanishing::Argument::read_commitments_before_y(transcript)?; // Sample y challenge, which keeps the gates linearly independent. - let y: ChallengeY<_> = transcript.squeeze_challenge_scalar(); + let y: F = transcript.squeeze_challenge(); let vanishing = vanishing.read_commitments_after_y(vk, transcript)?; // Sample x challenge, which is used to ensure the circuit is // satisfied with high probability. - let x: ChallengeX<_> = transcript.squeeze_challenge_scalar(); - let instance_evals = if V::QUERY_INSTANCE { - (0..num_proofs) - .map(|_| -> Result, _> { - read_n_scalars(transcript, vk.cs.instance_queries.len()) - }) - .collect::, _>>()? - } else { + let x: F = transcript.squeeze_challenge(); + let instance_evals = { let xn = x.pow([params.n()]); let (min_rotation, max_rotation) = vk.cs @@ -189,7 +140,7 @@ where .max_by(Ord::cmp) .unwrap_or_default(); let l_i_s = &vk.domain.l_i_range( - *x, + x, xn, -max_rotation..max_instance_len as i32 + min_rotation.abs(), ); @@ -210,12 +161,12 @@ where }; let advice_evals = (0..num_proofs) - .map(|_| -> Result, _> { read_n_scalars(transcript, vk.cs.advice_queries.len()) }) + .map(|_| -> Result, _> { read_n(transcript, vk.cs.advice_queries.len()) }) .collect::, _>>()?; - let fixed_evals = read_n_scalars(transcript, vk.cs.fixed_queries.len())?; + let fixed_evals = read_n(transcript, vk.cs.fixed_queries.len())?; - let vanishing = vanishing.evaluate_after_x(transcript)?; + let vanishing = vanishing.evaluate_after_x(vk, transcript)?; let permutations_common = vk.permutation.evaluate(transcript)?; @@ -243,12 +194,12 @@ where let blinding_factors = vk.cs.blinding_factors(); let l_evals = vk .domain - .l_i_range(*x, xn, (-((blinding_factors + 1) as i32))..=0); + .l_i_range(x, xn, (-((blinding_factors + 1) as i32))..=0); assert_eq!(l_evals.len(), 2 + blinding_factors); let l_last = l_evals[0]; - let l_blind: Scheme::Scalar = l_evals[1..(1 + blinding_factors)] + let l_blind: F = l_evals[1..(1 + blinding_factors)] .iter() - .fold(Scheme::Scalar::ZERO, |acc, eval| acc + eval); + .fold(F::ZERO, |acc, eval| acc + eval); let l_0 = l_evals[1 + blinding_factors]; // Compute the expected value of h(x) @@ -311,44 +262,22 @@ where )) }); - vanishing.verify(params, expressions, y, xn) + vanishing.verify(expressions, y, xn) }; - let queries = instance_commitments + let queries = advice_commitments .iter() - .zip(instance_evals.iter()) - .zip(advice_commitments.iter()) .zip(advice_evals.iter()) .zip(permutations_evaluated.iter()) .zip(lookups_evaluated.iter()) .flat_map( - |( - ( - (((instance_commitments, instance_evals), advice_commitments), advice_evals), - permutation, - ), - lookups, - )| { + |(((advice_commitments, advice_evals), permutation), lookups)| { iter::empty() - .chain( - V::QUERY_INSTANCE - .then_some(vk.cs.instance_queries.iter().enumerate().map( - move |(query_index, &(column, at))| { - VerifierQuery::new_commitment( - &instance_commitments[column.index()], - vk.domain.rotate_omega(*x, at), - instance_evals[query_index], - ) - }, - )) - .into_iter() - .flatten(), - ) .chain(vk.cs.advice_queries.iter().enumerate().map( move |(query_index, &(column, at))| { - VerifierQuery::new_commitment( - &advice_commitments[column.index()], - vk.domain.rotate_omega(*x, at), + VerifierQuery::new( + vk.domain.rotate_omega(x, at), + advice_commitments[column.index()], advice_evals[query_index], ) }, @@ -363,9 +292,9 @@ where .iter() .enumerate() .map(|(query_index, &(column, at))| { - VerifierQuery::new_commitment( - &vk.fixed_commitments[column.index()], - vk.domain.rotate_omega(*x, at), + VerifierQuery::new( + vk.domain.rotate_omega(x, at), + vk.fixed_commitments[column.index()], fixed_evals[query_index], ) }), @@ -375,11 +304,5 @@ where // We are now convinced the circuit is satisfied so long as the // polynomial commitments open to the correct values. - - let verifier = V::new(params); - strategy.process(|msm| { - verifier - .verify_proof(transcript, queries, msm) - .map_err(|_| Error::Opening) - }) + CS::verify(params, queries, transcript).map_err(|_| Error::Opening) } diff --git a/src/poly.rs b/src/poly.rs index 8ac2403d47..bd042c632d 100644 --- a/src/poly.rs +++ b/src/poly.rs @@ -7,14 +7,12 @@ use crate::helpers::SerdePrimeField; // use crate::plonk::Assigned; use crate::SerdeFormat; +use ff::BatchInvert; use group::ff::Field; -use rand_core::RngCore; use std::fmt::Debug; use std::io; use std::marker::PhantomData; -use std::ops::{ - Add, AddAssign, Deref, DerefMut, Index, IndexMut, Mul, MulAssign, RangeFrom, RangeFull, Sub, -}; +use std::ops::{Add, Deref, DerefMut, Index, IndexMut, Mul, RangeFrom, RangeFull, Sub}; // /// Generic commitment scheme structures // pub mod commitment; @@ -27,6 +25,7 @@ pub mod kzg; pub mod commitment; +use crate::rational::Rational; pub use domain::*; pub use query::{ProverQuery, VerifierQuery}; // pub use strategy::{Guard, VerificationStrategy}; @@ -174,52 +173,52 @@ impl Polynomial { } } -// pub(crate) fn batch_invert_assigned( -// assigned: Vec, LagrangeCoeff>>, -// ) -> Vec> { -// let mut assigned_denominators: Vec<_> = assigned -// .iter() -// .map(|f| { -// f.iter() -// .map(|value| value.denominator()) -// .collect::>() -// }) -// .collect(); -// -// assigned_denominators -// .iter_mut() -// .flat_map(|f| { -// f.iter_mut() -// // If the denominator is trivial, we can skip it, reducing the -// // size of the batch inversion. -// .filter_map(|d| d.as_mut()) -// }) -// .batch_invert(); -// -// assigned -// .iter() -// .zip(assigned_denominators) -// .map(|(poly, inv_denoms)| poly.invert(inv_denoms.into_iter().map(|d| d.unwrap_or(F::ONE)))) -// .collect() -// } -// -// impl Polynomial, LagrangeCoeff> { -// pub(crate) fn invert( -// &self, -// inv_denoms: impl Iterator + ExactSizeIterator, -// ) -> Polynomial { -// assert_eq!(inv_denoms.len(), self.values.len()); -// Polynomial { -// values: self -// .values -// .iter() -// .zip(inv_denoms) -// .map(|(a, inv_den)| a.numerator() * inv_den) -// .collect(), -// _marker: self._marker, -// } -// } -// } +pub(crate) fn batch_invert_rational( + assigned: Vec, LagrangeCoeff>>, +) -> Vec> { + let mut assigned_denominators: Vec<_> = assigned + .iter() + .map(|f| { + f.iter() + .map(|value| value.denominator()) + .collect::>() + }) + .collect(); + + assigned_denominators + .iter_mut() + .flat_map(|f| { + f.iter_mut() + // If the denominator is trivial, we can skip it, reducing the + // size of the batch inversion. + .filter_map(|d| d.as_mut()) + }) + .batch_invert(); + + assigned + .iter() + .zip(assigned_denominators) + .map(|(poly, inv_denoms)| poly.invert(inv_denoms.into_iter().map(|d| d.unwrap_or(F::ONE)))) + .collect() +} + +impl Polynomial, LagrangeCoeff> { + pub(crate) fn invert( + &self, + inv_denoms: impl Iterator + ExactSizeIterator, + ) -> Polynomial { + assert_eq!(inv_denoms.len(), self.values.len()); + Polynomial { + values: self + .values + .iter() + .zip(inv_denoms) + .map(|(a, inv_den)| a.numerator() * inv_den) + .collect(), + _marker: self._marker, + } + } +} impl<'a, F: Field, B: Basis> Add<&'a Polynomial> for Polynomial { type Output = Polynomial; @@ -321,60 +320,3 @@ impl Rotation { Rotation(1) } } - -/// Wrapper type around a blinding factor. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub struct Blind(pub F); - -impl Default for Blind { - fn default() -> Self { - Blind(F::ONE) - } -} - -impl Blind { - /// Given `rng` creates new blinding scalar - pub fn new(rng: &mut R) -> Self { - Blind(F::random(rng)) - } -} - -impl Add for Blind { - type Output = Self; - - fn add(self, rhs: Blind) -> Self { - Blind(self.0 + rhs.0) - } -} - -impl Mul for Blind { - type Output = Self; - - fn mul(self, rhs: Blind) -> Self { - Blind(self.0 * rhs.0) - } -} - -impl AddAssign for Blind { - fn add_assign(&mut self, rhs: Blind) { - self.0 += rhs.0; - } -} - -impl MulAssign for Blind { - fn mul_assign(&mut self, rhs: Blind) { - self.0 *= rhs.0; - } -} - -impl AddAssign for Blind { - fn add_assign(&mut self, rhs: F) { - self.0 += rhs; - } -} - -impl MulAssign for Blind { - fn mul_assign(&mut self, rhs: F) { - self.0 *= rhs; - } -} diff --git a/src/poly/commitment.rs b/src/poly/commitment.rs index 9c2810e3ed..a69de276be 100644 --- a/src/poly/commitment.rs +++ b/src/poly/commitment.rs @@ -1,40 +1,61 @@ //! Trait for a commitment scheme -use crate::poly::{Blind, Coeff, Error, Polynomial, ProverQuery, VerifierQuery}; +use crate::poly::{Coeff, Error, LagrangeCoeff, Polynomial, ProverQuery, VerifierQuery}; use crate::transcript::{Hashable, Sampleable, Transcript}; use ff::PrimeField; use halo2curves::serde::SerdeObject; +use std::fmt::Debug; /// Public interface for a Polynomial Commitment Scheme (PCS) -pub trait PolynomialCommitmentScheme: Clone { +pub trait PolynomialCommitmentScheme: Clone + Debug { /// Parameters needed to generate a proof in the PCS - type Parameters; + type Parameters: Params; + /// Parameters needed to verify a proof in the PCS - type VerifierParameters: From; + type VerifierParameters: Params; + /// Type of a committed polynomial - type Commitment: Clone + Copy + PartialEq + SerdeObject + Send + Sync; // Thinking of having an MSM here + type Commitment: Clone + Copy + Debug + Default + PartialEq + SerdeObject + Send + Sync; /// Setup the parameters for the PCS fn setup(k: u32) -> Self::Parameters; - /// Create a new instantiation of the PCS - fn new(params: Self::Parameters) -> Self; - /// Commit to a polynomial - fn commit(&self, polynomial: &Polynomial, blind: Blind) -> Self::Commitment; + + /// Commit to a polynomial in coefficient form + fn commit(params: &Self::Parameters, polynomial: &Polynomial) -> Self::Commitment; + /// Create an opening proof at a specific query - fn open( - &self, - prover_query: Vec>, + /// FIXME: We are not writing the queries to the transcript + fn open<'com, T: Transcript, I>( + params: &Self::Parameters, + prover_query: I, transcript: &mut T, ) -> Result<(), Error> where + I: IntoIterator> + Clone, F: Sampleable, Self::Commitment: Hashable; + /// Verify an opening proof at a given query - fn verify( - &self, - verifier_query: Vec>, + fn verify( + params: &Self::VerifierParameters, + verifier_query: I, transcript: &mut T, ) -> Result<(), Error> where - F: Sampleable, + I: IntoIterator> + Clone, + F: Sampleable, // This is not necessarily part of a PCS Self::Commitment: Hashable; } + +/// Interface for prover/verifier params +pub trait Params> { + /// Logarithmic size of polynomials that can be committed with these parameters + fn k(&self) -> u32; + + /// Size of polynomials that can be committed with these parameters + fn n(&self) -> u64; + + /// This commits to a polynomial using its evaluations over the $2^k$ size + /// evaluation domain. The commitment will be blinded by the blinding factor + /// `r`. + fn commit_lagrange(&self, poly: &Polynomial) -> CS::Commitment; +} diff --git a/src/poly/domain.rs b/src/poly/domain.rs index 7ab195c9b2..ba105badd7 100644 --- a/src/poly/domain.rs +++ b/src/poly/domain.rs @@ -1,15 +1,13 @@ //! Contains utilities for performing polynomial arithmetic over an evaluation //! domain that is of a suitable size for the application. -use crate::{ - arithmetic::{best_fft, parallelize}, - rational::Rational, -}; +use crate::arithmetic::{best_fft, parallelize}; use super::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation}; use ff::WithSmallOrderMulGroup; use group::ff::{BatchInvert, Field}; +use crate::rational::Rational; use std::marker::PhantomData; /// This structure contains precomputed constants and other details needed for @@ -24,7 +22,7 @@ pub struct EvaluationDomain { omega_inv: F, extended_omega: F, extended_omega_inv: F, - g_coset: F, + pub(crate) g_coset: F, g_coset_inv: F, quotient_poly_degree: u64, ifft_divisor: F, @@ -77,8 +75,6 @@ impl> EvaluationDomain { let omega = omega; let mut omega_inv = omega; // Inversion computed later - // We use zeta here because we know it generates a coset, and it's available - // already. // The coset evaluation domain is: // zeta {1, extended_omega, extended_omega^2, ..., extended_omega^{(2^extended_k) - 1}} let g_coset = F::ZETA; @@ -88,7 +84,7 @@ impl> EvaluationDomain { { // Compute the evaluations of t(X) = X^n - 1 in the coset evaluation domain. // We don't have to compute all of them, because it will repeat. - let orig = F::ZETA.pow_vartime([n, 0, 0, 0]); + let orig = g_coset.pow_vartime([n, 0, 0, 0]); let step = extended_omega.pow_vartime([n, 0, 0, 0]); let mut cur = orig; loop { diff --git a/src/poly/kzg/mod.rs b/src/poly/kzg/mod.rs index 517d962cac..b2b685d59a 100644 --- a/src/poly/kzg/mod.rs +++ b/src/poly/kzg/mod.rs @@ -13,7 +13,7 @@ use crate::poly::kzg::msm::{DualMSM, MSMKZG}; use crate::poly::kzg::params::{ParamsKZG, ParamsVerifierKZG}; use crate::poly::query::Query; use crate::poly::query::VerifierQuery; -use crate::poly::{Blind, Coeff, Error, Polynomial, ProverQuery}; +use crate::poly::{Coeff, Error, Polynomial, ProverQuery}; use crate::poly::commitment::PolynomialCommitmentScheme; use crate::transcript::{Hashable, Sampleable, Transcript}; @@ -25,50 +25,47 @@ use halo2curves::serde::SerdeObject; #[derive(Clone, Debug)] /// KZG verifier pub struct KZGCommitmentScheme { - params: ParamsKZG, + _marker: PhantomData, } impl PolynomialCommitmentScheme for KZGCommitmentScheme where E::Fr: SerdeObject, - E::G1Affine: SerdeObject, + E::G1Affine: Default + SerdeObject, { type Parameters = ParamsKZG; type VerifierParameters = ParamsVerifierKZG; type Commitment = E::G1Affine; + /// Unsafe function - do not use in production fn setup(k: u32) -> ParamsKZG { ParamsKZG::new(k) } - fn new(params: Self::Parameters) -> Self { - Self { params } - } - fn commit( - &self, + params: &Self::Parameters, polynomial: &Polynomial, - _blind: Blind, ) -> Self::Commitment { let mut scalars = Vec::with_capacity(polynomial.len()); scalars.extend(polynomial.iter()); - let bases = &self.params.g; + let bases = ¶ms.g; let size = scalars.len(); assert!(bases.len() >= size); best_multiexp(&scalars, &bases[0..size]).into() } - fn open( - &self, - prover_query: Vec>, + fn open<'com, T: Transcript, I>( + params: &Self::Parameters, + prover_query: I, transcript: &mut T, ) -> Result<(), Error> where + I: IntoIterator> + Clone, E::Fr: Sampleable, E::G1Affine: Hashable, { let v: E::Fr = transcript.squeeze_challenge(); - let commitment_data = construct_intermediate_sets(prover_query.into_iter()); + let commitment_data = construct_intermediate_sets(prover_query); for commitment_at_a_point in commitment_data.iter() { let z = commitment_at_a_point.point; @@ -92,7 +89,7 @@ where values: kate_division(&poly_batch.values, z), _marker: PhantomData, }; - let w = self.commit(&witness_poly, Blind::default()); + let w = Self::commit(params, &witness_poly); transcript.write(&w).map_err(|_| Error::OpeningError)?; } @@ -100,18 +97,19 @@ where Ok(()) } - fn verify( - &self, - verifier_query: Vec>, + fn verify( + params: &Self::VerifierParameters, + verifier_query: I, transcript: &mut T, ) -> Result<(), Error> where E::Fr: Sampleable, E::G1Affine: Hashable, + I: IntoIterator>> + Clone, { let v: E::Fr = transcript.squeeze_challenge(); - let commitment_data = construct_intermediate_sets(verifier_query.into_iter()); + let commitment_data = construct_intermediate_sets(verifier_query); let w = (0..commitment_data.len()) .map(|_| transcript.read().map_err(|_| Error::SamplingError)) @@ -159,13 +157,13 @@ where witness.append_term(power_of_u, wi.to_curve()); } - let mut msm = DualMSM::new(&self.params); + let mut msm = DualMSM::new(params); msm.left.add_msm(&witness); msm.right.add_msm(&witness_with_aux); msm.right.add_msm(&commitment_multi); - let g0: E::G1 = self.params.g[0].to_curve(); + let g0: E::G1 = params.g[0].to_curve(); msm.right.append_term(eval_multi, -g0); if msm.check() { @@ -214,10 +212,11 @@ where mod tests { use crate::arithmetic::eval_polynomial; use crate::poly::commitment::PolynomialCommitmentScheme; + use crate::poly::kzg::params::{ParamsKZG, ParamsVerifierKZG}; use crate::poly::kzg::KZGCommitmentScheme; use crate::poly::{ query::{ProverQuery, VerifierQuery}, - Blind, Error, EvaluationDomain, + Error, EvaluationDomain, }; use crate::transcript::{CircuitTranscript, Hashable, Sampleable, Transcript}; use blake2b_simd::State as Blake2bState; @@ -225,28 +224,25 @@ mod tests { use halo2curves::pairing::{Engine, MultiMillerLoop}; use halo2curves::serde::SerdeObject; use halo2curves::CurveAffine; - use rand_core::OsRng; #[test] fn test_roundtrip_gwc() { - use crate::poly::kzg::params::ParamsKZG; use crate::poly::kzg::KZGCommitmentScheme; use halo2curves::bn256::Bn256; const K: u32 = 4; - let params = ParamsKZG::::new(K); + let params = KZGCommitmentScheme::::setup(K); - let kzg_prover = KZGCommitmentScheme::new(params); - let proof = create_proof::<_, CircuitTranscript>(&kzg_prover); + let proof = create_proof::<_, CircuitTranscript>(¶ms); - verify::<_, CircuitTranscript>(&kzg_prover, &proof[..], false); + verify::<_, CircuitTranscript>(¶ms, &proof[..], false); - verify::>(&kzg_prover, &proof[..], true); + verify::>(¶ms, &proof[..], true); } fn verify( - verifier: &KZGCommitmentScheme, + verifier_params: &ParamsVerifierKZG, proof: &[u8], should_fail: bool, ) where @@ -284,8 +280,7 @@ mod tests { valid_queries }; - let result = verifier - .verify(queries.collect::>(), &mut transcript) + let result = KZGCommitmentScheme::verify(verifier_params, queries, &mut transcript) .map_err(|_| Error::OpeningError); if should_fail { @@ -295,14 +290,12 @@ mod tests { } } - fn create_proof( - kzg_prover: &KZGCommitmentScheme, - ) -> Vec + fn create_proof(kzg_params: &ParamsKZG) -> Vec where E::Fr: WithSmallOrderMulGroup<3> + SerdeObject + Hashable + Sampleable, - E::G1Affine: SerdeObject + Hashable, + E::G1Affine: SerdeObject + Hashable + Default, { - let domain = EvaluationDomain::new(1, kzg_prover.params.k); + let domain = EvaluationDomain::new(1, kzg_params.k); let mut ax = domain.empty_coeff(); for (i, a) in ax.iter_mut().enumerate() { @@ -321,11 +314,9 @@ mod tests { let mut transcript = T::init(); - let blind = Blind::new(&mut OsRng); - - let a = kzg_prover.commit(&ax, blind); - let b = kzg_prover.commit(&bx, blind); - let c = kzg_prover.commit(&cx, blind); + let a = KZGCommitmentScheme::commit(kzg_params, &ax); + let b = KZGCommitmentScheme::commit(kzg_params, &bx); + let c = KZGCommitmentScheme::commit(kzg_params, &cx); transcript.write(&a).unwrap(); transcript.write(&b).unwrap(); @@ -356,9 +347,9 @@ mod tests { poly: &cx, }, ] - .to_vec(); + .into_iter(); - kzg_prover.open(queries, &mut transcript).unwrap(); + KZGCommitmentScheme::open(kzg_params, queries, &mut transcript).unwrap(); transcript.finalize() } diff --git a/src/poly/kzg/params.rs b/src/poly/kzg/params.rs index e2b8dbff40..15991592e6 100644 --- a/src/poly/kzg/params.rs +++ b/src/poly/kzg/params.rs @@ -1,14 +1,17 @@ use crate::arithmetic::{best_multiexp, g_to_lagrange, parallelize}; use crate::helpers::SerdeCurveAffine; -use crate::poly::{Blind, LagrangeCoeff, Polynomial}; +use crate::poly::{LagrangeCoeff, Polynomial}; use crate::SerdeFormat; use ff::{Field, PrimeField}; use group::{prime::PrimeCurveAffine, Curve, Group}; -use halo2curves::pairing::Engine; +use halo2curves::pairing::{Engine, MultiMillerLoop}; use rand_core::{OsRng, RngCore}; use std::fmt::Debug; +use crate::poly::commitment::Params; +use crate::poly::kzg::KZGCommitmentScheme; +use halo2curves::serde::SerdeObject; use std::io; use super::msm::MSMKZG; @@ -24,6 +27,29 @@ pub struct ParamsKZG { pub(crate) s_g2: E::G2Affine, } +impl Params> for ParamsKZG +where + E::Fr: SerdeObject, + E::G1Affine: Default + SerdeObject, +{ + fn k(&self) -> u32 { + self.k + } + + fn n(&self) -> u64 { + self.n + } + + fn commit_lagrange(&self, poly: &Polynomial) -> E::G1Affine { + let mut scalars = Vec::with_capacity(poly.len()); + scalars.extend(poly.iter()); + let bases = &self.g_lagrange; + let size = scalars.len(); + assert!(bases.len() >= size); + best_multiexp(&scalars, &bases[0..size]).into() + } +} + impl ParamsKZG { /// Initializes parameters for the curve, draws toxic secret from given rng. /// MUST NOT be used in production. @@ -271,20 +297,6 @@ where MSMKZG::new() } - /// TODO - pub fn commit_lagrange( - &self, - poly: &Polynomial, - _: Blind, - ) -> E::G1 { - let mut scalars = Vec::with_capacity(poly.len()); - scalars.extend(poly.iter()); - let bases = &self.g_lagrange; - let size = scalars.len(); - assert!(bases.len() >= size); - best_multiexp(&scalars, &bases[0..size]) - } - /// Writes params to a buffer. pub fn write(&self, writer: &mut W) -> io::Result<()> { self.write_custom(writer, SerdeFormat::RawBytes) @@ -302,7 +314,7 @@ impl<'params, E: Engine + Debug> ParamsKZG { self } - /// TODO + /// UNSAFE function - do not use in production pub fn new(k: u32) -> Self { Self::setup(k, OsRng) } @@ -315,19 +327,14 @@ impl<'params, E: Engine + Debug> ParamsKZG { #[cfg(test)] mod test { - use crate::poly::commitment::PolynomialCommitmentScheme; + use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; use crate::poly::kzg::params::ParamsKZG; use crate::poly::kzg::KZGCommitmentScheme; - use crate::poly::Blind; - use ff::Field; - use halo2curves::pairing::Engine; #[test] fn test_commit_lagrange() { const K: u32 = 6; - use rand_core::OsRng; - use crate::poly::EvaluationDomain; use halo2curves::bn256::{Bn256, Fr}; @@ -342,10 +349,8 @@ mod test { let b = domain.lagrange_to_coeff(a.clone()); - let alpha = Blind(Fr::random(OsRng)); - let tmp = params.commit_lagrange(&a, alpha); - let cs = KZGCommitmentScheme::new(params); - let commitment: ::G1 = cs.commit(&b, alpha).into(); + let tmp = params.commit_lagrange(&a); + let commitment = KZGCommitmentScheme::commit(¶ms, &b); assert_eq!(commitment, tmp); } diff --git a/src/plonk/assigned.rs b/src/rational.rs similarity index 78% rename from src/plonk/assigned.rs rename to src/rational.rs index 07de325678..e1cc0c7a3e 100644 --- a/src/plonk/assigned.rs +++ b/src/rational.rs @@ -1,14 +1,14 @@ +//! Module that implements a rational value. A rational value is stored as a fraction, +//! so the backend can use batch inversion. use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use group::ff::Field; -/// A value assigned to a cell within a circuit. +/// A rational value. /// -/// Stored as a fraction, so the backend can use batch inversion. -/// -/// A denominator of zero maps to an assigned value of zero. +/// A denominator of zero maps to a value of zero. #[derive(Clone, Copy, Debug)] -pub enum Assigned { +pub enum Rational { /// The field element zero. Zero, /// A value that does not require inversion to evaluate. @@ -17,31 +17,31 @@ pub enum Assigned { Rational(F, F), } -impl From<&Assigned> for Assigned { - fn from(val: &Assigned) -> Self { +impl From<&Rational> for Rational { + fn from(val: &Rational) -> Self { *val } } -impl From<&F> for Assigned { +impl From<&F> for Rational { fn from(numerator: &F) -> Self { - Assigned::Trivial(*numerator) + Rational::Trivial(*numerator) } } -impl From for Assigned { +impl From for Rational { fn from(numerator: F) -> Self { - Assigned::Trivial(numerator) + Rational::Trivial(numerator) } } -impl From<(F, F)> for Assigned { +impl From<(F, F)> for Rational { fn from((numerator, denominator): (F, F)) -> Self { - Assigned::Rational(numerator, denominator) + Rational::Rational(numerator, denominator) } } -impl PartialEq for Assigned { +impl PartialEq for Rational { fn eq(&self, other: &Self) -> bool { match (self, other) { // At least one side is directly zero. @@ -69,10 +69,10 @@ impl PartialEq for Assigned { } } -impl Eq for Assigned {} +impl Eq for Rational {} -impl Neg for Assigned { - type Output = Assigned; +impl Neg for Rational { + type Output = Rational; fn neg(self) -> Self::Output { match self { Self::Zero => Self::Zero, @@ -82,16 +82,16 @@ impl Neg for Assigned { } } -impl Neg for &Assigned { - type Output = Assigned; +impl Neg for &Rational { + type Output = Rational; fn neg(self) -> Self::Output { -*self } } -impl Add for Assigned { - type Output = Assigned; - fn add(self, rhs: Assigned) -> Assigned { +impl Add for Rational { + type Output = Rational; + fn add(self, rhs: Rational) -> Rational { match (self, rhs) { // One side is directly zero. (Self::Zero, _) => rhs, @@ -121,110 +121,110 @@ impl Add for Assigned { } } -impl Add for Assigned { - type Output = Assigned; - fn add(self, rhs: F) -> Assigned { +impl Add for Rational { + type Output = Rational; + fn add(self, rhs: F) -> Rational { self + Self::Trivial(rhs) } } -impl Add for &Assigned { - type Output = Assigned; - fn add(self, rhs: F) -> Assigned { +impl Add for &Rational { + type Output = Rational; + fn add(self, rhs: F) -> Rational { *self + rhs } } -impl Add<&Assigned> for Assigned { - type Output = Assigned; - fn add(self, rhs: &Self) -> Assigned { +impl Add<&Rational> for Rational { + type Output = Rational; + fn add(self, rhs: &Self) -> Rational { self + *rhs } } -impl Add> for &Assigned { - type Output = Assigned; - fn add(self, rhs: Assigned) -> Assigned { +impl Add> for &Rational { + type Output = Rational; + fn add(self, rhs: Rational) -> Rational { *self + rhs } } -impl Add<&Assigned> for &Assigned { - type Output = Assigned; - fn add(self, rhs: &Assigned) -> Assigned { +impl Add<&Rational> for &Rational { + type Output = Rational; + fn add(self, rhs: &Rational) -> Rational { *self + *rhs } } -impl AddAssign for Assigned { +impl AddAssign for Rational { fn add_assign(&mut self, rhs: Self) { *self = *self + rhs; } } -impl AddAssign<&Assigned> for Assigned { +impl AddAssign<&Rational> for Rational { fn add_assign(&mut self, rhs: &Self) { *self = *self + rhs; } } -impl Sub for Assigned { - type Output = Assigned; - fn sub(self, rhs: Assigned) -> Assigned { +impl Sub for Rational { + type Output = Rational; + fn sub(self, rhs: Rational) -> Rational { self + (-rhs) } } -impl Sub for Assigned { - type Output = Assigned; - fn sub(self, rhs: F) -> Assigned { +impl Sub for Rational { + type Output = Rational; + fn sub(self, rhs: F) -> Rational { self + (-rhs) } } -impl Sub for &Assigned { - type Output = Assigned; - fn sub(self, rhs: F) -> Assigned { +impl Sub for &Rational { + type Output = Rational; + fn sub(self, rhs: F) -> Rational { *self - rhs } } -impl Sub<&Assigned> for Assigned { - type Output = Assigned; - fn sub(self, rhs: &Self) -> Assigned { +impl Sub<&Rational> for Rational { + type Output = Rational; + fn sub(self, rhs: &Self) -> Rational { self - *rhs } } -impl Sub> for &Assigned { - type Output = Assigned; - fn sub(self, rhs: Assigned) -> Assigned { +impl Sub> for &Rational { + type Output = Rational; + fn sub(self, rhs: Rational) -> Rational { *self - rhs } } -impl Sub<&Assigned> for &Assigned { - type Output = Assigned; - fn sub(self, rhs: &Assigned) -> Assigned { +impl Sub<&Rational> for &Rational { + type Output = Rational; + fn sub(self, rhs: &Rational) -> Rational { *self - *rhs } } -impl SubAssign for Assigned { +impl SubAssign for Rational { fn sub_assign(&mut self, rhs: Self) { *self = *self - rhs; } } -impl SubAssign<&Assigned> for Assigned { +impl SubAssign<&Rational> for Rational { fn sub_assign(&mut self, rhs: &Self) { *self = *self - rhs; } } -impl Mul for Assigned { - type Output = Assigned; - fn mul(self, rhs: Assigned) -> Assigned { +impl Mul for Rational { + type Output = Rational; + fn mul(self, rhs: Rational) -> Rational { match (self, rhs) { (Self::Zero, _) | (_, Self::Zero) => Self::Zero, (Self::Trivial(lhs), Self::Trivial(rhs)) => Self::Trivial(lhs * rhs), @@ -243,40 +243,40 @@ impl Mul for Assigned { } } -impl Mul for Assigned { - type Output = Assigned; - fn mul(self, rhs: F) -> Assigned { +impl Mul for Rational { + type Output = Rational; + fn mul(self, rhs: F) -> Rational { self * Self::Trivial(rhs) } } -impl Mul for &Assigned { - type Output = Assigned; - fn mul(self, rhs: F) -> Assigned { +impl Mul for &Rational { + type Output = Rational; + fn mul(self, rhs: F) -> Rational { *self * rhs } } -impl Mul<&Assigned> for Assigned { - type Output = Assigned; - fn mul(self, rhs: &Assigned) -> Assigned { +impl Mul<&Rational> for Rational { + type Output = Rational; + fn mul(self, rhs: &Rational) -> Rational { self * *rhs } } -impl MulAssign for Assigned { +impl MulAssign for Rational { fn mul_assign(&mut self, rhs: Self) { *self = *self * rhs; } } -impl MulAssign<&Assigned> for Assigned { +impl MulAssign<&Rational> for Rational { fn mul_assign(&mut self, rhs: &Self) { *self = *self * rhs; } } -impl Assigned { +impl Rational { /// Returns the numerator. pub fn numerator(&self) -> F { match self { @@ -369,14 +369,14 @@ impl Assigned { mod tests { use halo2curves::pasta::Fp; - use super::Assigned; + use super::Rational; // We use (numerator, denominator) in the comments below to denote a rational. #[test] fn add_trivial_to_inv0_rational() { // a = 2 // b = (1,0) - let a = Assigned::Trivial(Fp::from(2)); - let b = Assigned::Rational(Fp::one(), Fp::zero()); + let a = Rational::Trivial(Fp::from(2)); + let b = Rational::Rational(Fp::one(), Fp::zero()); // 2 + (1,0) = 2 + 0 = 2 // This fails if addition is implemented using normal rules for rationals. @@ -388,8 +388,8 @@ mod tests { fn add_rational_to_inv0_rational() { // a = (1,2) // b = (1,0) - let a = Assigned::Rational(Fp::one(), Fp::from(2)); - let b = Assigned::Rational(Fp::one(), Fp::zero()); + let a = Rational::Rational(Fp::one(), Fp::from(2)); + let b = Rational::Rational(Fp::one(), Fp::zero()); // (1,2) + (1,0) = (1,2) + 0 = (1,2) // This fails if addition is implemented using normal rules for rationals. @@ -401,8 +401,8 @@ mod tests { fn sub_trivial_from_inv0_rational() { // a = 2 // b = (1,0) - let a = Assigned::Trivial(Fp::from(2)); - let b = Assigned::Rational(Fp::one(), Fp::zero()); + let a = Rational::Trivial(Fp::from(2)); + let b = Rational::Rational(Fp::one(), Fp::zero()); // (1,0) - 2 = 0 - 2 = -2 // This fails if subtraction is implemented using normal rules for rationals. @@ -416,8 +416,8 @@ mod tests { fn sub_rational_from_inv0_rational() { // a = (1,2) // b = (1,0) - let a = Assigned::Rational(Fp::one(), Fp::from(2)); - let b = Assigned::Rational(Fp::one(), Fp::zero()); + let a = Rational::Rational(Fp::one(), Fp::from(2)); + let b = Rational::Rational(Fp::one(), Fp::zero()); // (1,0) - (1,2) = 0 - (1,2) = -(1,2) // This fails if subtraction is implemented using normal rules for rationals. @@ -431,8 +431,8 @@ mod tests { fn mul_rational_by_inv0_rational() { // a = (1,2) // b = (1,0) - let a = Assigned::Rational(Fp::one(), Fp::from(2)); - let b = Assigned::Rational(Fp::one(), Fp::zero()); + let a = Rational::Rational(Fp::one(), Fp::from(2)); + let b = Rational::Rational(Fp::one(), Fp::zero()); // (1,2) * (1,0) = (1,2) * 0 = 0 assert_eq!((a * b).evaluate(), Fp::zero()); @@ -449,12 +449,11 @@ mod proptests { ops::{Add, Mul, Neg, Sub}, }; + use crate::rational::Rational; use group::ff::Field; use halo2curves::pasta::Fp; use proptest::{collection::vec, prelude::*, sample::select}; - use super::Assigned; - trait UnaryOperand: Neg { fn double(&self) -> Self; fn square(&self) -> Self; @@ -480,7 +479,7 @@ mod proptests { } } - impl UnaryOperand for Assigned { + impl UnaryOperand for Rational { fn double(&self) -> Self { self.double() } @@ -529,7 +528,7 @@ mod proptests { trait BinaryOperand: Sized + Add + Sub + Mul {} impl BinaryOperand for F {} - impl BinaryOperand for Assigned {} + impl BinaryOperand for Rational {} #[derive(Clone, Debug)] enum BinaryOperator { @@ -568,8 +567,8 @@ mod proptests { } prop_compose! { - fn arb_trivial()(element in arb_element()) -> Assigned { - Assigned::Trivial(element) + fn arb_trivial()(element in arb_element()) -> Rational { + Rational::Trivial(element) } } @@ -581,8 +580,8 @@ mod proptests { 1 => Just(Fp::zero()), 2 => arb_element(), ], - ) -> Assigned { - Assigned::Rational(numerator, denominator) + ) -> Rational { + Rational::Rational(numerator, denominator) } } @@ -605,7 +604,7 @@ mod proptests { )( values in vec( prop_oneof![ - 1 => Just(Assigned::Zero), + 1 => Just(Rational::Zero), 2 => arb_trivial(), 2 => arb_rational(), ], @@ -614,7 +613,7 @@ mod proptests { // - we can apply every binary operator pairwise sequentially. cmp::max(usize::from(num_unary > 0), num_binary + 1)), operations in arb_operators(num_unary, num_binary).prop_shuffle(), - ) -> (Vec>, Vec) { + ) -> (Vec>, Vec) { (values, operations) } } diff --git a/src/transcript.rs b/src/transcript.rs index 2f895ffaeb..23a73d952e 100644 --- a/src/transcript.rs +++ b/src/transcript.rs @@ -2,8 +2,7 @@ //! transcripts. use blake2b_simd::{Params, State as Blake2bState}; -use ff::{FromUniformBytes, PrimeField}; -use group::prime::PrimeCurveAffine; +use ff::FromUniformBytes; use group::GroupEncoding; use halo2curves::bn256::{Fr, G1Affine}; use halo2curves::serde::SerdeObject; @@ -60,9 +59,11 @@ pub trait Transcript { fn init() -> Self; /// Parses an existing transcript + /// // todo: call from_bytes? fn parse(bytes: &[u8]) -> Self; - /// Squeeze a challenge + /// Squeeze a challenge of type `T` + /// todo: clarify type `T` in this and other functions fn squeeze_challenge>(&mut self) -> T; /// Writing a commitment to the transcript without writing it to the proof, @@ -152,6 +153,7 @@ impl TranscriptHash for Blake2bState { self.finalize().as_bytes().to_vec() } } + /////////////////////////////////////////////////// /// Implementation of Hashable for BN with Blake // /////////////////////////////////////////////////// @@ -177,18 +179,10 @@ impl Sampleable for Fr { } } -pub(crate) fn read_n_points(transcript: &mut T, n: usize) -> io::Result> -where - T: Transcript, - C: PrimeCurveAffine + Hashable + SerdeObject, -{ - (0..n).map(|_| transcript.read()).collect() -} - -pub(crate) fn read_n_scalars(transcript: &mut T, n: usize) -> io::Result> +pub(crate) fn read_n(transcript: &mut T, n: usize) -> io::Result> where T: Transcript, - F: PrimeField + Hashable + SerdeObject, + C: Hashable + SerdeObject, { (0..n).map(|_| transcript.read()).collect() } diff --git a/tests/plonk_api.rs b/tests/plonk_api.rs index cfae2d4f98..5b9e64d86a 100644 --- a/tests/plonk_api.rs +++ b/tests/plonk_api.rs @@ -1,558 +1,558 @@ -#![allow(clippy::many_single_char_names)] -#![allow(clippy::op_ref)] - -use assert_matches::assert_matches; -use ff::{FromUniformBytes, WithSmallOrderMulGroup}; -use halo2_proofs::arithmetic::Field; -use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; -use halo2_proofs::dev::MockProver; -use halo2_proofs::plonk::{ - create_proof as create_plonk_proof, keygen_pk, keygen_vk, verify_proof as verify_plonk_proof, - Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, ProvingKey, TableColumn, - VerifyingKey, -}; -use halo2_proofs::poly::commitment::{CommitmentScheme, ParamsProver, Prover, Verifier}; -use halo2_proofs::poly::Rotation; -use halo2_proofs::poly::VerificationStrategy; -use halo2_proofs::transcript::{ - Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge, TranscriptReadBuffer, - TranscriptWriterBuffer, -}; -use rand_core::{OsRng, RngCore}; -use std::marker::PhantomData; - -#[test] -fn plonk_api() { - const K: u32 = 5; - - /// This represents an advice column at a certain row in the ConstraintSystem - #[derive(Copy, Clone, Debug)] - pub struct Variable(Column, usize); - - #[derive(Clone)] - struct PlonkConfig { - a: Column, - b: Column, - c: Column, - d: Column, - e: Column, - - sa: Column, - sb: Column, - sc: Column, - sm: Column, - sp: Column, - sl: TableColumn, - } - - #[allow(clippy::type_complexity)] - trait StandardCs { - fn raw_multiply( - &self, - layouter: &mut impl Layouter, - f: F, - ) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; - fn raw_add( - &self, - layouter: &mut impl Layouter, - f: F, - ) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; - fn copy(&self, layouter: &mut impl Layouter, a: Cell, b: Cell) -> Result<(), Error>; - fn public_input(&self, layouter: &mut impl Layouter, f: F) -> Result - where - F: FnMut() -> Value; - fn lookup_table( - &self, - layouter: &mut impl Layouter, - values: &[FF], - ) -> Result<(), Error>; - } - - #[derive(Clone)] - struct MyCircuit { - a: Value, - lookup_table: Vec, - } - - struct StandardPlonk { - config: PlonkConfig, - _marker: PhantomData, - } - - impl StandardPlonk { - fn new(config: PlonkConfig) -> Self { - StandardPlonk { - config, - _marker: PhantomData, - } - } - } - - impl StandardCs for StandardPlonk { - fn raw_multiply( - &self, - layouter: &mut impl Layouter, - mut f: F, - ) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, - { - layouter.assign_region( - || "raw_multiply", - |mut region| { - let mut value = None; - let lhs = region.assign_advice( - || "lhs", - self.config.a, - 0, - || { - value = Some(f()); - value.unwrap().map(|v| v.0) - }, - )?; - region.assign_advice( - || "lhs^4", - self.config.d, - 0, - || value.unwrap().map(|v| v.0).square().square(), - )?; - let rhs = region.assign_advice( - || "rhs", - self.config.b, - 0, - || value.unwrap().map(|v| v.1), - )?; - region.assign_advice( - || "rhs^4", - self.config.e, - 0, - || value.unwrap().map(|v| v.1).square().square(), - )?; - let out = region.assign_advice( - || "out", - self.config.c, - 0, - || value.unwrap().map(|v| v.2), - )?; - - region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ZERO))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ZERO))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; - region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; - Ok((lhs.cell(), rhs.cell(), out.cell())) - }, - ) - } - fn raw_add( - &self, - layouter: &mut impl Layouter, - mut f: F, - ) -> Result<(Cell, Cell, Cell), Error> - where - F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, - { - layouter.assign_region( - || "raw_add", - |mut region| { - let mut value = None; - let lhs = region.assign_advice( - || "lhs", - self.config.a, - 0, - || { - value = Some(f()); - value.unwrap().map(|v| v.0) - }, - )?; - region.assign_advice( - || "lhs^4", - self.config.d, - 0, - || value.unwrap().map(|v| v.0).square().square(), - )?; - let rhs = region.assign_advice( - || "rhs", - self.config.b, - 0, - || value.unwrap().map(|v| v.1), - )?; - region.assign_advice( - || "rhs^4", - self.config.e, - 0, - || value.unwrap().map(|v| v.1).square().square(), - )?; - let out = region.assign_advice( - || "out", - self.config.c, - 0, - || value.unwrap().map(|v| v.2), - )?; - - region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ONE))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ONE))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; - region.assign_fixed( - || "a * b", - self.config.sm, - 0, - || Value::known(FF::ZERO), - )?; - Ok((lhs.cell(), rhs.cell(), out.cell())) - }, - ) - } - fn copy( - &self, - layouter: &mut impl Layouter, - left: Cell, - right: Cell, - ) -> Result<(), Error> { - layouter.assign_region( - || "copy", - |mut region| { - region.constrain_equal(left, right)?; - region.constrain_equal(left, right) - }, - ) - } - fn public_input(&self, layouter: &mut impl Layouter, mut f: F) -> Result - where - F: FnMut() -> Value, - { - layouter.assign_region( - || "public_input", - |mut region| { - let value = region.assign_advice(|| "value", self.config.a, 0, &mut f)?; - region.assign_fixed( - || "public", - self.config.sp, - 0, - || Value::known(FF::ONE), - )?; - - Ok(value.cell()) - }, - ) - } - fn lookup_table( - &self, - layouter: &mut impl Layouter, - values: &[FF], - ) -> Result<(), Error> { - layouter.assign_table( - || "", - |mut table| { - for (index, &value) in values.iter().enumerate() { - table.assign_cell( - || "table col", - self.config.sl, - index, - || Value::known(value), - )?; - } - Ok(()) - }, - )?; - Ok(()) - } - } - - impl Circuit for MyCircuit { - type Config = PlonkConfig; - type FloorPlanner = SimpleFloorPlanner; - #[cfg(feature = "circuit-params")] - type Params = (); - - fn without_witnesses(&self) -> Self { - Self { - a: Value::unknown(), - lookup_table: self.lookup_table.clone(), - } - } - - fn configure(meta: &mut ConstraintSystem) -> PlonkConfig { - let e = meta.advice_column(); - let a = meta.advice_column(); - let b = meta.advice_column(); - let sf = meta.fixed_column(); - let c = meta.advice_column(); - let d = meta.advice_column(); - let p = meta.instance_column(); - - meta.enable_equality(a); - meta.enable_equality(b); - meta.enable_equality(c); - - let sm = meta.fixed_column(); - let sa = meta.fixed_column(); - let sb = meta.fixed_column(); - let sc = meta.fixed_column(); - let sp = meta.fixed_column(); - let sl = meta.lookup_table_column(); - - /* - * A B ... sl - * [ - * instance 0 ... 0 - * a a ... 0 - * a a^2 ... 0 - * a a ... 0 - * a a^2 ... 0 - * ... ... ... ... - * ... ... ... instance - * ... ... ... a - * ... ... ... a - * ... ... ... 0 - * ] - */ - - meta.lookup("lookup", |meta| { - let a_ = meta.query_any(a, Rotation::cur()); - vec![(a_, sl)] - }); - - meta.create_gate("Combined add-mult", |meta| { - let d = meta.query_advice(d, Rotation::next()); - let a = meta.query_advice(a, Rotation::cur()); - let sf = meta.query_fixed(sf, Rotation::cur()); - let e = meta.query_advice(e, Rotation::prev()); - let b = meta.query_advice(b, Rotation::cur()); - let c = meta.query_advice(c, Rotation::cur()); - - let sa = meta.query_fixed(sa, Rotation::cur()); - let sb = meta.query_fixed(sb, Rotation::cur()); - let sc = meta.query_fixed(sc, Rotation::cur()); - let sm = meta.query_fixed(sm, Rotation::cur()); - - vec![a.clone() * sa + b.clone() * sb + a * b * sm - (c * sc) + sf * (d * e)] - }); - - meta.create_gate("Public input", |meta| { - let a = meta.query_advice(a, Rotation::cur()); - let p = meta.query_instance(p, Rotation::cur()); - let sp = meta.query_fixed(sp, Rotation::cur()); - - vec![sp * (a - p)] - }); - - meta.enable_equality(sf); - meta.enable_equality(e); - meta.enable_equality(d); - meta.enable_equality(p); - meta.enable_equality(sm); - meta.enable_equality(sa); - meta.enable_equality(sb); - meta.enable_equality(sc); - meta.enable_equality(sp); - - PlonkConfig { - a, - b, - c, - d, - e, - sa, - sb, - sc, - sm, - sp, - sl, - } - } - - fn synthesize( - &self, - config: PlonkConfig, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let cs = StandardPlonk::new(config); - - let _ = cs.public_input(&mut layouter, || Value::known(F::ONE + F::ONE))?; - - for _ in 0..10 { - let a: Value> = self.a.into(); - let mut a_squared = Value::unknown(); - let (a0, _, c0) = cs.raw_multiply(&mut layouter, || { - a_squared = a.square(); - a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) - })?; - let (a1, b1, _) = cs.raw_add(&mut layouter, || { - let fin = a_squared + a; - a.zip(a_squared) - .zip(fin) - .map(|((a, a_squared), fin)| (a, a_squared, fin)) - })?; - cs.copy(&mut layouter, a0, a1)?; - cs.copy(&mut layouter, b1, c0)?; - } - - cs.lookup_table(&mut layouter, &self.lookup_table)?; - - Ok(()) - } - } - - macro_rules! common { - ($scheme:ident) => {{ - let a = <$scheme as CommitmentScheme>::Scalar::from(2834758237) - * <$scheme as CommitmentScheme>::Scalar::ZETA; - let instance = <$scheme as CommitmentScheme>::Scalar::ONE - + <$scheme as CommitmentScheme>::Scalar::ONE; - let lookup_table = vec![instance, a, a, <$scheme as CommitmentScheme>::Scalar::ZERO]; - (a, instance, lookup_table) - }}; - } - - macro_rules! bad_keys { - ($scheme:ident) => {{ - let (_, _, lookup_table) = common!($scheme); - let empty_circuit: MyCircuit<<$scheme as CommitmentScheme>::Scalar> = MyCircuit { - a: Value::unknown(), - lookup_table: lookup_table.clone(), - }; - - // Check that we get an error if we try to initialize the proving key with a value of - // k that is too small for the minimum required number of rows. - let much_too_small_params= <$scheme as CommitmentScheme>::ParamsProver::new(1); - assert_matches!( - keygen_vk(&much_too_small_params, &empty_circuit), - Err(Error::NotEnoughRowsAvailable { - current_k, - }) if current_k == 1 - ); - - // Check that we get an error if we try to initialize the proving key with a value of - // k that is too small for the number of rows the circuit uses. - let slightly_too_small_params = <$scheme as CommitmentScheme>::ParamsProver::new(K-1); - assert_matches!( - keygen_vk(&slightly_too_small_params, &empty_circuit), - Err(Error::NotEnoughRowsAvailable { - current_k, - }) if current_k == K - 1 - ); - }}; - } - - fn keygen(params: &Scheme::ParamsProver) -> ProvingKey - where - Scheme::Scalar: FromUniformBytes<64> + WithSmallOrderMulGroup<3>, - { - let (_, _, lookup_table) = common!(Scheme); - let empty_circuit: MyCircuit = MyCircuit { - a: Value::unknown(), - lookup_table, - }; - - // Initialize the proving key - let vk = keygen_vk(params, &empty_circuit).expect("keygen_vk should not fail"); - - keygen_pk(params, vk, &empty_circuit).expect("keygen_pk should not fail") - } - - fn create_proof< - 'params, - Scheme: CommitmentScheme, - P: Prover<'params, Scheme>, - E: EncodedChallenge, - R: RngCore, - T: TranscriptWriterBuffer, Scheme::Curve, E>, - >( - rng: R, - params: &'params Scheme::ParamsProver, - pk: &ProvingKey, - ) -> Vec - where - Scheme::Scalar: Ord + WithSmallOrderMulGroup<3> + FromUniformBytes<64>, - { - let (a, instance, lookup_table) = common!(Scheme); - - let circuit: MyCircuit = MyCircuit { - a: Value::known(a), - lookup_table, - }; - - let mut transcript = T::init(vec![]); - - create_plonk_proof::( - params, - pk, - &[circuit.clone(), circuit.clone()], - &[&[&[instance]], &[&[instance]]], - rng, - &mut transcript, - ) - .expect("proof generation should not fail"); - - // Check this circuit is satisfied. - let prover = match MockProver::run(K, &circuit, vec![vec![instance]]) { - Ok(prover) => prover, - Err(e) => panic!("{e:?}"), - }; - assert_eq!(prover.verify(), Ok(())); - - transcript.finalize() - } - - fn verify_proof< - 'a, - 'params, - Scheme: CommitmentScheme, - V: Verifier<'params, Scheme>, - E: EncodedChallenge, - T: TranscriptReadBuffer<&'a [u8], Scheme::Curve, E>, - Strategy: VerificationStrategy<'params, Scheme, V, Output = Strategy>, - >( - params_verifier: &'params Scheme::ParamsVerifier, - vk: &VerifyingKey, - proof: &'a [u8], - ) where - Scheme::Scalar: Ord + WithSmallOrderMulGroup<3> + FromUniformBytes<64>, - { - let (_, instance, _) = common!(Scheme); - let pubinputs = [instance]; - - let mut transcript = T::init(proof); - - let strategy = Strategy::new(params_verifier); - let strategy = verify_plonk_proof( - params_verifier, - vk, - strategy, - &[&[&pubinputs[..]], &[&pubinputs[..]]], - &mut transcript, - ) - .unwrap(); - - assert!(strategy.finalize()); - } - - use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; - use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; - use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; - use halo2curves::bn256::Bn256; - - type Scheme = KZGCommitmentScheme; - bad_keys!(Scheme); - - let params = ParamsKZG::::new(K); - let rng = OsRng; - - let pk = keygen::>(¶ms); - - let proof = create_proof::<_, ProverGWC<_>, _, _, Blake2bWrite<_, _, Challenge255<_>>>( - rng, ¶ms, &pk, - ); - - let verifier_params = params.verifier_params(); - - verify_proof::<_, VerifierGWC<_>, _, Blake2bRead<_, _, Challenge255<_>>, AccumulatorStrategy<_>>( - verifier_params, - pk.get_vk(), - &proof[..], - ); -} +// #![allow(clippy::many_single_char_names)] +// #![allow(clippy::op_ref)] +// +// use assert_matches::assert_matches; +// use ff::{FromUniformBytes}; +// use halo2_proofs::arithmetic::Field; +// use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; +// use halo2_proofs::dev::MockProver; +// use halo2_proofs::plonk::{ +// create_proof as create_plonk_proof, keygen_pk, keygen_vk, verify_proof as verify_plonk_proof, +// Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, ProvingKey, TableColumn, +// VerifyingKey, +// }; +// use halo2_proofs::poly::commitment::{CommitmentScheme, ParamsProver, Prover, Verifier}; +// use halo2_proofs::poly::Rotation; +// use halo2_proofs::poly::VerificationStrategy; +// use halo2_proofs::transcript::{ +// Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge, TranscriptReadBuffer, +// TranscriptWriterBuffer, +// }; +// use rand_core::{OsRng, RngCore}; +// use std::marker::PhantomData; +// +// #[test] +// fn plonk_api() { +// const K: u32 = 5; +// +// /// This represents an advice column at a certain row in the ConstraintSystem +// #[derive(Copy, Clone, Debug)] +// pub struct Variable(Column, usize); +// +// #[derive(Clone)] +// struct PlonkConfig { +// a: Column, +// b: Column, +// c: Column, +// d: Column, +// e: Column, +// +// sa: Column, +// sb: Column, +// sc: Column, +// sm: Column, +// sp: Column, +// sl: TableColumn, +// } +// +// #[allow(clippy::type_complexity)] +// trait StandardCs { +// fn raw_multiply( +// &self, +// layouter: &mut impl Layouter, +// f: F, +// ) -> Result<(Cell, Cell, Cell), Error> +// where +// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; +// fn raw_add( +// &self, +// layouter: &mut impl Layouter, +// f: F, +// ) -> Result<(Cell, Cell, Cell), Error> +// where +// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; +// fn copy(&self, layouter: &mut impl Layouter, a: Cell, b: Cell) -> Result<(), Error>; +// fn public_input(&self, layouter: &mut impl Layouter, f: F) -> Result +// where +// F: FnMut() -> Value; +// fn lookup_table( +// &self, +// layouter: &mut impl Layouter, +// values: &[FF], +// ) -> Result<(), Error>; +// } +// +// #[derive(Clone)] +// struct MyCircuit { +// a: Value, +// lookup_table: Vec, +// } +// +// struct StandardPlonk { +// config: PlonkConfig, +// _marker: PhantomData, +// } +// +// impl StandardPlonk { +// fn new(config: PlonkConfig) -> Self { +// StandardPlonk { +// config, +// _marker: PhantomData, +// } +// } +// } +// +// impl StandardCs for StandardPlonk { +// fn raw_multiply( +// &self, +// layouter: &mut impl Layouter, +// mut f: F, +// ) -> Result<(Cell, Cell, Cell), Error> +// where +// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, +// { +// layouter.assign_region( +// || "raw_multiply", +// |mut region| { +// let mut value = None; +// let lhs = region.assign_advice( +// || "lhs", +// self.config.a, +// 0, +// || { +// value = Some(f()); +// value.unwrap().map(|v| v.0) +// }, +// )?; +// region.assign_advice( +// || "lhs^4", +// self.config.d, +// 0, +// || value.unwrap().map(|v| v.0).square().square(), +// )?; +// let rhs = region.assign_advice( +// || "rhs", +// self.config.b, +// 0, +// || value.unwrap().map(|v| v.1), +// )?; +// region.assign_advice( +// || "rhs^4", +// self.config.e, +// 0, +// || value.unwrap().map(|v| v.1).square().square(), +// )?; +// let out = region.assign_advice( +// || "out", +// self.config.c, +// 0, +// || value.unwrap().map(|v| v.2), +// )?; +// +// region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ZERO))?; +// region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ZERO))?; +// region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; +// region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; +// Ok((lhs.cell(), rhs.cell(), out.cell())) +// }, +// ) +// } +// fn raw_add( +// &self, +// layouter: &mut impl Layouter, +// mut f: F, +// ) -> Result<(Cell, Cell, Cell), Error> +// where +// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, +// { +// layouter.assign_region( +// || "raw_add", +// |mut region| { +// let mut value = None; +// let lhs = region.assign_advice( +// || "lhs", +// self.config.a, +// 0, +// || { +// value = Some(f()); +// value.unwrap().map(|v| v.0) +// }, +// )?; +// region.assign_advice( +// || "lhs^4", +// self.config.d, +// 0, +// || value.unwrap().map(|v| v.0).square().square(), +// )?; +// let rhs = region.assign_advice( +// || "rhs", +// self.config.b, +// 0, +// || value.unwrap().map(|v| v.1), +// )?; +// region.assign_advice( +// || "rhs^4", +// self.config.e, +// 0, +// || value.unwrap().map(|v| v.1).square().square(), +// )?; +// let out = region.assign_advice( +// || "out", +// self.config.c, +// 0, +// || value.unwrap().map(|v| v.2), +// )?; +// +// region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ONE))?; +// region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ONE))?; +// region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; +// region.assign_fixed( +// || "a * b", +// self.config.sm, +// 0, +// || Value::known(FF::ZERO), +// )?; +// Ok((lhs.cell(), rhs.cell(), out.cell())) +// }, +// ) +// } +// fn copy( +// &self, +// layouter: &mut impl Layouter, +// left: Cell, +// right: Cell, +// ) -> Result<(), Error> { +// layouter.assign_region( +// || "copy", +// |mut region| { +// region.constrain_equal(left, right)?; +// region.constrain_equal(left, right) +// }, +// ) +// } +// fn public_input(&self, layouter: &mut impl Layouter, mut f: F) -> Result +// where +// F: FnMut() -> Value, +// { +// layouter.assign_region( +// || "public_input", +// |mut region| { +// let value = region.assign_advice(|| "value", self.config.a, 0, &mut f)?; +// region.assign_fixed( +// || "public", +// self.config.sp, +// 0, +// || Value::known(FF::ONE), +// )?; +// +// Ok(value.cell()) +// }, +// ) +// } +// fn lookup_table( +// &self, +// layouter: &mut impl Layouter, +// values: &[FF], +// ) -> Result<(), Error> { +// layouter.assign_table( +// || "", +// |mut table| { +// for (index, &value) in values.iter().enumerate() { +// table.assign_cell( +// || "table col", +// self.config.sl, +// index, +// || Value::known(value), +// )?; +// } +// Ok(()) +// }, +// )?; +// Ok(()) +// } +// } +// +// impl Circuit for MyCircuit { +// type Config = PlonkConfig; +// type FloorPlanner = SimpleFloorPlanner; +// #[cfg(feature = "circuit-params")] +// type Params = (); +// +// fn without_witnesses(&self) -> Self { +// Self { +// a: Value::unknown(), +// lookup_table: self.lookup_table.clone(), +// } +// } +// +// fn configure(meta: &mut ConstraintSystem) -> PlonkConfig { +// let e = meta.advice_column(); +// let a = meta.advice_column(); +// let b = meta.advice_column(); +// let sf = meta.fixed_column(); +// let c = meta.advice_column(); +// let d = meta.advice_column(); +// let p = meta.instance_column(); +// +// meta.enable_equality(a); +// meta.enable_equality(b); +// meta.enable_equality(c); +// +// let sm = meta.fixed_column(); +// let sa = meta.fixed_column(); +// let sb = meta.fixed_column(); +// let sc = meta.fixed_column(); +// let sp = meta.fixed_column(); +// let sl = meta.lookup_table_column(); +// +// /* +// * A B ... sl +// * [ +// * instance 0 ... 0 +// * a a ... 0 +// * a a^2 ... 0 +// * a a ... 0 +// * a a^2 ... 0 +// * ... ... ... ... +// * ... ... ... instance +// * ... ... ... a +// * ... ... ... a +// * ... ... ... 0 +// * ] +// */ +// +// meta.lookup("lookup", |meta| { +// let a_ = meta.query_any(a, Rotation::cur()); +// vec![(a_, sl)] +// }); +// +// meta.create_gate("Combined add-mult", |meta| { +// let d = meta.query_advice(d, Rotation::next()); +// let a = meta.query_advice(a, Rotation::cur()); +// let sf = meta.query_fixed(sf, Rotation::cur()); +// let e = meta.query_advice(e, Rotation::prev()); +// let b = meta.query_advice(b, Rotation::cur()); +// let c = meta.query_advice(c, Rotation::cur()); +// +// let sa = meta.query_fixed(sa, Rotation::cur()); +// let sb = meta.query_fixed(sb, Rotation::cur()); +// let sc = meta.query_fixed(sc, Rotation::cur()); +// let sm = meta.query_fixed(sm, Rotation::cur()); +// +// vec![a.clone() * sa + b.clone() * sb + a * b * sm - (c * sc) + sf * (d * e)] +// }); +// +// meta.create_gate("Public input", |meta| { +// let a = meta.query_advice(a, Rotation::cur()); +// let p = meta.query_instance(p, Rotation::cur()); +// let sp = meta.query_fixed(sp, Rotation::cur()); +// +// vec![sp * (a - p)] +// }); +// +// meta.enable_equality(sf); +// meta.enable_equality(e); +// meta.enable_equality(d); +// meta.enable_equality(p); +// meta.enable_equality(sm); +// meta.enable_equality(sa); +// meta.enable_equality(sb); +// meta.enable_equality(sc); +// meta.enable_equality(sp); +// +// PlonkConfig { +// a, +// b, +// c, +// d, +// e, +// sa, +// sb, +// sc, +// sm, +// sp, +// sl, +// } +// } +// +// fn synthesize( +// &self, +// config: PlonkConfig, +// mut layouter: impl Layouter, +// ) -> Result<(), Error> { +// let cs = StandardPlonk::new(config); +// +// let _ = cs.public_input(&mut layouter, || Value::known(F::ONE + F::ONE))?; +// +// for _ in 0..10 { +// let a: Value> = self.a.into(); +// let mut a_squared = Value::unknown(); +// let (a0, _, c0) = cs.raw_multiply(&mut layouter, || { +// a_squared = a.square(); +// a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) +// })?; +// let (a1, b1, _) = cs.raw_add(&mut layouter, || { +// let fin = a_squared + a; +// a.zip(a_squared) +// .zip(fin) +// .map(|((a, a_squared), fin)| (a, a_squared, fin)) +// })?; +// cs.copy(&mut layouter, a0, a1)?; +// cs.copy(&mut layouter, b1, c0)?; +// } +// +// cs.lookup_table(&mut layouter, &self.lookup_table)?; +// +// Ok(()) +// } +// } +// +// macro_rules! common { +// ($scheme:ident) => {{ +// let a = <$scheme as CommitmentScheme>::Scalar::from(2834758237) +// * <$scheme as CommitmentScheme>::Scalar::ZETA; +// let instance = <$scheme as CommitmentScheme>::Scalar::ONE +// + <$scheme as CommitmentScheme>::Scalar::ONE; +// let lookup_table = vec![instance, a, a, <$scheme as CommitmentScheme>::Scalar::ZERO]; +// (a, instance, lookup_table) +// }}; +// } +// +// macro_rules! bad_keys { +// ($scheme:ident) => {{ +// let (_, _, lookup_table) = common!($scheme); +// let empty_circuit: MyCircuit<<$scheme as CommitmentScheme>::Scalar> = MyCircuit { +// a: Value::unknown(), +// lookup_table: lookup_table.clone(), +// }; +// +// // Check that we get an error if we try to initialize the proving key with a value of +// // k that is too small for the minimum required number of rows. +// let much_too_small_params= <$scheme as CommitmentScheme>::ParamsProver::new(1); +// assert_matches!( +// keygen_vk(&much_too_small_params, &empty_circuit), +// Err(Error::NotEnoughRowsAvailable { +// current_k, +// }) if current_k == 1 +// ); +// +// // Check that we get an error if we try to initialize the proving key with a value of +// // k that is too small for the number of rows the circuit uses. +// let slightly_too_small_params = <$scheme as CommitmentScheme>::ParamsProver::new(K-1); +// assert_matches!( +// keygen_vk(&slightly_too_small_params, &empty_circuit), +// Err(Error::NotEnoughRowsAvailable { +// current_k, +// }) if current_k == K - 1 +// ); +// }}; +// } +// +// fn keygen(params: &Scheme::ParamsProver) -> ProvingKey +// where +// Scheme::Scalar: FromUniformBytes<64> + WithSmallOrderMulGroup<3>, +// { +// let (_, _, lookup_table) = common!(Scheme); +// let empty_circuit: MyCircuit = MyCircuit { +// a: Value::unknown(), +// lookup_table, +// }; +// +// // Initialize the proving key +// let vk = keygen_vk(params, &empty_circuit).expect("keygen_vk should not fail"); +// +// keygen_pk(params, vk, &empty_circuit).expect("keygen_pk should not fail") +// } +// +// fn create_proof< +// 'params, +// Scheme: CommitmentScheme, +// P: Prover<'params, Scheme>, +// E: EncodedChallenge, +// R: RngCore, +// T: TranscriptWriterBuffer, Scheme::Curve, E>, +// >( +// rng: R, +// params: &'params Scheme::ParamsProver, +// pk: &ProvingKey, +// ) -> Vec +// where +// Scheme::Scalar: Ord + WithSmallOrderMulGroup<3> + FromUniformBytes<64>, +// { +// let (a, instance, lookup_table) = common!(Scheme); +// +// let circuit: MyCircuit = MyCircuit { +// a: Value::known(a), +// lookup_table, +// }; +// +// let mut transcript = T::init(vec![]); +// +// create_plonk_proof::( +// params, +// pk, +// &[circuit.clone(), circuit.clone()], +// &[&[&[instance]], &[&[instance]]], +// rng, +// &mut transcript, +// ) +// .expect("proof generation should not fail"); +// +// // Check this circuit is satisfied. +// let prover = match MockProver::run(K, &circuit, vec![vec![instance]]) { +// Ok(prover) => prover, +// Err(e) => panic!("{e:?}"), +// }; +// assert_eq!(prover.verify(), Ok(())); +// +// transcript.finalize() +// } +// +// fn verify_proof< +// 'a, +// 'params, +// Scheme: CommitmentScheme, +// V: Verifier<'params, Scheme>, +// E: EncodedChallenge, +// T: TranscriptReadBuffer<&'a [u8], Scheme::Curve, E>, +// Strategy: VerificationStrategy<'params, Scheme, V, Output = Strategy>, +// >( +// params_verifier: &'params Scheme::ParamsVerifier, +// vk: &VerifyingKey, +// proof: &'a [u8], +// ) where +// Scheme::Scalar: Ord + WithSmallOrderMulGroup<3> + FromUniformBytes<64>, +// { +// let (_, instance, _) = common!(Scheme); +// let pubinputs = [instance]; +// +// let mut transcript = T::init(proof); +// +// let strategy = Strategy::new(params_verifier); +// let strategy = verify_plonk_proof( +// params_verifier, +// vk, +// strategy, +// &[&[&pubinputs[..]], &[&pubinputs[..]]], +// &mut transcript, +// ) +// .unwrap(); +// +// assert!(strategy.finalize()); +// } +// +// use halo2_proofs::poly::kzg::params::{KZGCommitmentScheme, ParamsKZG}; +// use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; +// use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; +// use halo2curves::bn256::Bn256; +// +// type Scheme = KZGCommitmentScheme; +// bad_keys!(Scheme); +// +// let params = ParamsKZG::::new(K); +// let rng = OsRng; +// +// let pk = keygen::>(¶ms); +// +// let proof = create_proof::<_, ProverGWC<_>, _, _, Blake2bWrite<_, _, Challenge255<_>>>( +// rng, ¶ms, &pk, +// ); +// +// let verifier_params = params.verifier_params(); +// +// verify_proof::<_, VerifierGWC<_>, _, Blake2bRead<_, _, Challenge255<_>>, AccumulatorStrategy<_>>( +// verifier_params, +// pk.get_vk(), +// &proof[..], +// ); +// } From 522b2abf34c0e6fa196039ca7ba3ebb57e2fb92b Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Fri, 6 Dec 2024 15:45:25 +0100 Subject: [PATCH 09/20] Bring examples and benches back --- Cargo.toml | 38 +- benches/dev_lookup.rs | 232 +++---- benches/plonk.rs | 703 ++++++++++--------- examples/circuit-layout.rs | 612 ++++++++-------- examples/proof-size.rs | 202 +++--- examples/serialization.rs | 370 +++++----- examples/shuffle.rs | 705 +++++++++---------- examples/simple-example.rs | 686 +++++++++--------- examples/two-chip.rs | 1076 ++++++++++++++-------------- examples/vector-mul.rs | 633 +++++++++-------- examples/vector-ops-unblinded.rs | 1116 +++++++++++++++--------------- src/plonk/keygen.rs | 7 +- src/plonk/vanishing/verifier.rs | 8 +- src/plonk/verifier.rs | 2 +- 14 files changed, 3165 insertions(+), 3225 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cd05bb6a6e..1e865a63ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,25 +23,25 @@ keywords = ["halo", "proofs", "zkp", "zkSNARKs"] all-features = true rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] -#[[bench]] -#name = "commit_zk" -#harness = false -# -#[[bench]] -#name = "hashtocurve" -#harness = false -# -#[[bench]] -#name = "plonk" -#harness = false -# -#[[bench]] -#name = "dev_lookup" -#harness = false -# -#[[bench]] -#name = "fft" -#harness = false +[[bench]] +name = "commit_zk" +harness = false + +[[bench]] +name = "hashtocurve" +harness = false + +[[bench]] +name = "plonk" +harness = false + +[[bench]] +name = "dev_lookup" +harness = false + +[[bench]] +name = "fft" +harness = false [dependencies] backtrace = { version = "0.3", optional = true } diff --git a/benches/dev_lookup.rs b/benches/dev_lookup.rs index fd1ebb9c51..569ffd1019 100644 --- a/benches/dev_lookup.rs +++ b/benches/dev_lookup.rs @@ -1,116 +1,116 @@ -// #[macro_use] -// extern crate criterion; -// -// use ff::{Field, PrimeField}; -// use halo2_proofs::circuit::{Layouter, SimpleFloorPlanner, Value}; -// use halo2_proofs::dev::MockProver; -// use halo2_proofs::plonk::*; -// use halo2_proofs::poly::Rotation; -// use halo2curves::pasta::pallas; -// -// use std::marker::PhantomData; -// -// use criterion::{BenchmarkId, Criterion}; -// -// fn criterion_benchmark(c: &mut Criterion) { -// #[derive(Clone, Default)] -// struct MyCircuit { -// _marker: PhantomData, -// } -// -// #[derive(Clone)] -// struct MyConfig { -// selector: Selector, -// table: TableColumn, -// advice: Column, -// } -// -// impl Circuit for MyCircuit { -// type Config = MyConfig; -// type FloorPlanner = SimpleFloorPlanner; -// #[cfg(feature = "circuit-params")] -// type Params = (); -// -// fn without_witnesses(&self) -> Self { -// Self::default() -// } -// -// fn configure(meta: &mut ConstraintSystem) -> MyConfig { -// let config = MyConfig { -// selector: meta.complex_selector(), -// table: meta.lookup_table_column(), -// advice: meta.advice_column(), -// }; -// -// meta.lookup("lookup", |meta| { -// let selector = meta.query_selector(config.selector); -// let not_selector = Expression::Constant(F::ONE) - selector.clone(); -// let advice = meta.query_advice(config.advice, Rotation::cur()); -// vec![(selector * advice + not_selector, config.table)] -// }); -// -// config -// } -// -// fn synthesize( -// &self, -// config: MyConfig, -// mut layouter: impl Layouter, -// ) -> Result<(), Error> { -// layouter.assign_table( -// || "8-bit table", -// |mut table| { -// for row in 0u64..(1 << 8) { -// table.assign_cell( -// || format!("row {row}"), -// config.table, -// row as usize, -// || Value::known(F::from(row + 1)), -// )?; -// } -// -// Ok(()) -// }, -// )?; -// -// layouter.assign_region( -// || "assign values", -// |mut region| { -// for offset in 0u64..(1 << 10) { -// config.selector.enable(&mut region, offset as usize)?; -// region.assign_advice( -// || format!("offset {offset}"), -// config.advice, -// offset as usize, -// || Value::known(F::from((offset % 256) + 1)), -// )?; -// } -// -// Ok(()) -// }, -// ) -// } -// } -// -// fn prover(k: u32) { -// let circuit = MyCircuit:: { -// _marker: PhantomData, -// }; -// let prover = MockProver::run(k, &circuit, vec![]).unwrap(); -// assert_eq!(prover.verify(), Ok(())) -// } -// -// let k_range = 14..=18; -// -// let mut prover_group = c.benchmark_group("dev-lookup"); -// prover_group.sample_size(10); -// for k in k_range { -// prover_group.bench_with_input(BenchmarkId::from_parameter(k), &k, |b, &k| { -// b.iter(|| prover(k)); -// }); -// } -// prover_group.finish(); -// } -// -// criterion_group!(benches, criterion_benchmark); -// criterion_main!(benches); +#[macro_use] +extern crate criterion; + +use ff::{Field, PrimeField}; +use halo2_proofs::circuit::{Layouter, SimpleFloorPlanner, Value}; +use halo2_proofs::dev::MockProver; +use halo2_proofs::plonk::*; +use halo2_proofs::poly::Rotation; +use halo2curves::pasta::pallas; + +use std::marker::PhantomData; + +use criterion::{BenchmarkId, Criterion}; + +fn criterion_benchmark(c: &mut Criterion) { + #[derive(Clone, Default)] + struct MyCircuit { + _marker: PhantomData, + } + + #[derive(Clone)] + struct MyConfig { + selector: Selector, + table: TableColumn, + advice: Column, + } + + impl Circuit for MyCircuit { + type Config = MyConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> MyConfig { + let config = MyConfig { + selector: meta.complex_selector(), + table: meta.lookup_table_column(), + advice: meta.advice_column(), + }; + + meta.lookup("lookup", |meta| { + let selector = meta.query_selector(config.selector); + let not_selector = Expression::Constant(F::ONE) - selector.clone(); + let advice = meta.query_advice(config.advice, Rotation::cur()); + vec![(selector * advice + not_selector, config.table)] + }); + + config + } + + fn synthesize( + &self, + config: MyConfig, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_table( + || "8-bit table", + |mut table| { + for row in 0u64..(1 << 8) { + table.assign_cell( + || format!("row {row}"), + config.table, + row as usize, + || Value::known(F::from(row + 1)), + )?; + } + + Ok(()) + }, + )?; + + layouter.assign_region( + || "assign values", + |mut region| { + for offset in 0u64..(1 << 10) { + config.selector.enable(&mut region, offset as usize)?; + region.assign_advice( + || format!("offset {offset}"), + config.advice, + offset as usize, + || Value::known(F::from((offset % 256) + 1)), + )?; + } + + Ok(()) + }, + ) + } + } + + fn prover(k: u32) { + let circuit = MyCircuit:: { + _marker: PhantomData, + }; + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())) + } + + let k_range = 14..=18; + + let mut prover_group = c.benchmark_group("dev-lookup"); + prover_group.sample_size(10); + for k in k_range { + prover_group.bench_with_input(BenchmarkId::from_parameter(k), &k, |b, &k| { + b.iter(|| prover(k)); + }); + } + prover_group.finish(); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/benches/plonk.rs b/benches/plonk.rs index f68afcf883..8fc574cdf4 100644 --- a/benches/plonk.rs +++ b/benches/plonk.rs @@ -1,353 +1,350 @@ -// #[macro_use] -// extern crate criterion; -// -// use group::ff::Field; -// use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; -// use halo2_proofs::plonk::*; -// use halo2_proofs::poly::{commitment::ParamsProver, Rotation}; -// use halo2_proofs::transcript::{Blake2bRead, Blake2bWrite, Challenge255}; -// use halo2curves::bn256; -// use rand_core::OsRng; -// -// use halo2_proofs::transcript::{TranscriptReadBuffer, TranscriptWriterBuffer}; -// -// use std::marker::PhantomData; -// -// use criterion::{BenchmarkId, Criterion}; -// use halo2_proofs::poly::kzg::params::{KZGCommitmentScheme, ParamsKZG}; -// use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; -// use halo2_proofs::poly::kzg::strategy::SingleStrategy; -// -// fn criterion_benchmark(c: &mut Criterion) { -// /// This represents an advice column at a certain row in the ConstraintSystem -// #[derive(Copy, Clone, Debug)] -// pub struct Variable(Column, usize); -// -// #[derive(Clone)] -// struct PlonkConfig { -// a: Column, -// b: Column, -// c: Column, -// -// sa: Column, -// sb: Column, -// sc: Column, -// sm: Column, -// } -// -// trait StandardCs { -// fn raw_multiply( -// &self, -// layouter: &mut impl Layouter, -// f: F, -// ) -> Result<(Cell, Cell, Cell), Error> -// where -// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; -// fn raw_add( -// &self, -// layouter: &mut impl Layouter, -// f: F, -// ) -> Result<(Cell, Cell, Cell), Error> -// where -// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; -// fn copy(&self, layouter: &mut impl Layouter, a: Cell, b: Cell) -> Result<(), Error>; -// } -// -// #[derive(Clone)] -// struct MyCircuit { -// a: Value, -// k: u32, -// } -// -// struct StandardPlonk { -// config: PlonkConfig, -// _marker: PhantomData, -// } -// -// impl StandardPlonk { -// fn new(config: PlonkConfig) -> Self { -// StandardPlonk { -// config, -// _marker: PhantomData, -// } -// } -// } -// -// impl StandardCs for StandardPlonk { -// fn raw_multiply( -// &self, -// layouter: &mut impl Layouter, -// mut f: F, -// ) -> Result<(Cell, Cell, Cell), Error> -// where -// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, -// { -// layouter.assign_region( -// || "raw_multiply", -// |mut region| { -// let mut value = None; -// let lhs = region.assign_advice( -// || "lhs", -// self.config.a, -// 0, -// || { -// value = Some(f()); -// value.unwrap().map(|v| v.0) -// }, -// )?; -// let rhs = region.assign_advice( -// || "rhs", -// self.config.b, -// 0, -// || value.unwrap().map(|v| v.1), -// )?; -// let out = region.assign_advice( -// || "out", -// self.config.c, -// 0, -// || value.unwrap().map(|v| v.2), -// )?; -// -// region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ZERO))?; -// region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ZERO))?; -// region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; -// region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; -// Ok((lhs.cell(), rhs.cell(), out.cell())) -// }, -// ) -// } -// fn raw_add( -// &self, -// layouter: &mut impl Layouter, -// mut f: F, -// ) -> Result<(Cell, Cell, Cell), Error> -// where -// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, -// { -// layouter.assign_region( -// || "raw_add", -// |mut region| { -// let mut value = None; -// let lhs = region.assign_advice( -// || "lhs", -// self.config.a, -// 0, -// || { -// value = Some(f()); -// value.unwrap().map(|v| v.0) -// }, -// )?; -// let rhs = region.assign_advice( -// || "rhs", -// self.config.b, -// 0, -// || value.unwrap().map(|v| v.1), -// )?; -// let out = region.assign_advice( -// || "out", -// self.config.c, -// 0, -// || value.unwrap().map(|v| v.2), -// )?; -// -// region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ONE))?; -// region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ONE))?; -// region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; -// region.assign_fixed( -// || "a * b", -// self.config.sm, -// 0, -// || Value::known(FF::ZERO), -// )?; -// Ok((lhs.cell(), rhs.cell(), out.cell())) -// }, -// ) -// } -// fn copy( -// &self, -// layouter: &mut impl Layouter, -// left: Cell, -// right: Cell, -// ) -> Result<(), Error> { -// layouter.assign_region(|| "copy", |mut region| region.constrain_equal(left, right)) -// } -// } -// -// impl Circuit for MyCircuit { -// type Config = PlonkConfig; -// type FloorPlanner = SimpleFloorPlanner; -// #[cfg(feature = "circuit-params")] -// type Params = (); -// -// fn without_witnesses(&self) -> Self { -// Self { -// a: Value::unknown(), -// k: self.k, -// } -// } -// -// fn configure(meta: &mut ConstraintSystem) -> PlonkConfig { -// meta.set_minimum_degree(5); -// -// let a = meta.advice_column(); -// let b = meta.advice_column(); -// let c = meta.advice_column(); -// -// meta.enable_equality(a); -// meta.enable_equality(b); -// meta.enable_equality(c); -// -// let sm = meta.fixed_column(); -// let sa = meta.fixed_column(); -// let sb = meta.fixed_column(); -// let sc = meta.fixed_column(); -// -// meta.create_gate("Combined add-mult", |meta| { -// let a = meta.query_advice(a, Rotation::cur()); -// let b = meta.query_advice(b, Rotation::cur()); -// let c = meta.query_advice(c, Rotation::cur()); -// -// let sa = meta.query_fixed(sa, Rotation::cur()); -// let sb = meta.query_fixed(sb, Rotation::cur()); -// let sc = meta.query_fixed(sc, Rotation::cur()); -// let sm = meta.query_fixed(sm, Rotation::cur()); -// -// vec![a.clone() * sa + b.clone() * sb + a * b * sm - (c * sc)] -// }); -// -// PlonkConfig { -// a, -// b, -// c, -// sa, -// sb, -// sc, -// sm, -// } -// } -// -// fn synthesize( -// &self, -// config: PlonkConfig, -// mut layouter: impl Layouter, -// ) -> Result<(), Error> { -// let cs = StandardPlonk::new(config); -// -// for _ in 0..((1 << (self.k - 1)) - 3) { -// let a: Value> = self.a.into(); -// let mut a_squared = Value::unknown(); -// let (a0, _, c0) = cs.raw_multiply(&mut layouter, || { -// a_squared = a.square(); -// a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) -// })?; -// let (a1, b1, _) = cs.raw_add(&mut layouter, || { -// let fin = a_squared + a; -// a.zip(a_squared) -// .zip(fin) -// .map(|((a, a_squared), fin)| (a, a_squared, fin)) -// })?; -// cs.copy(&mut layouter, a0, a1)?; -// cs.copy(&mut layouter, b1, c0)?; -// } -// -// Ok(()) -// } -// } -// -// fn keygen(k: u32) -> (ParamsKZG, ProvingKey) { -// let params: ParamsKZG = ParamsKZG::new(k); -// let empty_circuit: MyCircuit = MyCircuit { -// a: Value::unknown(), -// k, -// }; -// let vk = keygen_vk(¶ms, &empty_circuit).expect("keygen_vk should not fail"); -// let pk = keygen_pk(¶ms, vk, &empty_circuit).expect("keygen_pk should not fail"); -// (params, pk) -// } -// -// fn prover( -// k: u32, -// params: &ParamsKZG, -// pk: &ProvingKey, -// ) -> Vec { -// let rng = OsRng; -// -// let circuit: MyCircuit = MyCircuit { -// a: Value::known(bn256::Fr::random(rng)), -// k, -// }; -// -// let mut transcript = Blake2bWrite::<_, _, Challenge255>::init(vec![]); -// create_proof::, ProverGWC, _, _, _, _>( -// params, -// pk, -// &[circuit], -// &[&[]], -// rng, -// &mut transcript, -// ) -// .expect("proof generation should not fail"); -// transcript.finalize() -// } -// -// fn verifier( -// params: &ParamsKZG, -// vk: &VerifyingKey, -// proof: &[u8], -// ) { -// let strategy = SingleStrategy::new(params); -// let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(proof); -// assert!(verify_proof::<_, VerifierGWC, _, _, _>( -// params, -// vk, -// strategy, -// &[&[]], -// &mut transcript -// ) -// .is_ok()); -// } -// -// let k_range = 8..=16; -// -// let mut keygen_group = c.benchmark_group("plonk-keygen"); -// keygen_group.sample_size(10); -// for k in k_range.clone() { -// keygen_group.bench_with_input(BenchmarkId::from_parameter(k), &k, |b, &k| { -// b.iter(|| keygen(k)); -// }); -// } -// keygen_group.finish(); -// -// let mut prover_group = c.benchmark_group("plonk-prover"); -// prover_group.sample_size(10); -// for k in k_range.clone() { -// let (params, pk) = keygen(k); -// -// prover_group.bench_with_input( -// BenchmarkId::from_parameter(k), -// &(k, ¶ms, &pk), -// |b, &(k, params, pk)| { -// b.iter(|| prover(k, params, pk)); -// }, -// ); -// } -// prover_group.finish(); -// -// let mut verifier_group = c.benchmark_group("plonk-verifier"); -// for k in k_range { -// let (params, pk) = keygen(k); -// let proof = prover(k, ¶ms, &pk); -// -// verifier_group.bench_with_input( -// BenchmarkId::from_parameter(k), -// &(¶ms, pk.get_vk(), &proof[..]), -// |b, &(params, vk, proof)| { -// b.iter(|| verifier(params, vk, proof)); -// }, -// ); -// } -// verifier_group.finish(); -// } -// -// criterion_group!(benches, criterion_benchmark); -// criterion_main!(benches); +#[macro_use] +extern crate criterion; + +use group::ff::Field; +use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; +use halo2_proofs::plonk::*; +use halo2_proofs::poly::{Rotation}; +use halo2curves::bn256; +use rand_core::OsRng; + + +use std::marker::PhantomData; + +use criterion::{BenchmarkId, Criterion}; +use halo2_proofs::poly::kzg::{KZGCommitmentScheme, params::ParamsKZG}; +use halo2_proofs::rational::Rational; +use halo2_proofs::transcript::{CircuitTranscript, Transcript}; + +fn criterion_benchmark(c: &mut Criterion) { + /// This represents an advice column at a certain row in the ConstraintSystem + #[derive(Copy, Clone, Debug)] + pub struct Variable(Column, usize); + + #[derive(Clone)] + struct PlonkConfig { + a: Column, + b: Column, + c: Column, + + sa: Column, + sb: Column, + sc: Column, + sm: Column, + } + + trait StandardCs { + fn raw_multiply( + &self, + layouter: &mut impl Layouter, + f: F, + ) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Rational, Rational, Rational)>; + fn raw_add( + &self, + layouter: &mut impl Layouter, + f: F, + ) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Rational, Rational, Rational)>; + fn copy(&self, layouter: &mut impl Layouter, a: Cell, b: Cell) -> Result<(), Error>; + } + + #[derive(Clone)] + struct MyCircuit { + a: Value, + k: u32, + } + + struct StandardPlonk { + config: PlonkConfig, + _marker: PhantomData, + } + + impl StandardPlonk { + fn new(config: PlonkConfig) -> Self { + StandardPlonk { + config, + _marker: PhantomData, + } + } + } + + impl StandardCs for StandardPlonk { + fn raw_multiply( + &self, + layouter: &mut impl Layouter, + mut f: F, + ) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Rational, Rational, Rational)>, + { + layouter.assign_region( + || "raw_multiply", + |mut region| { + let mut value = None; + let lhs = region.assign_advice( + || "lhs", + self.config.a, + 0, + || { + value = Some(f()); + value.unwrap().map(|v| v.0) + }, + )?; + let rhs = region.assign_advice( + || "rhs", + self.config.b, + 0, + || value.unwrap().map(|v| v.1), + )?; + let out = region.assign_advice( + || "out", + self.config.c, + 0, + || value.unwrap().map(|v| v.2), + )?; + + region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ZERO))?; + region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ZERO))?; + region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; + region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; + Ok((lhs.cell(), rhs.cell(), out.cell())) + }, + ) + } + fn raw_add( + &self, + layouter: &mut impl Layouter, + mut f: F, + ) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Rational, Rational, Rational)>, + { + layouter.assign_region( + || "raw_add", + |mut region| { + let mut value = None; + let lhs = region.assign_advice( + || "lhs", + self.config.a, + 0, + || { + value = Some(f()); + value.unwrap().map(|v| v.0) + }, + )?; + let rhs = region.assign_advice( + || "rhs", + self.config.b, + 0, + || value.unwrap().map(|v| v.1), + )?; + let out = region.assign_advice( + || "out", + self.config.c, + 0, + || value.unwrap().map(|v| v.2), + )?; + + region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ONE))?; + region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ONE))?; + region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; + region.assign_fixed( + || "a * b", + self.config.sm, + 0, + || Value::known(FF::ZERO), + )?; + Ok((lhs.cell(), rhs.cell(), out.cell())) + }, + ) + } + fn copy( + &self, + layouter: &mut impl Layouter, + left: Cell, + right: Cell, + ) -> Result<(), Error> { + layouter.assign_region(|| "copy", |mut region| region.constrain_equal(left, right)) + } + } + + impl Circuit for MyCircuit { + type Config = PlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self { + a: Value::unknown(), + k: self.k, + } + } + + fn configure(meta: &mut ConstraintSystem) -> PlonkConfig { + meta.set_minimum_degree(5); + + let a = meta.advice_column(); + let b = meta.advice_column(); + let c = meta.advice_column(); + + meta.enable_equality(a); + meta.enable_equality(b); + meta.enable_equality(c); + + let sm = meta.fixed_column(); + let sa = meta.fixed_column(); + let sb = meta.fixed_column(); + let sc = meta.fixed_column(); + + meta.create_gate("Combined add-mult", |meta| { + let a = meta.query_advice(a, Rotation::cur()); + let b = meta.query_advice(b, Rotation::cur()); + let c = meta.query_advice(c, Rotation::cur()); + + let sa = meta.query_fixed(sa, Rotation::cur()); + let sb = meta.query_fixed(sb, Rotation::cur()); + let sc = meta.query_fixed(sc, Rotation::cur()); + let sm = meta.query_fixed(sm, Rotation::cur()); + + vec![a.clone() * sa + b.clone() * sb + a * b * sm - (c * sc)] + }); + + PlonkConfig { + a, + b, + c, + sa, + sb, + sc, + sm, + } + } + + fn synthesize( + &self, + config: PlonkConfig, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let cs = StandardPlonk::new(config); + + for _ in 0..((1 << (self.k - 1)) - 3) { + let a: Value> = self.a.into(); + let mut a_squared = Value::unknown(); + let (a0, _, c0) = cs.raw_multiply(&mut layouter, || { + a_squared = a.square(); + a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) + })?; + let (a1, b1, _) = cs.raw_add(&mut layouter, || { + let fin = a_squared + a; + a.zip(a_squared) + .zip(fin) + .map(|((a, a_squared), fin)| (a, a_squared, fin)) + })?; + cs.copy(&mut layouter, a0, a1)?; + cs.copy(&mut layouter, b1, c0)?; + } + + Ok(()) + } + } + + fn keygen(k: u32) -> (ParamsKZG, ProvingKey>) { + let params: ParamsKZG = ParamsKZG::new(k); + let empty_circuit: MyCircuit = MyCircuit { + a: Value::unknown(), + k, + }; + let vk = keygen_vk(¶ms, &empty_circuit).expect("keygen_vk should not fail"); + let pk = keygen_pk(¶ms, vk, &empty_circuit).expect("keygen_pk should not fail"); + (params, pk) + } + + fn prover( + k: u32, + params: &ParamsKZG, + pk: &ProvingKey>, + ) -> Vec { + let rng = OsRng; + + let circuit: MyCircuit = MyCircuit { + a: Value::known(bn256::Fr::random(rng)), + k, + }; + + let mut transcript = CircuitTranscript::init(); + + create_proof::, _, _>( + params, + pk, + &[circuit], + &[&[]], + rng, + &mut transcript, + ) + .expect("proof generation should not fail"); + transcript.finalize() + } + + fn verifier( + params: &ParamsKZG, + vk: &VerifyingKey>, + proof: &[u8], + ) { + let mut transcript = CircuitTranscript::parse(proof); + assert!(verify_proof::, _>( + params, + vk, + &[&[]], + &mut transcript + ) + .is_ok()); + } + + let k_range = 8..=16; + + let mut keygen_group = c.benchmark_group("plonk-keygen"); + keygen_group.sample_size(10); + for k in k_range.clone() { + keygen_group.bench_with_input(BenchmarkId::from_parameter(k), &k, |b, &k| { + b.iter(|| keygen(k)); + }); + } + keygen_group.finish(); + + let mut prover_group = c.benchmark_group("plonk-prover"); + prover_group.sample_size(10); + for k in k_range.clone() { + let (params, pk) = keygen(k); + + prover_group.bench_with_input( + BenchmarkId::from_parameter(k), + &(k, ¶ms, &pk), + |b, &(k, params, pk)| { + b.iter(|| prover(k, params, pk)); + }, + ); + } + prover_group.finish(); + + let mut verifier_group = c.benchmark_group("plonk-verifier"); + for k in k_range { + let (params, pk) = keygen(k); + let proof = prover(k, ¶ms, &pk); + + verifier_group.bench_with_input( + BenchmarkId::from_parameter(k), + &(¶ms, pk.get_vk(), &proof[..]), + |b, &(params, vk, proof)| { + b.iter(|| verifier(params, vk, proof)); + }, + ); + } + verifier_group.finish(); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/examples/circuit-layout.rs b/examples/circuit-layout.rs index 0862485ff4..b65adf5599 100644 --- a/examples/circuit-layout.rs +++ b/examples/circuit-layout.rs @@ -1,308 +1,306 @@ -fn main() {} +use ff::Field; +use halo2_proofs::{ + circuit::{Cell, Layouter, Region, SimpleFloorPlanner, Value}, + plonk::{Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, TableColumn}, + poly::Rotation, +}; +use halo2curves::pasta::Fp; +use rand_core::OsRng; +use std::marker::PhantomData; -// use ff::Field; -// use halo2_proofs::{ -// circuit::{Cell, Layouter, Region, SimpleFloorPlanner, Value}, -// plonk::{Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, TableColumn}, -// poly::Rotation, -// }; -// use halo2curves::pasta::Fp; -// use rand_core::OsRng; -// use std::marker::PhantomData; -// -// /// This represents an advice column at a certain row in the ConstraintSystem -// #[derive(Copy, Clone, Debug)] -// pub struct Variable(Column, usize); -// -// #[derive(Clone)] -// struct PlonkConfig { -// a: Column, -// b: Column, -// c: Column, -// d: Column, -// e: Column, -// -// sa: Column, -// sb: Column, -// sc: Column, -// sm: Column, -// sl: TableColumn, -// } -// -// trait StandardCs { -// fn raw_multiply(&self, region: &mut Region, f: F) -> Result<(Cell, Cell, Cell), Error> -// where -// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; -// fn raw_add(&self, region: &mut Region, f: F) -> Result<(Cell, Cell, Cell), Error> -// where -// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; -// fn copy(&self, region: &mut Region, a: Cell, b: Cell) -> Result<(), Error>; -// fn lookup_table(&self, layouter: &mut impl Layouter, values: &[FF]) -> Result<(), Error>; -// } -// -// struct MyCircuit { -// a: Value, -// lookup_table: Vec, -// } -// -// struct StandardPlonk { -// config: PlonkConfig, -// _marker: PhantomData, -// } -// -// impl StandardPlonk { -// fn new(config: PlonkConfig) -> Self { -// StandardPlonk { -// config, -// _marker: PhantomData, -// } -// } -// } -// -// impl StandardCs for StandardPlonk { -// fn raw_multiply( -// &self, -// region: &mut Region, -// mut f: F, -// ) -> Result<(Cell, Cell, Cell), Error> -// where -// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, -// { -// let mut value = None; -// let lhs = region.assign_advice( -// || "lhs", -// self.config.a, -// 0, -// || { -// value = Some(f()); -// value.unwrap().map(|v| v.0) -// }, -// )?; -// region.assign_advice( -// || "lhs^4", -// self.config.d, -// 0, -// || value.unwrap().map(|v| v.0).square().square(), -// )?; -// let rhs = -// region.assign_advice(|| "rhs", self.config.b, 0, || value.unwrap().map(|v| v.1))?; -// region.assign_advice( -// || "rhs^4", -// self.config.e, -// 0, -// || value.unwrap().map(|v| v.1).square().square(), -// )?; -// let out = -// region.assign_advice(|| "out", self.config.c, 0, || value.unwrap().map(|v| v.2))?; -// -// region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ZERO))?; -// region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ZERO))?; -// region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; -// region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; -// Ok((lhs.cell(), rhs.cell(), out.cell())) -// } -// fn raw_add(&self, region: &mut Region, mut f: F) -> Result<(Cell, Cell, Cell), Error> -// where -// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, -// { -// let mut value = None; -// let lhs = region.assign_advice( -// || "lhs", -// self.config.a, -// 0, -// || { -// value = Some(f()); -// value.unwrap().map(|v| v.0) -// }, -// )?; -// region.assign_advice( -// || "lhs^4", -// self.config.d, -// 0, -// || value.unwrap().map(|v| v.0.square().square()), -// )?; -// let rhs = -// region.assign_advice(|| "rhs", self.config.b, 0, || value.unwrap().map(|v| v.1))?; -// region.assign_advice( -// || "rhs^4", -// self.config.e, -// 0, -// || value.unwrap().map(|v| v.1.square().square()), -// )?; -// let out = -// region.assign_advice(|| "out", self.config.c, 0, || value.unwrap().map(|v| v.2))?; -// -// region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ONE))?; -// region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ONE))?; -// region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; -// region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ZERO))?; -// Ok((lhs.cell(), rhs.cell(), out.cell())) -// } -// fn copy(&self, region: &mut Region, left: Cell, right: Cell) -> Result<(), Error> { -// region.constrain_equal(left, right) -// } -// fn lookup_table(&self, layouter: &mut impl Layouter, values: &[FF]) -> Result<(), Error> { -// layouter.assign_table( -// || "", -// |mut table| { -// for (index, &value) in values.iter().enumerate() { -// table.assign_cell( -// || "table col", -// self.config.sl, -// index, -// || Value::known(value), -// )?; -// } -// Ok(()) -// }, -// )?; -// Ok(()) -// } -// } -// -// impl Circuit for MyCircuit { -// type Config = PlonkConfig; -// type FloorPlanner = SimpleFloorPlanner; -// #[cfg(feature = "circuit-params")] -// type Params = (); -// -// fn without_witnesses(&self) -> Self { -// Self { -// a: Value::unknown(), -// lookup_table: self.lookup_table.clone(), -// } -// } -// -// #[allow(clippy::many_single_char_names)] -// fn configure(meta: &mut ConstraintSystem) -> PlonkConfig { -// let e = meta.advice_column(); -// let a = meta.advice_column(); -// let b = meta.advice_column(); -// let sf = meta.fixed_column(); -// let c = meta.advice_column(); -// let d = meta.advice_column(); -// -// meta.enable_equality(a); -// meta.enable_equality(b); -// meta.enable_equality(c); -// -// let sm = meta.fixed_column(); -// let sa = meta.fixed_column(); -// let sb = meta.fixed_column(); -// let sc = meta.fixed_column(); -// let sl = meta.lookup_table_column(); -// -// /* -// * A B ... sl -// * [ -// * instance 0 ... 0 -// * a a ... 0 -// * a a^2 ... 0 -// * a a ... 0 -// * a a^2 ... 0 -// * ... ... ... ... -// * ... ... ... instance -// * ... ... ... a -// * ... ... ... a -// * ... ... ... 0 -// * ] -// */ -// meta.lookup("lookup", |meta| { -// let a_ = meta.query_any(a, Rotation::cur()); -// vec![(a_, sl)] -// }); -// -// meta.create_gate("Combined add-mult", |meta| { -// let d = meta.query_advice(d, Rotation::next()); -// let a = meta.query_advice(a, Rotation::cur()); -// let sf = meta.query_fixed(sf, Rotation::cur()); -// let e = meta.query_advice(e, Rotation::prev()); -// let b = meta.query_advice(b, Rotation::cur()); -// let c = meta.query_advice(c, Rotation::cur()); -// -// let sa = meta.query_fixed(sa, Rotation::cur()); -// let sb = meta.query_fixed(sb, Rotation::cur()); -// let sc = meta.query_fixed(sc, Rotation::cur()); -// let sm = meta.query_fixed(sm, Rotation::cur()); -// -// vec![a.clone() * sa + b.clone() * sb + a * b * sm - (c * sc) + sf * (d * e)] -// }); -// -// PlonkConfig { -// a, -// b, -// c, -// d, -// e, -// sa, -// sb, -// sc, -// sm, -// sl, -// } -// } -// -// fn synthesize(&self, config: PlonkConfig, mut layouter: impl Layouter) -> Result<(), Error> { -// let cs = StandardPlonk::new(config); -// -// for i in 0..10 { -// layouter.assign_region( -// || format!("region_{i}"), -// |mut region| { -// let a: Value> = self.a.into(); -// let mut a_squared = Value::unknown(); -// let (a0, _, c0) = cs.raw_multiply(&mut region, || { -// a_squared = a.square(); -// a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) -// })?; -// let (a1, b1, _) = cs.raw_add(&mut region, || { -// let fin = a_squared + a; -// a.zip(a_squared) -// .zip(fin) -// .map(|((a, a_squared), fin)| (a, a_squared, fin)) -// })?; -// cs.copy(&mut region, a0, a1)?; -// cs.copy(&mut region, b1, c0) -// }, -// )?; -// } -// -// cs.lookup_table(&mut layouter, &self.lookup_table)?; -// -// Ok(()) -// } -// } -// -// // ANCHOR: dev-graph -// fn main() { -// // Prepare the circuit you want to render. -// // You don't need to include any witness variables. -// let a = Fp::random(OsRng); -// let instance = Fp::one() + Fp::one(); -// let lookup_table = vec![instance, a, a, Fp::zero()]; -// let circuit: MyCircuit = MyCircuit { -// a: Value::unknown(), -// lookup_table, -// }; -// -// // Create the area you want to draw on. -// // Use SVGBackend if you want to render to .svg instead. -// use plotters::prelude::*; -// let root = BitMapBackend::new("layout.png", (1024, 768)).into_drawing_area(); -// root.fill(&WHITE).unwrap(); -// let root = root -// .titled("Example Circuit Layout", ("sans-serif", 60)) -// .unwrap(); -// -// halo2_proofs::dev::CircuitLayout::default() -// // You can optionally render only a section of the circuit. -// .view_width(0..2) -// .view_height(0..16) -// // You can hide labels, which can be useful with smaller areas. -// .show_labels(false) -// // Render the circuit onto your area! -// // The first argument is the size parameter for the circuit. -// .render(5, &circuit, &root) -// .unwrap(); -// } -// // ANCHOR_END: dev-graph +/// This represents an advice column at a certain row in the ConstraintSystem +#[derive(Copy, Clone, Debug)] +pub struct Variable(Column, usize); + +#[derive(Clone)] +struct PlonkConfig { + a: Column, + b: Column, + c: Column, + d: Column, + e: Column, + + sa: Column, + sb: Column, + sc: Column, + sm: Column, + sl: TableColumn, +} + +trait StandardCs { + fn raw_multiply(&self, region: &mut Region, f: F) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; + fn raw_add(&self, region: &mut Region, f: F) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; + fn copy(&self, region: &mut Region, a: Cell, b: Cell) -> Result<(), Error>; + fn lookup_table(&self, layouter: &mut impl Layouter, values: &[FF]) -> Result<(), Error>; +} + +struct MyCircuit { + a: Value, + lookup_table: Vec, +} + +struct StandardPlonk { + config: PlonkConfig, + _marker: PhantomData, +} + +impl StandardPlonk { + fn new(config: PlonkConfig) -> Self { + StandardPlonk { + config, + _marker: PhantomData, + } + } +} + +impl StandardCs for StandardPlonk { + fn raw_multiply( + &self, + region: &mut Region, + mut f: F, + ) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, + { + let mut value = None; + let lhs = region.assign_advice( + || "lhs", + self.config.a, + 0, + || { + value = Some(f()); + value.unwrap().map(|v| v.0) + }, + )?; + region.assign_advice( + || "lhs^4", + self.config.d, + 0, + || value.unwrap().map(|v| v.0).square().square(), + )?; + let rhs = + region.assign_advice(|| "rhs", self.config.b, 0, || value.unwrap().map(|v| v.1))?; + region.assign_advice( + || "rhs^4", + self.config.e, + 0, + || value.unwrap().map(|v| v.1).square().square(), + )?; + let out = + region.assign_advice(|| "out", self.config.c, 0, || value.unwrap().map(|v| v.2))?; + + region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ZERO))?; + region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ZERO))?; + region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; + region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; + Ok((lhs.cell(), rhs.cell(), out.cell())) + } + fn raw_add(&self, region: &mut Region, mut f: F) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, + { + let mut value = None; + let lhs = region.assign_advice( + || "lhs", + self.config.a, + 0, + || { + value = Some(f()); + value.unwrap().map(|v| v.0) + }, + )?; + region.assign_advice( + || "lhs^4", + self.config.d, + 0, + || value.unwrap().map(|v| v.0.square().square()), + )?; + let rhs = + region.assign_advice(|| "rhs", self.config.b, 0, || value.unwrap().map(|v| v.1))?; + region.assign_advice( + || "rhs^4", + self.config.e, + 0, + || value.unwrap().map(|v| v.1.square().square()), + )?; + let out = + region.assign_advice(|| "out", self.config.c, 0, || value.unwrap().map(|v| v.2))?; + + region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ONE))?; + region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ONE))?; + region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; + region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ZERO))?; + Ok((lhs.cell(), rhs.cell(), out.cell())) + } + fn copy(&self, region: &mut Region, left: Cell, right: Cell) -> Result<(), Error> { + region.constrain_equal(left, right) + } + fn lookup_table(&self, layouter: &mut impl Layouter, values: &[FF]) -> Result<(), Error> { + layouter.assign_table( + || "", + |mut table| { + for (index, &value) in values.iter().enumerate() { + table.assign_cell( + || "table col", + self.config.sl, + index, + || Value::known(value), + )?; + } + Ok(()) + }, + )?; + Ok(()) + } +} + +impl Circuit for MyCircuit { + type Config = PlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self { + a: Value::unknown(), + lookup_table: self.lookup_table.clone(), + } + } + + #[allow(clippy::many_single_char_names)] + fn configure(meta: &mut ConstraintSystem) -> PlonkConfig { + let e = meta.advice_column(); + let a = meta.advice_column(); + let b = meta.advice_column(); + let sf = meta.fixed_column(); + let c = meta.advice_column(); + let d = meta.advice_column(); + + meta.enable_equality(a); + meta.enable_equality(b); + meta.enable_equality(c); + + let sm = meta.fixed_column(); + let sa = meta.fixed_column(); + let sb = meta.fixed_column(); + let sc = meta.fixed_column(); + let sl = meta.lookup_table_column(); + + /* + * A B ... sl + * [ + * instance 0 ... 0 + * a a ... 0 + * a a^2 ... 0 + * a a ... 0 + * a a^2 ... 0 + * ... ... ... ... + * ... ... ... instance + * ... ... ... a + * ... ... ... a + * ... ... ... 0 + * ] + */ + meta.lookup("lookup", |meta| { + let a_ = meta.query_any(a, Rotation::cur()); + vec![(a_, sl)] + }); + + meta.create_gate("Combined add-mult", |meta| { + let d = meta.query_advice(d, Rotation::next()); + let a = meta.query_advice(a, Rotation::cur()); + let sf = meta.query_fixed(sf, Rotation::cur()); + let e = meta.query_advice(e, Rotation::prev()); + let b = meta.query_advice(b, Rotation::cur()); + let c = meta.query_advice(c, Rotation::cur()); + + let sa = meta.query_fixed(sa, Rotation::cur()); + let sb = meta.query_fixed(sb, Rotation::cur()); + let sc = meta.query_fixed(sc, Rotation::cur()); + let sm = meta.query_fixed(sm, Rotation::cur()); + + vec![a.clone() * sa + b.clone() * sb + a * b * sm - (c * sc) + sf * (d * e)] + }); + + PlonkConfig { + a, + b, + c, + d, + e, + sa, + sb, + sc, + sm, + sl, + } + } + + fn synthesize(&self, config: PlonkConfig, mut layouter: impl Layouter) -> Result<(), Error> { + let cs = StandardPlonk::new(config); + + for i in 0..10 { + layouter.assign_region( + || format!("region_{i}"), + |mut region| { + let a: Value> = self.a.into(); + let mut a_squared = Value::unknown(); + let (a0, _, c0) = cs.raw_multiply(&mut region, || { + a_squared = a.square(); + a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) + })?; + let (a1, b1, _) = cs.raw_add(&mut region, || { + let fin = a_squared + a; + a.zip(a_squared) + .zip(fin) + .map(|((a, a_squared), fin)| (a, a_squared, fin)) + })?; + cs.copy(&mut region, a0, a1)?; + cs.copy(&mut region, b1, c0) + }, + )?; + } + + cs.lookup_table(&mut layouter, &self.lookup_table)?; + + Ok(()) + } +} + +// ANCHOR: dev-graph +fn main() { + // Prepare the circuit you want to render. + // You don't need to include any witness variables. + let a = Fp::random(OsRng); + let instance = Fp::one() + Fp::one(); + let lookup_table = vec![instance, a, a, Fp::zero()]; + let circuit: MyCircuit = MyCircuit { + a: Value::unknown(), + lookup_table, + }; + + // Create the area you want to draw on. + // Use SVGBackend if you want to render to .svg instead. + use plotters::prelude::*; + let root = BitMapBackend::new("layout.png", (1024, 768)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root + .titled("Example Circuit Layout", ("sans-serif", 60)) + .unwrap(); + + halo2_proofs::dev::CircuitLayout::default() + // You can optionally render only a section of the circuit. + .view_width(0..2) + .view_height(0..16) + // You can hide labels, which can be useful with smaller areas. + .show_labels(false) + // Render the circuit onto your area! + // The first argument is the size parameter for the circuit. + .render(5, &circuit, &root) + .unwrap(); +} +// ANCHOR_END: dev-graph diff --git a/examples/proof-size.rs b/examples/proof-size.rs index 139baaab19..3d5b242fb0 100644 --- a/examples/proof-size.rs +++ b/examples/proof-size.rs @@ -1,103 +1,101 @@ -fn main() {} +use ff::Field; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, +}; +use halo2curves::pasta::Fp; -// use ff::Field; -// use halo2_proofs::{ -// circuit::{Layouter, SimpleFloorPlanner, Value}, -// plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, -// }; -// use halo2curves::pasta::Fp; -// -// use halo2_proofs::dev::cost_model::{from_circuit_to_model_circuit, CommitmentScheme}; -// use halo2_proofs::plonk::{Expression, Selector, TableColumn}; -// use halo2_proofs::poly::Rotation; -// -// // We use a lookup example -// #[derive(Clone, Copy)] -// struct TestCircuit {} -// -// #[derive(Debug, Clone)] -// struct MyConfig { -// selector: Selector, -// table: TableColumn, -// advice: Column, -// } -// -// impl Circuit for TestCircuit { -// type Config = MyConfig; -// type FloorPlanner = SimpleFloorPlanner; -// #[cfg(feature = "circuit-params")] -// type Params = (); -// -// fn without_witnesses(&self) -> Self { -// Self {} -// } -// -// fn configure(meta: &mut ConstraintSystem) -> MyConfig { -// let config = MyConfig { -// selector: meta.complex_selector(), -// table: meta.lookup_table_column(), -// advice: meta.advice_column(), -// }; -// -// meta.lookup("lookup", |meta| { -// let selector = meta.query_selector(config.selector); -// let not_selector = Expression::Constant(Fp::ONE) - selector.clone(); -// let advice = meta.query_advice(config.advice, Rotation::cur()); -// vec![(selector * advice + not_selector, config.table)] -// }); -// -// config -// } -// -// fn synthesize(&self, config: MyConfig, mut layouter: impl Layouter) -> Result<(), Error> { -// layouter.assign_table( -// || "8-bit table", -// |mut table| { -// for row in 0u64..(1 << 8) { -// table.assign_cell( -// || format!("row {row}"), -// config.table, -// row as usize, -// || Value::known(Fp::from(row + 1)), -// )?; -// } -// -// Ok(()) -// }, -// )?; -// -// layouter.assign_region( -// || "assign values", -// |mut region| { -// for offset in 0u64..(1 << 10) { -// config.selector.enable(&mut region, offset as usize)?; -// region.assign_advice( -// || format!("offset {offset}"), -// config.advice, -// offset as usize, -// || Value::known(Fp::from((offset % 256) + 1)), -// )?; -// } -// -// Ok(()) -// }, -// ) -// } -// } -// -// const K: u32 = 11; -// -// fn main() { -// let circuit = TestCircuit {}; -// -// let model = from_circuit_to_model_circuit::<_, _, 56, 56>( -// K, -// &circuit, -// vec![], -// CommitmentScheme::KZGGWC, -// ); -// println!( -// "Cost of circuit with 8 bit lookup table: \n{}", -// serde_json::to_string_pretty(&model).unwrap() -// ); -// } +use halo2_proofs::dev::cost_model::{from_circuit_to_model_circuit, CommitmentScheme}; +use halo2_proofs::plonk::{Expression, Selector, TableColumn}; +use halo2_proofs::poly::Rotation; + +// We use a lookup example +#[derive(Clone, Copy)] +struct TestCircuit {} + +#[derive(Debug, Clone)] +struct MyConfig { + selector: Selector, + table: TableColumn, + advice: Column, +} + +impl Circuit for TestCircuit { + type Config = MyConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self {} + } + + fn configure(meta: &mut ConstraintSystem) -> MyConfig { + let config = MyConfig { + selector: meta.complex_selector(), + table: meta.lookup_table_column(), + advice: meta.advice_column(), + }; + + meta.lookup("lookup", |meta| { + let selector = meta.query_selector(config.selector); + let not_selector = Expression::Constant(Fp::ONE) - selector.clone(); + let advice = meta.query_advice(config.advice, Rotation::cur()); + vec![(selector * advice + not_selector, config.table)] + }); + + config + } + + fn synthesize(&self, config: MyConfig, mut layouter: impl Layouter) -> Result<(), Error> { + layouter.assign_table( + || "8-bit table", + |mut table| { + for row in 0u64..(1 << 8) { + table.assign_cell( + || format!("row {row}"), + config.table, + row as usize, + || Value::known(Fp::from(row + 1)), + )?; + } + + Ok(()) + }, + )?; + + layouter.assign_region( + || "assign values", + |mut region| { + for offset in 0u64..(1 << 10) { + config.selector.enable(&mut region, offset as usize)?; + region.assign_advice( + || format!("offset {offset}"), + config.advice, + offset as usize, + || Value::known(Fp::from((offset % 256) + 1)), + )?; + } + + Ok(()) + }, + ) + } +} + +const K: u32 = 11; + +fn main() { + let circuit = TestCircuit {}; + + let model = from_circuit_to_model_circuit::<_, _, 56, 56>( + K, + &circuit, + vec![], + CommitmentScheme::KZGGWC, + ); + println!( + "Cost of circuit with 8 bit lookup table: \n{}", + serde_json::to_string_pretty(&model).unwrap() + ); +} diff --git a/examples/serialization.rs b/examples/serialization.rs index 278101f0d6..d588cc5a1d 100644 --- a/examples/serialization.rs +++ b/examples/serialization.rs @@ -1,194 +1,176 @@ -fn main() {} - -// use std::{ -// fs::File, -// io::{BufReader, BufWriter, Write}, -// }; -// -// use ff::Field; -// use halo2_proofs::{ -// circuit::{Layouter, SimpleFloorPlanner, Value}, -// plonk::{ -// create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, -// ConstraintSystem, Error, Fixed, Instance, ProvingKey, -// }, -// poly::{ -// kzg::{ -// params::{KZGCommitmentScheme, ParamsKZG}, -// gwc::{ProverGWC, VerifierGWC}, -// strategy::SingleStrategy, -// }, -// Rotation, -// }, -// transcript::{ -// Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, -// }, -// SerdeFormat, -// }; -// use halo2curves::bn256::{Bn256, Fr, G1Affine}; -// use rand_core::OsRng; -// -// #[derive(Clone, Copy)] -// struct StandardPlonkConfig { -// a: Column, -// b: Column, -// c: Column, -// q_a: Column, -// q_b: Column, -// q_c: Column, -// q_ab: Column, -// constant: Column, -// #[allow(dead_code)] -// instance: Column, -// } -// -// impl StandardPlonkConfig { -// fn configure(meta: &mut ConstraintSystem) -> Self { -// let [a, b, c] = [(); 3].map(|_| meta.advice_column()); -// let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); -// let instance = meta.instance_column(); -// -// [a, b, c].map(|column| meta.enable_equality(column)); -// -// meta.create_gate( -// "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", -// |meta| { -// 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())); -// let instance = meta.query_instance(instance, Rotation::cur()); -// Some( -// q_a * a.clone() -// + q_b * b.clone() -// + q_c * c -// + q_ab * a * b -// + constant -// + instance, -// ) -// }, -// ); -// -// StandardPlonkConfig { -// a, -// b, -// c, -// q_a, -// q_b, -// q_c, -// q_ab, -// constant, -// instance, -// } -// } -// } -// -// #[derive(Clone, Default)] -// struct StandardPlonk(Fr); -// -// impl Circuit for StandardPlonk { -// type Config = StandardPlonkConfig; -// type FloorPlanner = SimpleFloorPlanner; -// #[cfg(feature = "circuit-params")] -// type Params = (); -// -// 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| { -// region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; -// region.assign_fixed(|| "", config.q_a, 0, || Value::known(-Fr::one()))?; -// -// region.assign_advice(|| "", config.a, 1, || Value::known(-Fr::from(5u64)))?; -// for (idx, column) in (1..).zip([ -// config.q_a, -// config.q_b, -// config.q_c, -// config.q_ab, -// config.constant, -// ]) { -// region.assign_fixed(|| "", column, 1, || Value::known(Fr::from(idx as u64)))?; -// } -// -// let a = region.assign_advice(|| "", config.a, 2, || Value::known(Fr::one()))?; -// a.copy_advice(|| "", &mut region, config.b, 3)?; -// a.copy_advice(|| "", &mut region, config.c, 4)?; -// Ok(()) -// }, -// ) -// } -// } -// -// fn main() { -// let k = 4; -// let circuit = StandardPlonk(Fr::random(OsRng)); -// let params = ParamsKZG::::setup(k, OsRng); -// let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail"); -// let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail"); -// -// let f = File::create("serialization-test.pk").unwrap(); -// let mut writer = BufWriter::new(f); -// pk.write(&mut writer, SerdeFormat::RawBytes).unwrap(); -// writer.flush().unwrap(); -// -// let f = File::open("serialization-test.pk").unwrap(); -// let mut reader = BufReader::new(f); -// #[allow(clippy::unit_arg)] -// let pk = ProvingKey::::read::<_, StandardPlonk>( -// &mut reader, -// SerdeFormat::RawBytes, -// #[cfg(feature = "circuit-params")] -// circuit.params(), -// ) -// .unwrap(); -// -// std::fs::remove_file("serialization-test.pk").unwrap(); -// -// let instances: &[&[Fr]] = &[&[circuit.0]]; -// let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); -// create_proof::< -// KZGCommitmentScheme, -// ProverGWC<'_, Bn256>, -// Challenge255, -// _, -// Blake2bWrite, G1Affine, Challenge255<_>>, -// _, -// >( -// ¶ms, -// &pk, -// &[circuit], -// &[instances], -// OsRng, -// &mut transcript, -// ) -// .expect("prover should not fail"); -// let proof = transcript.finalize(); -// -// let strategy = SingleStrategy::new(¶ms); -// let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); -// assert!(verify_proof::< -// KZGCommitmentScheme, -// VerifierGWC<'_, Bn256>, -// Challenge255, -// Blake2bRead<&[u8], G1Affine, Challenge255>, -// SingleStrategy<'_, Bn256>, -// >( -// ¶ms, -// pk.get_vk(), -// strategy, -// &[instances], -// &mut transcript -// ) -// .is_ok()); -// } +use blake2b_simd::State; +use std::{ + fs::File, + io::{BufReader, BufWriter, Write}, +}; + +use ff::Field; +use halo2_proofs::transcript::{CircuitTranscript, Transcript}; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{ + create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, + ConstraintSystem, Error, Fixed, Instance, ProvingKey, + }, + poly::{ + kzg::{params::ParamsKZG, KZGCommitmentScheme}, + Rotation, + }, + SerdeFormat, +}; +use halo2curves::bn256::{Bn256, Fr}; +use rand_core::OsRng; + +#[derive(Clone, Copy)] +struct StandardPlonkConfig { + a: Column, + b: Column, + c: Column, + q_a: Column, + q_b: Column, + q_c: Column, + q_ab: Column, + constant: Column, + #[allow(dead_code)] + instance: Column, +} + +impl StandardPlonkConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let [a, b, c] = [(); 3].map(|_| meta.advice_column()); + let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); + let instance = meta.instance_column(); + + [a, b, c].map(|column| meta.enable_equality(column)); + + meta.create_gate( + "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", + |meta| { + 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())); + let instance = meta.query_instance(instance, Rotation::cur()); + Some( + q_a * a.clone() + + q_b * b.clone() + + q_c * c + + q_ab * a * b + + constant + + instance, + ) + }, + ); + + StandardPlonkConfig { + a, + b, + c, + q_a, + q_b, + q_c, + q_ab, + constant, + instance, + } + } +} + +#[derive(Clone, Default)] +struct StandardPlonk(Fr); + +impl Circuit for StandardPlonk { + type Config = StandardPlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + 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| { + region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; + region.assign_fixed(|| "", config.q_a, 0, || Value::known(-Fr::one()))?; + + region.assign_advice(|| "", config.a, 1, || Value::known(-Fr::from(5u64)))?; + for (idx, column) in (1..).zip([ + config.q_a, + config.q_b, + config.q_c, + config.q_ab, + config.constant, + ]) { + region.assign_fixed(|| "", column, 1, || Value::known(Fr::from(idx as u64)))?; + } + + let a = region.assign_advice(|| "", config.a, 2, || Value::known(Fr::one()))?; + a.copy_advice(|| "", &mut region, config.b, 3)?; + a.copy_advice(|| "", &mut region, config.c, 4)?; + Ok(()) + }, + ) + } +} + +fn main() { + let k = 4; + let circuit = StandardPlonk(Fr::random(OsRng)); + let params = ParamsKZG::::setup(k, OsRng); + let vk = keygen_vk::<_, KZGCommitmentScheme, _>(¶ms, &circuit) + .expect("vk should not fail"); + let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail"); + + let f = File::create("serialization-test.pk").unwrap(); + let mut writer = BufWriter::new(f); + pk.write(&mut writer, SerdeFormat::RawBytes).unwrap(); + writer.flush().unwrap(); + + let f = File::open("serialization-test.pk").unwrap(); + let mut reader = BufReader::new(f); + #[allow(clippy::unit_arg)] + let pk = ProvingKey::>::read::<_, StandardPlonk>( + &mut reader, + SerdeFormat::RawBytes, + #[cfg(feature = "circuit-params")] + circuit.params(), + ) + .unwrap(); + + std::fs::remove_file("serialization-test.pk").unwrap(); + + let instances: &[&[Fr]] = &[&[circuit.0]]; + let mut transcript = CircuitTranscript::::init(); + + create_proof::, _, _>( + ¶ms, + &pk, + &[circuit], + &[instances], + OsRng, + &mut transcript, + ) + .expect("proof generation should not fail"); + + let proof = transcript.finalize(); + + let mut transcript = CircuitTranscript::::parse(&proof[..]); + + assert!(verify_proof::, _>( + ¶ms, + pk.get_vk(), + &[instances], + &mut transcript, + ) + .is_ok()); +} diff --git a/examples/shuffle.rs b/examples/shuffle.rs index b5e3502c97..8742ccc267 100644 --- a/examples/shuffle.rs +++ b/examples/shuffle.rs @@ -1,360 +1,345 @@ -fn main() {} - -// use ff::{BatchInvert, FromUniformBytes}; -// use halo2_proofs::arithmetic::CurveExt; -// use halo2_proofs::helpers::SerdeCurveAffine; -// use halo2_proofs::poly::kzg::params::{KZGCommitmentScheme, ParamsKZG}; -// use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; -// use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; -// use halo2_proofs::{ -// arithmetic::Field, -// circuit::{floor_planner::V1, Layouter, Value}, -// dev::{metadata, FailureLocation, MockProver, VerifyFailure}, -// plonk::*, -// poly::{commitment::ParamsProver, VerificationStrategy}, -// transcript::{ -// Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, -// }, -// }; -// use halo2curves::pairing::MultiMillerLoop; -// use halo2curves::{bn256, pairing::Engine}; -// use rand_core::{OsRng, RngCore}; -// use std::iter; -// -// fn rand_2d_array(rng: &mut R) -> [[F; H]; W] { -// [(); W].map(|_| [(); H].map(|_| F::random(&mut *rng))) -// } -// -// fn shuffled( -// original: [[F; H]; W], -// rng: &mut R, -// ) -> [[F; H]; W] { -// let mut shuffled = original; -// -// for row in (1..H).rev() { -// let rand_row = (rng.next_u32() as usize) % row; -// for column in shuffled.iter_mut() { -// column.swap(row, rand_row); -// } -// } -// -// shuffled -// } -// -// #[derive(Clone)] -// struct MyConfig { -// q_shuffle: Selector, -// q_first: Selector, -// q_last: Selector, -// original: [Column; W], -// shuffled: [Column; W], -// theta: Challenge, -// gamma: Challenge, -// z: Column, -// } -// -// impl MyConfig { -// fn configure(meta: &mut ConstraintSystem) -> Self { -// let [q_shuffle, q_first, q_last] = [(); 3].map(|_| meta.selector()); -// // First phase -// let original = [(); W].map(|_| meta.advice_column_in(FirstPhase)); -// let shuffled = [(); W].map(|_| meta.advice_column_in(FirstPhase)); -// let [theta, gamma] = [(); 2].map(|_| meta.challenge_usable_after(FirstPhase)); -// // Second phase -// let z = meta.advice_column_in(SecondPhase); -// -// meta.create_gate("z should start with 1", |_| { -// let one = Expression::Constant(F::ONE); -// -// vec![q_first.expr() * (one - z.cur())] -// }); -// -// meta.create_gate("z should end with 1", |_| { -// let one = Expression::Constant(F::ONE); -// -// vec![q_last.expr() * (one - z.cur())] -// }); -// -// meta.create_gate("z should have valid transition", |_| { -// let q_shuffle = q_shuffle.expr(); -// let original = original.map(|advice| advice.cur()); -// let shuffled = shuffled.map(|advice| advice.cur()); -// let [theta, gamma] = [theta, gamma].map(|challenge| challenge.expr()); -// -// // Compress -// let original = original -// .iter() -// .cloned() -// .reduce(|acc, a| acc * theta.clone() + a) -// .unwrap(); -// let shuffled = shuffled -// .iter() -// .cloned() -// .reduce(|acc, a| acc * theta.clone() + a) -// .unwrap(); -// -// vec![q_shuffle * (z.cur() * (original + gamma.clone()) - z.next() * (shuffled + gamma))] -// }); -// -// Self { -// q_shuffle, -// q_first, -// q_last, -// original, -// shuffled, -// theta, -// gamma, -// z, -// } -// } -// } -// -// #[derive(Clone, Default)] -// struct MyCircuit { -// original: Value<[[F; H]; W]>, -// shuffled: Value<[[F; H]; W]>, -// } -// -// impl MyCircuit { -// fn rand(rng: &mut R) -> Self { -// let original = rand_2d_array::(rng); -// let shuffled = shuffled(original, rng); -// -// Self { -// original: Value::known(original), -// shuffled: Value::known(shuffled), -// } -// } -// } -// -// impl Circuit for MyCircuit { -// type Config = MyConfig; -// type FloorPlanner = V1; -// #[cfg(feature = "circuit-params")] -// type Params = (); -// -// fn without_witnesses(&self) -> Self { -// Self::default() -// } -// -// fn configure(meta: &mut ConstraintSystem) -> Self::Config { -// MyConfig::configure(meta) -// } -// -// fn synthesize( -// &self, -// config: Self::Config, -// mut layouter: impl Layouter, -// ) -> Result<(), Error> { -// let theta = layouter.get_challenge(config.theta); -// let gamma = layouter.get_challenge(config.gamma); -// -// layouter.assign_region( -// || "Shuffle original into shuffled", -// |mut region| { -// // Keygen -// config.q_first.enable(&mut region, 0)?; -// config.q_last.enable(&mut region, H)?; -// for offset in 0..H { -// config.q_shuffle.enable(&mut region, offset)?; -// } -// -// // First phase -// for (idx, (&column, values)) in config -// .original -// .iter() -// .zip(self.original.transpose_array().iter()) -// .enumerate() -// { -// for (offset, &value) in values.transpose_array().iter().enumerate() { -// region.assign_advice( -// || format!("original[{idx}][{offset}]"), -// column, -// offset, -// || value, -// )?; -// } -// } -// for (idx, (&column, values)) in config -// .shuffled -// .iter() -// .zip(self.shuffled.transpose_array().iter()) -// .enumerate() -// { -// for (offset, &value) in values.transpose_array().iter().enumerate() { -// region.assign_advice( -// || format!("shuffled[{idx}][{offset}]"), -// column, -// offset, -// || value, -// )?; -// } -// } -// -// // Second phase -// let z = self.original.zip(self.shuffled).zip(theta).zip(gamma).map( -// |(((original, shuffled), theta), gamma)| { -// let mut product = vec![F::ZERO; H]; -// for (idx, product) in product.iter_mut().enumerate() { -// let mut compressed = F::ZERO; -// for value in shuffled.iter() { -// compressed *= theta; -// compressed += value[idx]; -// } -// -// *product = compressed + gamma -// } -// -// product.iter_mut().batch_invert(); -// -// for (idx, product) in product.iter_mut().enumerate() { -// let mut compressed = F::ZERO; -// for value in original.iter() { -// compressed *= theta; -// compressed += value[idx]; -// } -// -// *product *= compressed + gamma -// } -// -// #[allow(clippy::let_and_return)] -// let z = iter::once(F::ONE) -// .chain(product) -// .scan(F::ONE, |state, cur| { -// *state *= &cur; -// Some(*state) -// }) -// .collect::>(); -// -// #[cfg(feature = "sanity-checks")] -// assert_eq!(F::ONE, *z.last().unwrap()); -// -// z -// }, -// ); -// for (offset, value) in z.transpose_vec(H + 1).into_iter().enumerate() { -// region.assign_advice(|| format!("z[{offset}]"), config.z, offset, || value)?; -// } -// -// Ok(()) -// }, -// ) -// } -// } -// -// fn test_mock_prover, const W: usize, const H: usize>( -// k: u32, -// circuit: MyCircuit, -// expected: Result<(), Vec<(metadata::Constraint, FailureLocation)>>, -// ) { -// let prover = MockProver::run(k, &circuit, vec![]).unwrap(); -// match (prover.verify(), expected) { -// (Ok(_), Ok(_)) => {} -// (Err(err), Err(expected)) => { -// assert_eq!( -// err.into_iter() -// .map(|failure| match failure { -// VerifyFailure::ConstraintNotSatisfied { -// constraint, -// location, -// .. -// } => (constraint, location), -// _ => panic!("MockProver::verify has result unmatching expected"), -// }) -// .collect::>(), -// expected -// ) -// } -// (_, _) => panic!("MockProver::verify has result unmatching expected"), -// }; -// } -// -// fn test_prover( -// k: u32, -// circuit: MyCircuit, -// expected: bool, -// ) where -// E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, -// E::G1: CurveExt, -// E::G2Affine: SerdeCurveAffine, -// E::Fr: FromUniformBytes<64> + WithSmallOrderMulGroup<3>, -// { -// let params = ParamsKZG::::new(k); -// let vk = keygen_vk(¶ms, &circuit).unwrap(); -// let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); -// -// let proof = { -// let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); -// -// create_proof::, ProverGWC, _, _, _, _>( -// ¶ms, -// &pk, -// &[circuit], -// &[&[]], -// OsRng, -// &mut transcript, -// ) -// .expect("proof generation should not fail"); -// -// transcript.finalize() -// }; -// -// let accepted = { -// let strategy = AccumulatorStrategy::new(¶ms); -// let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); -// -// verify_proof::, VerifierGWC, _, _, _>( -// ¶ms, -// pk.get_vk(), -// strategy, -// &[&[]], -// &mut transcript, -// ) -// .map(|strategy| { -// as VerificationStrategy< -// KZGCommitmentScheme, -// VerifierGWC, -// >>::finalize(strategy) -// }) -// .unwrap_or_default() -// }; -// -// assert_eq!(accepted, expected); -// } -// -// fn main() { -// const W: usize = 4; -// const H: usize = 32; -// const K: u32 = 8; -// -// let circuit = &MyCircuit::<_, W, H>::rand(&mut OsRng); -// -// { -// test_mock_prover(K, circuit.clone(), Ok(())); -// test_prover::(K, circuit.clone(), true); -// } -// -// #[cfg(not(feature = "sanity-checks"))] -// { -// use std::ops::IndexMut; -// -// let mut circuit = circuit.clone(); -// circuit.shuffled = circuit.shuffled.map(|mut shuffled| { -// shuffled.index_mut(0).swap(0, 1); -// shuffled -// }); -// -// test_mock_prover( -// K, -// circuit.clone(), -// Err(vec![( -// ((1, "z should end with 1").into(), 0, "").into(), -// FailureLocation::InRegion { -// region: (0, "Shuffle original into shuffled").into(), -// offset: 32, -// }, -// )]), -// ); -// test_prover::(K, circuit, false); -// } -// } +use blake2b_simd::State; +use ff::{BatchInvert, FromUniformBytes, WithSmallOrderMulGroup}; +use halo2_proofs::poly::kzg::{params::ParamsKZG, KZGCommitmentScheme}; +use halo2_proofs::transcript::{CircuitTranscript, Hashable, Sampleable, Transcript}; +use halo2_proofs::{ + arithmetic::Field, + circuit::{floor_planner::V1, Layouter, Value}, + dev::{metadata, FailureLocation, MockProver, VerifyFailure}, + plonk::*, +}; +use halo2curves::pairing::MultiMillerLoop; +use halo2curves::serde::SerdeObject; +use halo2curves::{bn256, pairing::Engine}; +use rand_core::{OsRng, RngCore}; +use std::iter; + +fn rand_2d_array(rng: &mut R) -> [[F; H]; W] { + [(); W].map(|_| [(); H].map(|_| F::random(&mut *rng))) +} + +fn shuffled( + original: [[F; H]; W], + rng: &mut R, +) -> [[F; H]; W] { + let mut shuffled = original; + + for row in (1..H).rev() { + let rand_row = (rng.next_u32() as usize) % row; + for column in shuffled.iter_mut() { + column.swap(row, rand_row); + } + } + + shuffled +} + +#[derive(Clone)] +struct MyConfig { + q_shuffle: Selector, + q_first: Selector, + q_last: Selector, + original: [Column; W], + shuffled: [Column; W], + theta: Challenge, + gamma: Challenge, + z: Column, +} + +impl MyConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let [q_shuffle, q_first, q_last] = [(); 3].map(|_| meta.selector()); + // First phase + let original = [(); W].map(|_| meta.advice_column_in(FirstPhase)); + let shuffled = [(); W].map(|_| meta.advice_column_in(FirstPhase)); + let [theta, gamma] = [(); 2].map(|_| meta.challenge_usable_after(FirstPhase)); + // Second phase + let z = meta.advice_column_in(SecondPhase); + + meta.create_gate("z should start with 1", |_| { + let one = Expression::Constant(F::ONE); + + vec![q_first.expr() * (one - z.cur())] + }); + + meta.create_gate("z should end with 1", |_| { + let one = Expression::Constant(F::ONE); + + vec![q_last.expr() * (one - z.cur())] + }); + + meta.create_gate("z should have valid transition", |_| { + let q_shuffle = q_shuffle.expr(); + let original = original.map(|advice| advice.cur()); + let shuffled = shuffled.map(|advice| advice.cur()); + let [theta, gamma] = [theta, gamma].map(|challenge| challenge.expr()); + + // Compress + let original = original + .iter() + .cloned() + .reduce(|acc, a| acc * theta.clone() + a) + .unwrap(); + let shuffled = shuffled + .iter() + .cloned() + .reduce(|acc, a| acc * theta.clone() + a) + .unwrap(); + + vec![q_shuffle * (z.cur() * (original + gamma.clone()) - z.next() * (shuffled + gamma))] + }); + + Self { + q_shuffle, + q_first, + q_last, + original, + shuffled, + theta, + gamma, + z, + } + } +} + +#[derive(Clone, Default)] +struct MyCircuit { + original: Value<[[F; H]; W]>, + shuffled: Value<[[F; H]; W]>, +} + +impl MyCircuit { + fn rand(rng: &mut R) -> Self { + let original = rand_2d_array::(rng); + let shuffled = shuffled(original, rng); + + Self { + original: Value::known(original), + shuffled: Value::known(shuffled), + } + } +} + +impl Circuit for MyCircuit { + type Config = MyConfig; + type FloorPlanner = V1; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + MyConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let theta = layouter.get_challenge(config.theta); + let gamma = layouter.get_challenge(config.gamma); + + layouter.assign_region( + || "Shuffle original into shuffled", + |mut region| { + // Keygen + config.q_first.enable(&mut region, 0)?; + config.q_last.enable(&mut region, H)?; + for offset in 0..H { + config.q_shuffle.enable(&mut region, offset)?; + } + + // First phase + for (idx, (&column, values)) in config + .original + .iter() + .zip(self.original.transpose_array().iter()) + .enumerate() + { + for (offset, &value) in values.transpose_array().iter().enumerate() { + region.assign_advice( + || format!("original[{idx}][{offset}]"), + column, + offset, + || value, + )?; + } + } + for (idx, (&column, values)) in config + .shuffled + .iter() + .zip(self.shuffled.transpose_array().iter()) + .enumerate() + { + for (offset, &value) in values.transpose_array().iter().enumerate() { + region.assign_advice( + || format!("shuffled[{idx}][{offset}]"), + column, + offset, + || value, + )?; + } + } + + // Second phase + let z = self.original.zip(self.shuffled).zip(theta).zip(gamma).map( + |(((original, shuffled), theta), gamma)| { + let mut product = vec![F::ZERO; H]; + for (idx, product) in product.iter_mut().enumerate() { + let mut compressed = F::ZERO; + for value in shuffled.iter() { + compressed *= theta; + compressed += value[idx]; + } + + *product = compressed + gamma + } + + product.iter_mut().batch_invert(); + + for (idx, product) in product.iter_mut().enumerate() { + let mut compressed = F::ZERO; + for value in original.iter() { + compressed *= theta; + compressed += value[idx]; + } + + *product *= compressed + gamma + } + + #[allow(clippy::let_and_return)] + let z = iter::once(F::ONE) + .chain(product) + .scan(F::ONE, |state, cur| { + *state *= &cur; + Some(*state) + }) + .collect::>(); + + #[cfg(feature = "sanity-checks")] + assert_eq!(F::ONE, *z.last().unwrap()); + + z + }, + ); + for (offset, value) in z.transpose_vec(H + 1).into_iter().enumerate() { + region.assign_advice(|| format!("z[{offset}]"), config.z, offset, || value)?; + } + + Ok(()) + }, + ) + } +} + +fn test_mock_prover, const W: usize, const H: usize>( + k: u32, + circuit: MyCircuit, + expected: Result<(), Vec<(metadata::Constraint, FailureLocation)>>, +) { + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + match (prover.verify(), expected) { + (Ok(_), Ok(_)) => {} + (Err(err), Err(expected)) => { + assert_eq!( + err.into_iter() + .map(|failure| match failure { + VerifyFailure::ConstraintNotSatisfied { + constraint, + location, + .. + } => (constraint, location), + _ => panic!("MockProver::verify has result unmatching expected"), + }) + .collect::>(), + expected + ) + } + (_, _) => panic!("MockProver::verify has result unmatching expected"), + }; +} + +fn test_prover( + k: u32, + circuit: MyCircuit, + expected: bool, +) where + E::G1Affine: Default + SerdeObject + Hashable, + E::Fr: WithSmallOrderMulGroup<3> + + FromUniformBytes<64> + + SerdeObject + + Sampleable + + Hashable + + Ord, +{ + let params = ParamsKZG::::new(k); + let vk = keygen_vk(¶ms, &circuit).unwrap(); + let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); + + let proof = { + let mut transcript = CircuitTranscript::::init(); + + create_proof::, _, _>( + ¶ms, + &pk, + &[circuit], + &[&[]], + OsRng, + &mut transcript, + ) + .expect("proof generation should not fail"); + + transcript.finalize() + }; + + let mut transcript = CircuitTranscript::::parse(&proof[..]); + + let verifier = verify_proof::, _>( + ¶ms, + pk.get_vk(), + &[&[]], + &mut transcript, + ); + + assert_eq!(verifier.is_ok(), expected); +} + +fn main() { + const W: usize = 4; + const H: usize = 32; + const K: u32 = 8; + + let circuit = &MyCircuit::<_, W, H>::rand(&mut OsRng); + + { + test_mock_prover(K, circuit.clone(), Ok(())); + test_prover::(K, circuit.clone(), true); + } + + #[cfg(not(feature = "sanity-checks"))] + { + use std::ops::IndexMut; + + let mut circuit = circuit.clone(); + circuit.shuffled = circuit.shuffled.map(|mut shuffled| { + shuffled.index_mut(0).swap(0, 1); + shuffled + }); + + test_mock_prover( + K, + circuit.clone(), + Err(vec![( + ((1, "z should end with 1").into(), 0, "").into(), + FailureLocation::InRegion { + region: (0, "Shuffle original into shuffled").into(), + offset: 32, + }, + )]), + ); + test_prover::(K, circuit, false); + } +} diff --git a/examples/simple-example.rs b/examples/simple-example.rs index a4ca7c3757..242257a692 100644 --- a/examples/simple-example.rs +++ b/examples/simple-example.rs @@ -1,344 +1,342 @@ -fn main() {} - -// use std::marker::PhantomData; -// -// use halo2_proofs::{ -// arithmetic::Field, -// circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, -// plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector}, -// poly::Rotation, -// }; -// -// // ANCHOR: instructions -// trait NumericInstructions: Chip { -// /// Variable representing a number. -// type Num; -// -// /// Loads a number into the circuit as a private input. -// fn load_private(&self, layouter: impl Layouter, a: Value) -> Result; -// -// /// Loads a number into the circuit as a fixed constant. -// fn load_constant(&self, layouter: impl Layouter, constant: F) -> Result; -// -// /// Returns `c = a * b`. -// fn mul( -// &self, -// layouter: impl Layouter, -// a: Self::Num, -// b: Self::Num, -// ) -> Result; -// -// /// Exposes a number as a public input to the circuit. -// fn expose_public( -// &self, -// layouter: impl Layouter, -// num: Self::Num, -// row: usize, -// ) -> Result<(), Error>; -// } -// // ANCHOR_END: instructions -// -// // ANCHOR: chip -// /// The chip that will implement our instructions! Chips store their own -// /// config, as well as type markers if necessary. -// struct FieldChip { -// config: FieldConfig, -// _marker: PhantomData, -// } -// // ANCHOR_END: chip -// -// // ANCHOR: chip-config -// /// Chip state is stored in a config struct. This is generated by the chip -// /// during configuration, and then stored inside the chip. -// #[derive(Clone, Debug)] -// struct FieldConfig { -// /// For this chip, we will use two advice columns to implement our instructions. -// /// These are also the columns through which we communicate with other parts of -// /// the circuit. -// advice: [Column; 2], -// -// /// This is the public input (instance) column. -// instance: Column, -// -// // We need a selector to enable the multiplication gate, so that we aren't placing -// // any constraints on cells where `NumericInstructions::mul` is not being used. -// // This is important when building larger circuits, where columns are used by -// // multiple sets of instructions. -// s_mul: Selector, -// } -// -// impl FieldChip { -// fn construct(config: >::Config) -> Self { -// Self { -// config, -// _marker: PhantomData, -// } -// } -// -// fn configure( -// meta: &mut ConstraintSystem, -// advice: [Column; 2], -// instance: Column, -// constant: Column, -// ) -> >::Config { -// meta.enable_equality(instance); -// meta.enable_constant(constant); -// for column in &advice { -// meta.enable_equality(*column); -// } -// let s_mul = meta.selector(); -// -// // Define our multiplication gate! -// meta.create_gate("mul", |meta| { -// // To implement multiplication, we need three advice cells and a selector -// // cell. We arrange them like so: -// // -// // | a0 | a1 | s_mul | -// // |-----|-----|-------| -// // | lhs | rhs | s_mul | -// // | out | | | -// // -// // Gates may refer to any relative offsets we want, but each distinct -// // offset adds a cost to the proof. The most common offsets are 0 (the -// // current row), 1 (the next row), and -1 (the previous row), for which -// // `Rotation` has specific constructors. -// let lhs = meta.query_advice(advice[0], Rotation::cur()); -// let rhs = meta.query_advice(advice[1], Rotation::cur()); -// let out = meta.query_advice(advice[0], Rotation::next()); -// let s_mul = meta.query_selector(s_mul); -// -// // Finally, we return the polynomial expressions that constrain this gate. -// // For our multiplication gate, we only need a single polynomial constraint. -// // -// // The polynomial expressions returned from `create_gate` will be -// // constrained by the proving system to equal zero. Our expression -// // has the following properties: -// // - When s_mul = 0, any value is allowed in lhs, rhs, and out. -// // - When s_mul != 0, this constrains lhs * rhs = out. -// vec![s_mul * (lhs * rhs - out)] -// }); -// -// FieldConfig { -// advice, -// instance, -// s_mul, -// } -// } -// } -// // ANCHOR_END: chip-config -// -// // ANCHOR: chip-impl -// impl Chip for FieldChip { -// type Config = FieldConfig; -// type Loaded = (); -// -// fn config(&self) -> &Self::Config { -// &self.config -// } -// -// fn loaded(&self) -> &Self::Loaded { -// &() -// } -// } -// // ANCHOR_END: chip-impl -// -// // ANCHOR: instructions-impl -// /// A variable representing a number. -// #[derive(Clone)] -// struct Number(AssignedCell); -// -// impl NumericInstructions for FieldChip { -// type Num = Number; -// -// fn load_private( -// &self, -// mut layouter: impl Layouter, -// value: Value, -// ) -> Result { -// let config = self.config(); -// -// layouter.assign_region( -// || "load private", -// |mut region| { -// region -// .assign_advice(|| "private input", config.advice[0], 0, || value) -// .map(Number) -// }, -// ) -// } -// -// fn load_constant( -// &self, -// mut layouter: impl Layouter, -// constant: F, -// ) -> Result { -// let config = self.config(); -// -// layouter.assign_region( -// || "load constant", -// |mut region| { -// region -// .assign_advice_from_constant(|| "constant value", config.advice[0], 0, constant) -// .map(Number) -// }, -// ) -// } -// -// fn mul( -// &self, -// mut layouter: impl Layouter, -// a: Self::Num, -// b: Self::Num, -// ) -> Result { -// let config = self.config(); -// -// layouter.assign_region( -// || "mul", -// |mut region: Region<'_, F>| { -// // We only want to use a single multiplication gate in this region, -// // so we enable it at region offset 0; this means it will constrain -// // cells at offsets 0 and 1. -// config.s_mul.enable(&mut region, 0)?; -// -// // The inputs we've been given could be located anywhere in the circuit, -// // but we can only rely on relative offsets inside this region. So we -// // assign new cells inside the region and constrain them to have the -// // same values as the inputs. -// a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; -// b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; -// -// // Now we can assign the multiplication result, which is to be assigned -// // into the output position. -// let value = a.0.value().copied() * b.0.value(); -// -// // Finally, we do the assignment to the output, returning a -// // variable to be used in another part of the circuit. -// region -// .assign_advice(|| "lhs * rhs", config.advice[0], 1, || value) -// .map(Number) -// }, -// ) -// } -// -// fn expose_public( -// &self, -// mut layouter: impl Layouter, -// num: Self::Num, -// row: usize, -// ) -> Result<(), Error> { -// let config = self.config(); -// -// layouter.constrain_instance(num.0.cell(), config.instance, row) -// } -// } -// // ANCHOR_END: instructions-impl -// -// // ANCHOR: circuit -// /// The full circuit implementation. -// /// -// /// In this struct we store the private input variables. We use `Option` because -// /// they won't have any value during key generation. During proving, if any of these -// /// were `None` we would get an error. -// #[derive(Default)] -// struct MyCircuit { -// constant: F, -// a: Value, -// b: Value, -// } -// -// impl Circuit for MyCircuit { -// // Since we are using a single chip for everything, we can just reuse its config. -// type Config = FieldConfig; -// type FloorPlanner = SimpleFloorPlanner; -// #[cfg(feature = "circuit-params")] -// type Params = (); -// -// fn without_witnesses(&self) -> Self { -// Self::default() -// } -// -// fn configure(meta: &mut ConstraintSystem) -> Self::Config { -// // We create the two advice columns that FieldChip uses for I/O. -// let advice = [meta.advice_column(), meta.advice_column()]; -// -// // We also need an instance column to store public inputs. -// let instance = meta.instance_column(); -// -// // Create a fixed column to load constants. -// let constant = meta.fixed_column(); -// -// FieldChip::configure(meta, advice, instance, constant) -// } -// -// fn synthesize( -// &self, -// config: Self::Config, -// mut layouter: impl Layouter, -// ) -> Result<(), Error> { -// let field_chip = FieldChip::::construct(config); -// -// // Load our private values into the circuit. -// let a = field_chip.load_private(layouter.namespace(|| "load a"), self.a)?; -// let b = field_chip.load_private(layouter.namespace(|| "load b"), self.b)?; -// -// // Load the constant factor into the circuit. -// let constant = -// field_chip.load_constant(layouter.namespace(|| "load constant"), self.constant)?; -// -// // We only have access to plain multiplication. -// // We could implement our circuit as: -// // asq = a*a -// // bsq = b*b -// // absq = asq*bsq -// // c = constant*asq*bsq -// // -// // but it's more efficient to implement it as: -// // ab = a*b -// // absq = ab^2 -// // c = constant*absq -// let ab = field_chip.mul(layouter.namespace(|| "a * b"), a, b)?; -// let absq = field_chip.mul(layouter.namespace(|| "ab * ab"), ab.clone(), ab)?; -// let c = field_chip.mul(layouter.namespace(|| "constant * absq"), constant, absq)?; -// -// // Expose the result as a public input to the circuit. -// field_chip.expose_public(layouter.namespace(|| "expose c"), c, 0) -// } -// } -// // ANCHOR_END: circuit -// -// fn main() { -// use halo2_proofs::dev::MockProver; -// use halo2curves::pasta::Fp; -// -// // ANCHOR: test-circuit -// // The number of rows in our circuit cannot exceed 2^k. Since our example -// // circuit is very small, we can pick a very small value here. -// let k = 4; -// -// // Prepare the private and public inputs to the circuit! -// let constant = Fp::from(7); -// let a = Fp::from(2); -// let b = Fp::from(3); -// let c = constant * a.square() * b.square(); -// -// // Instantiate the circuit with the private inputs. -// let circuit = MyCircuit { -// constant, -// a: Value::known(a), -// b: Value::known(b), -// }; -// -// // Arrange the public input. We expose the multiplication result in row 0 -// // of the instance column, so we position it there in our public inputs. -// let mut public_inputs = vec![c]; -// -// // Given the correct public input, our circuit will verify. -// let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); -// assert_eq!(prover.verify(), Ok(())); -// -// // If we try some other public input, the proof will fail! -// public_inputs[0] += Fp::one(); -// let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); -// assert!(prover.verify().is_err()); -// // ANCHOR_END: test-circuit -// } +use std::marker::PhantomData; + +use halo2_proofs::{ + arithmetic::Field, + circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector}, + poly::Rotation, +}; + +// ANCHOR: instructions +trait NumericInstructions: Chip { + /// Variable representing a number. + type Num; + + /// Loads a number into the circuit as a private input. + fn load_private(&self, layouter: impl Layouter, a: Value) -> Result; + + /// Loads a number into the circuit as a fixed constant. + fn load_constant(&self, layouter: impl Layouter, constant: F) -> Result; + + /// Returns `c = a * b`. + fn mul( + &self, + layouter: impl Layouter, + a: Self::Num, + b: Self::Num, + ) -> Result; + + /// Exposes a number as a public input to the circuit. + fn expose_public( + &self, + layouter: impl Layouter, + num: Self::Num, + row: usize, + ) -> Result<(), Error>; +} +// ANCHOR_END: instructions + +// ANCHOR: chip +/// The chip that will implement our instructions! Chips store their own +/// config, as well as type markers if necessary. +struct FieldChip { + config: FieldConfig, + _marker: PhantomData, +} +// ANCHOR_END: chip + +// ANCHOR: chip-config +/// Chip state is stored in a config struct. This is generated by the chip +/// during configuration, and then stored inside the chip. +#[derive(Clone, Debug)] +struct FieldConfig { + /// For this chip, we will use two advice columns to implement our instructions. + /// These are also the columns through which we communicate with other parts of + /// the circuit. + advice: [Column; 2], + + /// This is the public input (instance) column. + instance: Column, + + // We need a selector to enable the multiplication gate, so that we aren't placing + // any constraints on cells where `NumericInstructions::mul` is not being used. + // This is important when building larger circuits, where columns are used by + // multiple sets of instructions. + s_mul: Selector, +} + +impl FieldChip { + fn construct(config: >::Config) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + fn configure( + meta: &mut ConstraintSystem, + advice: [Column; 2], + instance: Column, + constant: Column, + ) -> >::Config { + meta.enable_equality(instance); + meta.enable_constant(constant); + for column in &advice { + meta.enable_equality(*column); + } + let s_mul = meta.selector(); + + // Define our multiplication gate! + meta.create_gate("mul", |meta| { + // To implement multiplication, we need three advice cells and a selector + // cell. We arrange them like so: + // + // | a0 | a1 | s_mul | + // |-----|-----|-------| + // | lhs | rhs | s_mul | + // | out | | | + // + // Gates may refer to any relative offsets we want, but each distinct + // offset adds a cost to the proof. The most common offsets are 0 (the + // current row), 1 (the next row), and -1 (the previous row), for which + // `Rotation` has specific constructors. + let lhs = meta.query_advice(advice[0], Rotation::cur()); + let rhs = meta.query_advice(advice[1], Rotation::cur()); + let out = meta.query_advice(advice[0], Rotation::next()); + let s_mul = meta.query_selector(s_mul); + + // Finally, we return the polynomial expressions that constrain this gate. + // For our multiplication gate, we only need a single polynomial constraint. + // + // The polynomial expressions returned from `create_gate` will be + // constrained by the proving system to equal zero. Our expression + // has the following properties: + // - When s_mul = 0, any value is allowed in lhs, rhs, and out. + // - When s_mul != 0, this constrains lhs * rhs = out. + vec![s_mul * (lhs * rhs - out)] + }); + + FieldConfig { + advice, + instance, + s_mul, + } + } +} +// ANCHOR_END: chip-config + +// ANCHOR: chip-impl +impl Chip for FieldChip { + type Config = FieldConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} +// ANCHOR_END: chip-impl + +// ANCHOR: instructions-impl +/// A variable representing a number. +#[derive(Clone)] +struct Number(AssignedCell); + +impl NumericInstructions for FieldChip { + type Num = Number; + + fn load_private( + &self, + mut layouter: impl Layouter, + value: Value, + ) -> Result { + let config = self.config(); + + layouter.assign_region( + || "load private", + |mut region| { + region + .assign_advice(|| "private input", config.advice[0], 0, || value) + .map(Number) + }, + ) + } + + fn load_constant( + &self, + mut layouter: impl Layouter, + constant: F, + ) -> Result { + let config = self.config(); + + layouter.assign_region( + || "load constant", + |mut region| { + region + .assign_advice_from_constant(|| "constant value", config.advice[0], 0, constant) + .map(Number) + }, + ) + } + + fn mul( + &self, + mut layouter: impl Layouter, + a: Self::Num, + b: Self::Num, + ) -> Result { + let config = self.config(); + + layouter.assign_region( + || "mul", + |mut region: Region<'_, F>| { + // We only want to use a single multiplication gate in this region, + // so we enable it at region offset 0; this means it will constrain + // cells at offsets 0 and 1. + config.s_mul.enable(&mut region, 0)?; + + // The inputs we've been given could be located anywhere in the circuit, + // but we can only rely on relative offsets inside this region. So we + // assign new cells inside the region and constrain them to have the + // same values as the inputs. + a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; + b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; + + // Now we can assign the multiplication result, which is to be assigned + // into the output position. + let value = a.0.value().copied() * b.0.value(); + + // Finally, we do the assignment to the output, returning a + // variable to be used in another part of the circuit. + region + .assign_advice(|| "lhs * rhs", config.advice[0], 1, || value) + .map(Number) + }, + ) + } + + fn expose_public( + &self, + mut layouter: impl Layouter, + num: Self::Num, + row: usize, + ) -> Result<(), Error> { + let config = self.config(); + + layouter.constrain_instance(num.0.cell(), config.instance, row) + } +} +// ANCHOR_END: instructions-impl + +// ANCHOR: circuit +/// The full circuit implementation. +/// +/// In this struct we store the private input variables. We use `Option` because +/// they won't have any value during key generation. During proving, if any of these +/// were `None` we would get an error. +#[derive(Default)] +struct MyCircuit { + constant: F, + a: Value, + b: Value, +} + +impl Circuit for MyCircuit { + // Since we are using a single chip for everything, we can just reuse its config. + type Config = FieldConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + // We create the two advice columns that FieldChip uses for I/O. + let advice = [meta.advice_column(), meta.advice_column()]; + + // We also need an instance column to store public inputs. + let instance = meta.instance_column(); + + // Create a fixed column to load constants. + let constant = meta.fixed_column(); + + FieldChip::configure(meta, advice, instance, constant) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let field_chip = FieldChip::::construct(config); + + // Load our private values into the circuit. + let a = field_chip.load_private(layouter.namespace(|| "load a"), self.a)?; + let b = field_chip.load_private(layouter.namespace(|| "load b"), self.b)?; + + // Load the constant factor into the circuit. + let constant = + field_chip.load_constant(layouter.namespace(|| "load constant"), self.constant)?; + + // We only have access to plain multiplication. + // We could implement our circuit as: + // asq = a*a + // bsq = b*b + // absq = asq*bsq + // c = constant*asq*bsq + // + // but it's more efficient to implement it as: + // ab = a*b + // absq = ab^2 + // c = constant*absq + let ab = field_chip.mul(layouter.namespace(|| "a * b"), a, b)?; + let absq = field_chip.mul(layouter.namespace(|| "ab * ab"), ab.clone(), ab)?; + let c = field_chip.mul(layouter.namespace(|| "constant * absq"), constant, absq)?; + + // Expose the result as a public input to the circuit. + field_chip.expose_public(layouter.namespace(|| "expose c"), c, 0) + } +} +// ANCHOR_END: circuit + +fn main() { + use halo2_proofs::dev::MockProver; + use halo2curves::pasta::Fp; + + // ANCHOR: test-circuit + // The number of rows in our circuit cannot exceed 2^k. Since our example + // circuit is very small, we can pick a very small value here. + let k = 4; + + // Prepare the private and public inputs to the circuit! + let constant = Fp::from(7); + let a = Fp::from(2); + let b = Fp::from(3); + let c = constant * a.square() * b.square(); + + // Instantiate the circuit with the private inputs. + let circuit = MyCircuit { + constant, + a: Value::known(a), + b: Value::known(b), + }; + + // Arrange the public input. We expose the multiplication result in row 0 + // of the instance column, so we position it there in our public inputs. + let mut public_inputs = vec![c]; + + // Given the correct public input, our circuit will verify. + let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + + // If we try some other public input, the proof will fail! + public_inputs[0] += Fp::one(); + let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); + assert!(prover.verify().is_err()); + // ANCHOR_END: test-circuit +} diff --git a/examples/two-chip.rs b/examples/two-chip.rs index cc320d8e20..336f9c4957 100644 --- a/examples/two-chip.rs +++ b/examples/two-chip.rs @@ -1,539 +1,537 @@ -fn main() {} - -// use std::marker::PhantomData; -// -// use halo2_proofs::{ -// arithmetic::Field, -// circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, -// plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, -// poly::Rotation, -// }; -// -// // ANCHOR: field-instructions -// /// A variable representing a number. -// #[derive(Clone)] -// struct Number(AssignedCell); -// -// trait FieldInstructions: AddInstructions + MulInstructions { -// /// Variable representing a number. -// type Num; -// -// /// Loads a number into the circuit as a private input. -// fn load_private( -// &self, -// layouter: impl Layouter, -// a: Value, -// ) -> Result<>::Num, Error>; -// -// /// Returns `d = (a + b) * c`. -// fn add_and_mul( -// &self, -// layouter: &mut impl Layouter, -// a: >::Num, -// b: >::Num, -// c: >::Num, -// ) -> Result<>::Num, Error>; -// -// /// Exposes a number as a public input to the circuit. -// fn expose_public( -// &self, -// layouter: impl Layouter, -// num: >::Num, -// row: usize, -// ) -> Result<(), Error>; -// } -// // ANCHOR_END: field-instructions -// -// // ANCHOR: add-instructions -// trait AddInstructions: Chip { -// /// Variable representing a number. -// type Num; -// -// /// Returns `c = a + b`. -// fn add( -// &self, -// layouter: impl Layouter, -// a: Self::Num, -// b: Self::Num, -// ) -> Result; -// } -// // ANCHOR_END: add-instructions -// -// // ANCHOR: mul-instructions -// trait MulInstructions: Chip { -// /// Variable representing a number. -// type Num; -// -// /// Returns `c = a * b`. -// fn mul( -// &self, -// layouter: impl Layouter, -// a: Self::Num, -// b: Self::Num, -// ) -> Result; -// } -// // ANCHOR_END: mul-instructions -// -// // ANCHOR: field-config -// // The top-level config that provides all necessary columns and permutations -// // for the other configs. -// #[derive(Clone, Debug)] -// struct FieldConfig { -// /// For this chip, we will use two advice columns to implement our instructions. -// /// These are also the columns through which we communicate with other parts of -// /// the circuit. -// advice: [Column; 2], -// -// /// Public inputs -// instance: Column, -// -// add_config: AddConfig, -// mul_config: MulConfig, -// } -// // ANCHOR END: field-config -// -// // ANCHOR: add-config -// #[derive(Clone, Debug)] -// struct AddConfig { -// advice: [Column; 2], -// s_add: Selector, -// } -// // ANCHOR_END: add-config -// -// // ANCHOR: mul-config -// #[derive(Clone, Debug)] -// struct MulConfig { -// advice: [Column; 2], -// s_mul: Selector, -// } -// // ANCHOR END: mul-config -// -// // ANCHOR: field-chip -// /// The top-level chip that will implement the `FieldInstructions`. -// struct FieldChip { -// config: FieldConfig, -// _marker: PhantomData, -// } -// // ANCHOR_END: field-chip -// -// // ANCHOR: add-chip -// struct AddChip { -// config: AddConfig, -// _marker: PhantomData, -// } -// // ANCHOR END: add-chip -// -// // ANCHOR: mul-chip -// struct MulChip { -// config: MulConfig, -// _marker: PhantomData, -// } -// // ANCHOR_END: mul-chip -// -// // ANCHOR: add-chip-trait-impl -// impl Chip for AddChip { -// type Config = AddConfig; -// type Loaded = (); -// -// fn config(&self) -> &Self::Config { -// &self.config -// } -// -// fn loaded(&self) -> &Self::Loaded { -// &() -// } -// } -// // ANCHOR END: add-chip-trait-impl -// -// // ANCHOR: add-chip-impl -// impl AddChip { -// fn construct(config: >::Config, _loaded: >::Loaded) -> Self { -// Self { -// config, -// _marker: PhantomData, -// } -// } -// -// fn configure( -// meta: &mut ConstraintSystem, -// advice: [Column; 2], -// ) -> >::Config { -// let s_add = meta.selector(); -// -// // Define our addition gate! -// meta.create_gate("add", |meta| { -// let lhs = meta.query_advice(advice[0], Rotation::cur()); -// let rhs = meta.query_advice(advice[1], Rotation::cur()); -// let out = meta.query_advice(advice[0], Rotation::next()); -// let s_add = meta.query_selector(s_add); -// -// vec![s_add * (lhs + rhs - out)] -// }); -// -// AddConfig { advice, s_add } -// } -// } -// // ANCHOR END: add-chip-impl -// -// // ANCHOR: add-instructions-impl -// impl AddInstructions for FieldChip { -// type Num = Number; -// fn add( -// &self, -// layouter: impl Layouter, -// a: Self::Num, -// b: Self::Num, -// ) -> Result { -// let config = self.config().add_config.clone(); -// -// let add_chip = AddChip::::construct(config, ()); -// add_chip.add(layouter, a, b) -// } -// } -// -// impl AddInstructions for AddChip { -// type Num = Number; -// -// fn add( -// &self, -// mut layouter: impl Layouter, -// a: Self::Num, -// b: Self::Num, -// ) -> Result { -// let config = self.config(); -// -// layouter.assign_region( -// || "add", -// |mut region: Region<'_, F>| { -// // We only want to use a single addition gate in this region, -// // so we enable it at region offset 0; this means it will constrain -// // cells at offsets 0 and 1. -// config.s_add.enable(&mut region, 0)?; -// -// // The inputs we've been given could be located anywhere in the circuit, -// // but we can only rely on relative offsets inside this region. So we -// // assign new cells inside the region and constrain them to have the -// // same values as the inputs. -// a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; -// b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; -// -// // Now we can compute the addition result, which is to be assigned -// // into the output position. -// let value = a.0.value().copied() + b.0.value(); -// -// // Finally, we do the assignment to the output, returning a -// // variable to be used in another part of the circuit. -// region -// .assign_advice(|| "lhs + rhs", config.advice[0], 1, || value) -// .map(Number) -// }, -// ) -// } -// } -// // ANCHOR END: add-instructions-impl -// -// // ANCHOR: mul-chip-trait-impl -// impl Chip for MulChip { -// type Config = MulConfig; -// type Loaded = (); -// -// fn config(&self) -> &Self::Config { -// &self.config -// } -// -// fn loaded(&self) -> &Self::Loaded { -// &() -// } -// } -// // ANCHOR END: mul-chip-trait-impl -// -// // ANCHOR: mul-chip-impl -// impl MulChip { -// fn construct(config: >::Config, _loaded: >::Loaded) -> Self { -// Self { -// config, -// _marker: PhantomData, -// } -// } -// -// fn configure( -// meta: &mut ConstraintSystem, -// advice: [Column; 2], -// ) -> >::Config { -// for column in &advice { -// meta.enable_equality(*column); -// } -// let s_mul = meta.selector(); -// -// // Define our multiplication gate! -// meta.create_gate("mul", |meta| { -// // To implement multiplication, we need three advice cells and a selector -// // cell. We arrange them like so: -// // -// // | a0 | a1 | s_mul | -// // |-----|-----|-------| -// // | lhs | rhs | s_mul | -// // | out | | | -// // -// // Gates may refer to any relative offsets we want, but each distinct -// // offset adds a cost to the proof. The most common offsets are 0 (the -// // current row), 1 (the next row), and -1 (the previous row), for which -// // `Rotation` has specific constructors. -// let lhs = meta.query_advice(advice[0], Rotation::cur()); -// let rhs = meta.query_advice(advice[1], Rotation::cur()); -// let out = meta.query_advice(advice[0], Rotation::next()); -// let s_mul = meta.query_selector(s_mul); -// -// // The polynomial expression returned from `create_gate` will be -// // constrained by the proving system to equal zero. Our expression -// // has the following properties: -// // - When s_mul = 0, any value is allowed in lhs, rhs, and out. -// // - When s_mul != 0, this constrains lhs * rhs = out. -// vec![s_mul * (lhs * rhs - out)] -// }); -// -// MulConfig { advice, s_mul } -// } -// } -// // ANCHOR_END: mul-chip-impl -// -// // ANCHOR: mul-instructions-impl -// impl MulInstructions for FieldChip { -// type Num = Number; -// fn mul( -// &self, -// layouter: impl Layouter, -// a: Self::Num, -// b: Self::Num, -// ) -> Result { -// let config = self.config().mul_config.clone(); -// let mul_chip = MulChip::::construct(config, ()); -// mul_chip.mul(layouter, a, b) -// } -// } -// -// impl MulInstructions for MulChip { -// type Num = Number; -// -// fn mul( -// &self, -// mut layouter: impl Layouter, -// a: Self::Num, -// b: Self::Num, -// ) -> Result { -// let config = self.config(); -// -// layouter.assign_region( -// || "mul", -// |mut region: Region<'_, F>| { -// // We only want to use a single multiplication gate in this region, -// // so we enable it at region offset 0; this means it will constrain -// // cells at offsets 0 and 1. -// config.s_mul.enable(&mut region, 0)?; -// -// // The inputs we've been given could be located anywhere in the circuit, -// // but we can only rely on relative offsets inside this region. So we -// // assign new cells inside the region and constrain them to have the -// // same values as the inputs. -// a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; -// b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; -// -// // Now we can compute the multiplication result, which is to be assigned -// // into the output position. -// let value = a.0.value().copied() * b.0.value(); -// -// // Finally, we do the assignment to the output, returning a -// // variable to be used in another part of the circuit. -// region -// .assign_advice(|| "lhs * rhs", config.advice[0], 1, || value) -// .map(Number) -// }, -// ) -// } -// } -// // ANCHOR END: mul-instructions-impl -// -// // ANCHOR: field-chip-trait-impl -// impl Chip for FieldChip { -// type Config = FieldConfig; -// type Loaded = (); -// -// fn config(&self) -> &Self::Config { -// &self.config -// } -// -// fn loaded(&self) -> &Self::Loaded { -// &() -// } -// } -// // ANCHOR_END: field-chip-trait-impl -// -// // ANCHOR: field-chip-impl -// impl FieldChip { -// fn construct(config: >::Config, _loaded: >::Loaded) -> Self { -// Self { -// config, -// _marker: PhantomData, -// } -// } -// -// fn configure( -// meta: &mut ConstraintSystem, -// advice: [Column; 2], -// instance: Column, -// ) -> >::Config { -// let add_config = AddChip::configure(meta, advice); -// let mul_config = MulChip::configure(meta, advice); -// -// meta.enable_equality(instance); -// -// FieldConfig { -// advice, -// instance, -// add_config, -// mul_config, -// } -// } -// } -// // ANCHOR_END: field-chip-impl -// -// // ANCHOR: field-instructions-impl -// impl FieldInstructions for FieldChip { -// type Num = Number; -// -// fn load_private( -// &self, -// mut layouter: impl Layouter, -// value: Value, -// ) -> Result<>::Num, Error> { -// let config = self.config(); -// -// layouter.assign_region( -// || "load private", -// |mut region| { -// region -// .assign_advice(|| "private input", config.advice[0], 0, || value) -// .map(Number) -// }, -// ) -// } -// -// /// Returns `d = (a + b) * c`. -// fn add_and_mul( -// &self, -// layouter: &mut impl Layouter, -// a: >::Num, -// b: >::Num, -// c: >::Num, -// ) -> Result<>::Num, Error> { -// let ab = self.add(layouter.namespace(|| "a + b"), a, b)?; -// self.mul(layouter.namespace(|| "(a + b) * c"), ab, c) -// } -// -// fn expose_public( -// &self, -// mut layouter: impl Layouter, -// num: >::Num, -// row: usize, -// ) -> Result<(), Error> { -// let config = self.config(); -// -// layouter.constrain_instance(num.0.cell(), config.instance, row) -// } -// } -// // ANCHOR_END: field-instructions-impl -// -// // ANCHOR: circuit -// /// The full circuit implementation. -// /// -// /// In this struct we store the private input variables. We use `Value` because -// /// they won't have any value during key generation. During proving, if any of these -// /// were `Value::unknown()` we would get an error. -// #[derive(Default)] -// struct MyCircuit { -// a: Value, -// b: Value, -// c: Value, -// } -// -// impl Circuit for MyCircuit { -// // Since we are using a single chip for everything, we can just reuse its config. -// type Config = FieldConfig; -// type FloorPlanner = SimpleFloorPlanner; -// #[cfg(feature = "circuit-params")] -// type Params = (); -// -// fn without_witnesses(&self) -> Self { -// Self::default() -// } -// -// fn configure(meta: &mut ConstraintSystem) -> Self::Config { -// // We create the two advice columns that FieldChip uses for I/O. -// let advice = [meta.advice_column(), meta.advice_column()]; -// -// // We also need an instance column to store public inputs. -// let instance = meta.instance_column(); -// -// FieldChip::configure(meta, advice, instance) -// } -// -// fn synthesize( -// &self, -// config: Self::Config, -// mut layouter: impl Layouter, -// ) -> Result<(), Error> { -// let field_chip = FieldChip::::construct(config, ()); -// -// // Load our private values into the circuit. -// let a = field_chip.load_private(layouter.namespace(|| "load a"), self.a)?; -// let b = field_chip.load_private(layouter.namespace(|| "load b"), self.b)?; -// let c = field_chip.load_private(layouter.namespace(|| "load c"), self.c)?; -// -// // Use `add_and_mul` to get `d = (a + b) * c`. -// let d = field_chip.add_and_mul(&mut layouter, a, b, c)?; -// -// // Expose the result as a public input to the circuit. -// field_chip.expose_public(layouter.namespace(|| "expose d"), d, 0) -// } -// } -// // ANCHOR_END: circuit -// -// #[allow(clippy::many_single_char_names)] -// fn main() { -// use halo2_proofs::dev::MockProver; -// use halo2curves::pasta::Fp; -// use rand_core::OsRng; -// -// // ANCHOR: test-circuit -// // The number of rows in our circuit cannot exceed 2^k. Since our example -// // circuit is very small, we can pick a very small value here. -// let k = 4; -// -// // Prepare the private and public inputs to the circuit! -// let rng = OsRng; -// let a = Fp::random(rng); -// let b = Fp::random(rng); -// let c = Fp::random(rng); -// let d = (a + b) * c; -// -// // Instantiate the circuit with the private inputs. -// let circuit = MyCircuit { -// a: Value::known(a), -// b: Value::known(b), -// c: Value::known(c), -// }; -// -// // Arrange the public input. We expose the multiplication result in row 0 -// // of the instance column, so we position it there in our public inputs. -// let mut public_inputs = vec![d]; -// -// // Given the correct public input, our circuit will verify. -// let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); -// assert_eq!(prover.verify(), Ok(())); -// -// // If we try some other public input, the proof will fail! -// public_inputs[0] += Fp::one(); -// let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); -// assert!(prover.verify().is_err()); -// // ANCHOR_END: test-circuit -// } +use std::marker::PhantomData; + +use halo2_proofs::{ + arithmetic::Field, + circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, + poly::Rotation, +}; + +// ANCHOR: field-instructions +/// A variable representing a number. +#[derive(Clone)] +struct Number(AssignedCell); + +trait FieldInstructions: AddInstructions + MulInstructions { + /// Variable representing a number. + type Num; + + /// Loads a number into the circuit as a private input. + fn load_private( + &self, + layouter: impl Layouter, + a: Value, + ) -> Result<>::Num, Error>; + + /// Returns `d = (a + b) * c`. + fn add_and_mul( + &self, + layouter: &mut impl Layouter, + a: >::Num, + b: >::Num, + c: >::Num, + ) -> Result<>::Num, Error>; + + /// Exposes a number as a public input to the circuit. + fn expose_public( + &self, + layouter: impl Layouter, + num: >::Num, + row: usize, + ) -> Result<(), Error>; +} +// ANCHOR_END: field-instructions + +// ANCHOR: add-instructions +trait AddInstructions: Chip { + /// Variable representing a number. + type Num; + + /// Returns `c = a + b`. + fn add( + &self, + layouter: impl Layouter, + a: Self::Num, + b: Self::Num, + ) -> Result; +} +// ANCHOR_END: add-instructions + +// ANCHOR: mul-instructions +trait MulInstructions: Chip { + /// Variable representing a number. + type Num; + + /// Returns `c = a * b`. + fn mul( + &self, + layouter: impl Layouter, + a: Self::Num, + b: Self::Num, + ) -> Result; +} +// ANCHOR_END: mul-instructions + +// ANCHOR: field-config +// The top-level config that provides all necessary columns and permutations +// for the other configs. +#[derive(Clone, Debug)] +struct FieldConfig { + /// For this chip, we will use two advice columns to implement our instructions. + /// These are also the columns through which we communicate with other parts of + /// the circuit. + advice: [Column; 2], + + /// Public inputs + instance: Column, + + add_config: AddConfig, + mul_config: MulConfig, +} +// ANCHOR END: field-config + +// ANCHOR: add-config +#[derive(Clone, Debug)] +struct AddConfig { + advice: [Column; 2], + s_add: Selector, +} +// ANCHOR_END: add-config + +// ANCHOR: mul-config +#[derive(Clone, Debug)] +struct MulConfig { + advice: [Column; 2], + s_mul: Selector, +} +// ANCHOR END: mul-config + +// ANCHOR: field-chip +/// The top-level chip that will implement the `FieldInstructions`. +struct FieldChip { + config: FieldConfig, + _marker: PhantomData, +} +// ANCHOR_END: field-chip + +// ANCHOR: add-chip +struct AddChip { + config: AddConfig, + _marker: PhantomData, +} +// ANCHOR END: add-chip + +// ANCHOR: mul-chip +struct MulChip { + config: MulConfig, + _marker: PhantomData, +} +// ANCHOR_END: mul-chip + +// ANCHOR: add-chip-trait-impl +impl Chip for AddChip { + type Config = AddConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} +// ANCHOR END: add-chip-trait-impl + +// ANCHOR: add-chip-impl +impl AddChip { + fn construct(config: >::Config, _loaded: >::Loaded) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + fn configure( + meta: &mut ConstraintSystem, + advice: [Column; 2], + ) -> >::Config { + let s_add = meta.selector(); + + // Define our addition gate! + meta.create_gate("add", |meta| { + let lhs = meta.query_advice(advice[0], Rotation::cur()); + let rhs = meta.query_advice(advice[1], Rotation::cur()); + let out = meta.query_advice(advice[0], Rotation::next()); + let s_add = meta.query_selector(s_add); + + vec![s_add * (lhs + rhs - out)] + }); + + AddConfig { advice, s_add } + } +} +// ANCHOR END: add-chip-impl + +// ANCHOR: add-instructions-impl +impl AddInstructions for FieldChip { + type Num = Number; + fn add( + &self, + layouter: impl Layouter, + a: Self::Num, + b: Self::Num, + ) -> Result { + let config = self.config().add_config.clone(); + + let add_chip = AddChip::::construct(config, ()); + add_chip.add(layouter, a, b) + } +} + +impl AddInstructions for AddChip { + type Num = Number; + + fn add( + &self, + mut layouter: impl Layouter, + a: Self::Num, + b: Self::Num, + ) -> Result { + let config = self.config(); + + layouter.assign_region( + || "add", + |mut region: Region<'_, F>| { + // We only want to use a single addition gate in this region, + // so we enable it at region offset 0; this means it will constrain + // cells at offsets 0 and 1. + config.s_add.enable(&mut region, 0)?; + + // The inputs we've been given could be located anywhere in the circuit, + // but we can only rely on relative offsets inside this region. So we + // assign new cells inside the region and constrain them to have the + // same values as the inputs. + a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; + b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; + + // Now we can compute the addition result, which is to be assigned + // into the output position. + let value = a.0.value().copied() + b.0.value(); + + // Finally, we do the assignment to the output, returning a + // variable to be used in another part of the circuit. + region + .assign_advice(|| "lhs + rhs", config.advice[0], 1, || value) + .map(Number) + }, + ) + } +} +// ANCHOR END: add-instructions-impl + +// ANCHOR: mul-chip-trait-impl +impl Chip for MulChip { + type Config = MulConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} +// ANCHOR END: mul-chip-trait-impl + +// ANCHOR: mul-chip-impl +impl MulChip { + fn construct(config: >::Config, _loaded: >::Loaded) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + fn configure( + meta: &mut ConstraintSystem, + advice: [Column; 2], + ) -> >::Config { + for column in &advice { + meta.enable_equality(*column); + } + let s_mul = meta.selector(); + + // Define our multiplication gate! + meta.create_gate("mul", |meta| { + // To implement multiplication, we need three advice cells and a selector + // cell. We arrange them like so: + // + // | a0 | a1 | s_mul | + // |-----|-----|-------| + // | lhs | rhs | s_mul | + // | out | | | + // + // Gates may refer to any relative offsets we want, but each distinct + // offset adds a cost to the proof. The most common offsets are 0 (the + // current row), 1 (the next row), and -1 (the previous row), for which + // `Rotation` has specific constructors. + let lhs = meta.query_advice(advice[0], Rotation::cur()); + let rhs = meta.query_advice(advice[1], Rotation::cur()); + let out = meta.query_advice(advice[0], Rotation::next()); + let s_mul = meta.query_selector(s_mul); + + // The polynomial expression returned from `create_gate` will be + // constrained by the proving system to equal zero. Our expression + // has the following properties: + // - When s_mul = 0, any value is allowed in lhs, rhs, and out. + // - When s_mul != 0, this constrains lhs * rhs = out. + vec![s_mul * (lhs * rhs - out)] + }); + + MulConfig { advice, s_mul } + } +} +// ANCHOR_END: mul-chip-impl + +// ANCHOR: mul-instructions-impl +impl MulInstructions for FieldChip { + type Num = Number; + fn mul( + &self, + layouter: impl Layouter, + a: Self::Num, + b: Self::Num, + ) -> Result { + let config = self.config().mul_config.clone(); + let mul_chip = MulChip::::construct(config, ()); + mul_chip.mul(layouter, a, b) + } +} + +impl MulInstructions for MulChip { + type Num = Number; + + fn mul( + &self, + mut layouter: impl Layouter, + a: Self::Num, + b: Self::Num, + ) -> Result { + let config = self.config(); + + layouter.assign_region( + || "mul", + |mut region: Region<'_, F>| { + // We only want to use a single multiplication gate in this region, + // so we enable it at region offset 0; this means it will constrain + // cells at offsets 0 and 1. + config.s_mul.enable(&mut region, 0)?; + + // The inputs we've been given could be located anywhere in the circuit, + // but we can only rely on relative offsets inside this region. So we + // assign new cells inside the region and constrain them to have the + // same values as the inputs. + a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; + b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; + + // Now we can compute the multiplication result, which is to be assigned + // into the output position. + let value = a.0.value().copied() * b.0.value(); + + // Finally, we do the assignment to the output, returning a + // variable to be used in another part of the circuit. + region + .assign_advice(|| "lhs * rhs", config.advice[0], 1, || value) + .map(Number) + }, + ) + } +} +// ANCHOR END: mul-instructions-impl + +// ANCHOR: field-chip-trait-impl +impl Chip for FieldChip { + type Config = FieldConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} +// ANCHOR_END: field-chip-trait-impl + +// ANCHOR: field-chip-impl +impl FieldChip { + fn construct(config: >::Config, _loaded: >::Loaded) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + fn configure( + meta: &mut ConstraintSystem, + advice: [Column; 2], + instance: Column, + ) -> >::Config { + let add_config = AddChip::configure(meta, advice); + let mul_config = MulChip::configure(meta, advice); + + meta.enable_equality(instance); + + FieldConfig { + advice, + instance, + add_config, + mul_config, + } + } +} +// ANCHOR_END: field-chip-impl + +// ANCHOR: field-instructions-impl +impl FieldInstructions for FieldChip { + type Num = Number; + + fn load_private( + &self, + mut layouter: impl Layouter, + value: Value, + ) -> Result<>::Num, Error> { + let config = self.config(); + + layouter.assign_region( + || "load private", + |mut region| { + region + .assign_advice(|| "private input", config.advice[0], 0, || value) + .map(Number) + }, + ) + } + + /// Returns `d = (a + b) * c`. + fn add_and_mul( + &self, + layouter: &mut impl Layouter, + a: >::Num, + b: >::Num, + c: >::Num, + ) -> Result<>::Num, Error> { + let ab = self.add(layouter.namespace(|| "a + b"), a, b)?; + self.mul(layouter.namespace(|| "(a + b) * c"), ab, c) + } + + fn expose_public( + &self, + mut layouter: impl Layouter, + num: >::Num, + row: usize, + ) -> Result<(), Error> { + let config = self.config(); + + layouter.constrain_instance(num.0.cell(), config.instance, row) + } +} +// ANCHOR_END: field-instructions-impl + +// ANCHOR: circuit +/// The full circuit implementation. +/// +/// In this struct we store the private input variables. We use `Value` because +/// they won't have any value during key generation. During proving, if any of these +/// were `Value::unknown()` we would get an error. +#[derive(Default)] +struct MyCircuit { + a: Value, + b: Value, + c: Value, +} + +impl Circuit for MyCircuit { + // Since we are using a single chip for everything, we can just reuse its config. + type Config = FieldConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + // We create the two advice columns that FieldChip uses for I/O. + let advice = [meta.advice_column(), meta.advice_column()]; + + // We also need an instance column to store public inputs. + let instance = meta.instance_column(); + + FieldChip::configure(meta, advice, instance) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let field_chip = FieldChip::::construct(config, ()); + + // Load our private values into the circuit. + let a = field_chip.load_private(layouter.namespace(|| "load a"), self.a)?; + let b = field_chip.load_private(layouter.namespace(|| "load b"), self.b)?; + let c = field_chip.load_private(layouter.namespace(|| "load c"), self.c)?; + + // Use `add_and_mul` to get `d = (a + b) * c`. + let d = field_chip.add_and_mul(&mut layouter, a, b, c)?; + + // Expose the result as a public input to the circuit. + field_chip.expose_public(layouter.namespace(|| "expose d"), d, 0) + } +} +// ANCHOR_END: circuit + +#[allow(clippy::many_single_char_names)] +fn main() { + use halo2_proofs::dev::MockProver; + use halo2curves::pasta::Fp; + use rand_core::OsRng; + + // ANCHOR: test-circuit + // The number of rows in our circuit cannot exceed 2^k. Since our example + // circuit is very small, we can pick a very small value here. + let k = 4; + + // Prepare the private and public inputs to the circuit! + let rng = OsRng; + let a = Fp::random(rng); + let b = Fp::random(rng); + let c = Fp::random(rng); + let d = (a + b) * c; + + // Instantiate the circuit with the private inputs. + let circuit = MyCircuit { + a: Value::known(a), + b: Value::known(b), + c: Value::known(c), + }; + + // Arrange the public input. We expose the multiplication result in row 0 + // of the instance column, so we position it there in our public inputs. + let mut public_inputs = vec![d]; + + // Given the correct public input, our circuit will verify. + let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + + // If we try some other public input, the proof will fail! + public_inputs[0] += Fp::one(); + let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); + assert!(prover.verify().is_err()); + // ANCHOR_END: test-circuit +} diff --git a/examples/vector-mul.rs b/examples/vector-mul.rs index e35927cc54..5a99f57bc2 100644 --- a/examples/vector-mul.rs +++ b/examples/vector-mul.rs @@ -1,317 +1,316 @@ -fn main() {} -// use std::marker::PhantomData; -// -// use halo2_proofs::{ -// arithmetic::Field, -// circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, -// plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, -// poly::Rotation, -// }; -// -// // ANCHOR: instructions -// trait NumericInstructions: Chip { -// /// Variable representing a number. -// type Num; -// -// /// Loads a number into the circuit as a private input. -// fn load_private( -// &self, -// layouter: impl Layouter, -// a: &[Value], -// ) -> Result, Error>; -// -// /// Returns `c = a * b`. The caller is responsible for ensuring that `a.len() == b.len()`. -// fn mul( -// &self, -// layouter: impl Layouter, -// a: &[Self::Num], -// b: &[Self::Num], -// ) -> Result, Error>; -// -// /// Exposes a number as a public input to the circuit. -// fn expose_public( -// &self, -// layouter: impl Layouter, -// num: &Self::Num, -// row: usize, -// ) -> Result<(), Error>; -// } -// // ANCHOR_END: instructions -// -// // ANCHOR: chip -// /// The chip that will implement our instructions! Chips store their own -// /// config, as well as type markers if necessary. -// struct FieldChip { -// config: FieldConfig, -// _marker: PhantomData, -// } -// // ANCHOR_END: chip -// -// // ANCHOR: chip-config -// /// Chip state is stored in a config struct. This is generated by the chip -// /// during configuration, and then stored inside the chip. -// #[derive(Clone, Debug)] -// struct FieldConfig { -// /// For this chip, we will use two advice columns to implement our instructions. -// /// These are also the columns through which we communicate with other parts of -// /// the circuit. -// advice: [Column; 3], -// -// /// This is the public input (instance) column. -// instance: Column, -// -// // We need a selector to enable the multiplication gate, so that we aren't placing -// // any constraints on cells where `NumericInstructions::mul` is not being used. -// // This is important when building larger circuits, where columns are used by -// // multiple sets of instructions. -// s_mul: Selector, -// } -// -// impl FieldChip { -// fn construct(config: >::Config) -> Self { -// Self { -// config, -// _marker: PhantomData, -// } -// } -// -// fn configure( -// meta: &mut ConstraintSystem, -// advice: [Column; 3], -// instance: Column, -// ) -> >::Config { -// meta.enable_equality(instance); -// for column in &advice { -// meta.enable_equality(*column); -// } -// let s_mul = meta.selector(); -// -// // Define our multiplication gate! -// meta.create_gate("mul", |meta| { -// // To implement multiplication, we need three advice cells and a selector -// // cell. We arrange them like so: -// // -// // | a0 | a1 | a2 | s_mul | -// // |-----|-----|-----|-------| -// // | lhs | rhs | out | s_mul | -// // -// // Gates may refer to any relative offsets we want, but each distinct -// // offset adds a cost to the proof. The most common offsets are 0 (the -// // current row), 1 (the next row), and -1 (the previous row), for which -// // `Rotation` has specific constructors. -// let lhs = meta.query_advice(advice[0], Rotation::cur()); -// let rhs = meta.query_advice(advice[1], Rotation::cur()); -// let out = meta.query_advice(advice[2], Rotation::cur()); -// let s_mul = meta.query_selector(s_mul); -// -// // Finally, we return the polynomial expressions that constrain this gate. -// // For our multiplication gate, we only need a single polynomial constraint. -// // -// // The polynomial expressions returned from `create_gate` will be -// // constrained by the proving system to equal zero. Our expression -// // has the following properties: -// // - When s_mul = 0, any value is allowed in lhs, rhs, and out. -// // - When s_mul != 0, this constrains lhs * rhs = out. -// vec![s_mul * (lhs * rhs - out)] -// }); -// -// FieldConfig { -// advice, -// instance, -// s_mul, -// } -// } -// } -// // ANCHOR_END: chip-config -// -// // ANCHOR: chip-impl -// impl Chip for FieldChip { -// type Config = FieldConfig; -// type Loaded = (); -// -// fn config(&self) -> &Self::Config { -// &self.config -// } -// -// fn loaded(&self) -> &Self::Loaded { -// &() -// } -// } -// // ANCHOR_END: chip-impl -// -// // ANCHOR: instructions-impl -// /// A variable representing a number. -// #[derive(Clone, Debug)] -// struct Number(AssignedCell); -// -// impl NumericInstructions for FieldChip { -// type Num = Number; -// -// fn load_private( -// &self, -// mut layouter: impl Layouter, -// values: &[Value], -// ) -> Result, Error> { -// let config = self.config(); -// -// layouter.assign_region( -// || "load private", -// |mut region| { -// values -// .iter() -// .enumerate() -// .map(|(i, value)| { -// region -// .assign_advice(|| "private input", config.advice[0], i, || *value) -// .map(Number) -// }) -// .collect() -// }, -// ) -// } -// -// fn mul( -// &self, -// mut layouter: impl Layouter, -// a: &[Self::Num], -// b: &[Self::Num], -// ) -> Result, Error> { -// let config = self.config(); -// assert_eq!(a.len(), b.len()); -// -// layouter.assign_region( -// || "mul", -// |mut region: Region<'_, F>| { -// a.iter() -// .zip(b.iter()) -// .enumerate() -// .map(|(i, (a, b))| { -// config.s_mul.enable(&mut region, i)?; -// -// a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?; -// b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?; -// -// let value = a.0.value().copied() * b.0.value(); -// -// // Finally, we do the assignment to the output, returning a -// // variable to be used in another part of the circuit. -// region -// .assign_advice(|| "lhs * rhs", config.advice[2], i, || value) -// .map(Number) -// }) -// .collect() -// }, -// ) -// } -// -// fn expose_public( -// &self, -// mut layouter: impl Layouter, -// num: &Self::Num, -// row: usize, -// ) -> Result<(), Error> { -// let config = self.config(); -// -// layouter.constrain_instance(num.0.cell(), config.instance, row) -// } -// } -// // ANCHOR_END: instructions-impl -// -// // ANCHOR: circuit -// /// The full circuit implementation. -// /// -// /// In this struct we store the private input variables. We use `Option` because -// /// they won't have any value during key generation. During proving, if any of these -// /// were `None` we would get an error. -// #[derive(Default)] -// struct MyCircuit { -// a: Vec>, -// b: Vec>, -// } -// -// impl Circuit for MyCircuit { -// // Since we are using a single chip for everything, we can just reuse its config. -// type Config = FieldConfig; -// type FloorPlanner = SimpleFloorPlanner; -// #[cfg(feature = "circuit-params")] -// type Params = (); -// -// fn without_witnesses(&self) -> Self { -// Self::default() -// } -// -// fn configure(meta: &mut ConstraintSystem) -> Self::Config { -// // We create the three advice columns that FieldChip uses for I/O. -// let advice = [ -// meta.advice_column(), -// meta.advice_column(), -// meta.advice_column(), -// ]; -// -// // We also need an instance column to store public inputs. -// let instance = meta.instance_column(); -// -// FieldChip::configure(meta, advice, instance) -// } -// -// fn synthesize( -// &self, -// config: Self::Config, -// mut layouter: impl Layouter, -// ) -> Result<(), Error> { -// let field_chip = FieldChip::::construct(config); -// -// // Load our private values into the circuit. -// let a = field_chip.load_private(layouter.namespace(|| "load a"), &self.a)?; -// let b = field_chip.load_private(layouter.namespace(|| "load b"), &self.b)?; -// -// let ab = field_chip.mul(layouter.namespace(|| "a * b"), &a, &b)?; -// -// for (i, c) in ab.iter().enumerate() { -// // Expose the result as a public input to the circuit. -// field_chip.expose_public(layouter.namespace(|| "expose c"), c, i)?; -// } -// Ok(()) -// } -// } -// // ANCHOR_END: circuit -// -// fn main() { -// use halo2_proofs::dev::MockProver; -// use halo2curves::pasta::Fp; -// -// const N: usize = 20000; -// // ANCHOR: test-circuit -// // The number of rows in our circuit cannot exceed 2^k. Since our example -// // circuit is very small, we can pick a very small value here. -// let k = 16; -// -// // Prepare the private and public inputs to the circuit! -// let a = [Fp::from(2); N]; -// let b = [Fp::from(3); N]; -// let c: Vec = a.iter().zip(b).map(|(&a, b)| a * b).collect(); -// -// // Instantiate the circuit with the private inputs. -// let circuit = MyCircuit { -// a: a.iter().map(|&x| Value::known(x)).collect(), -// b: b.iter().map(|&x| Value::known(x)).collect(), -// }; -// -// // Arrange the public input. We expose the multiplication result in row 0 -// // of the instance column, so we position it there in our public inputs. -// let mut public_inputs = c; -// -// let start = std::time::Instant::now(); -// // Given the correct public input, our circuit will verify. -// let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); -// assert_eq!(prover.verify(), Ok(())); -// println!("positive test took {:?}", start.elapsed()); -// -// // If we try some other public input, the proof will fail! -// let start = std::time::Instant::now(); -// public_inputs[0] += Fp::one(); -// let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); -// assert!(prover.verify().is_err()); -// println!("negative test took {:?}", start.elapsed()); -// // ANCHOR_END: test-circuit -// } +use std::marker::PhantomData; + +use halo2_proofs::{ + arithmetic::Field, + circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, + poly::Rotation, +}; + +// ANCHOR: instructions +trait NumericInstructions: Chip { + /// Variable representing a number. + type Num; + + /// Loads a number into the circuit as a private input. + fn load_private( + &self, + layouter: impl Layouter, + a: &[Value], + ) -> Result, Error>; + + /// Returns `c = a * b`. The caller is responsible for ensuring that `a.len() == b.len()`. + fn mul( + &self, + layouter: impl Layouter, + a: &[Self::Num], + b: &[Self::Num], + ) -> Result, Error>; + + /// Exposes a number as a public input to the circuit. + fn expose_public( + &self, + layouter: impl Layouter, + num: &Self::Num, + row: usize, + ) -> Result<(), Error>; +} +// ANCHOR_END: instructions + +// ANCHOR: chip +/// The chip that will implement our instructions! Chips store their own +/// config, as well as type markers if necessary. +struct FieldChip { + config: FieldConfig, + _marker: PhantomData, +} +// ANCHOR_END: chip + +// ANCHOR: chip-config +/// Chip state is stored in a config struct. This is generated by the chip +/// during configuration, and then stored inside the chip. +#[derive(Clone, Debug)] +struct FieldConfig { + /// For this chip, we will use two advice columns to implement our instructions. + /// These are also the columns through which we communicate with other parts of + /// the circuit. + advice: [Column; 3], + + /// This is the public input (instance) column. + instance: Column, + + // We need a selector to enable the multiplication gate, so that we aren't placing + // any constraints on cells where `NumericInstructions::mul` is not being used. + // This is important when building larger circuits, where columns are used by + // multiple sets of instructions. + s_mul: Selector, +} + +impl FieldChip { + fn construct(config: >::Config) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + fn configure( + meta: &mut ConstraintSystem, + advice: [Column; 3], + instance: Column, + ) -> >::Config { + meta.enable_equality(instance); + for column in &advice { + meta.enable_equality(*column); + } + let s_mul = meta.selector(); + + // Define our multiplication gate! + meta.create_gate("mul", |meta| { + // To implement multiplication, we need three advice cells and a selector + // cell. We arrange them like so: + // + // | a0 | a1 | a2 | s_mul | + // |-----|-----|-----|-------| + // | lhs | rhs | out | s_mul | + // + // Gates may refer to any relative offsets we want, but each distinct + // offset adds a cost to the proof. The most common offsets are 0 (the + // current row), 1 (the next row), and -1 (the previous row), for which + // `Rotation` has specific constructors. + let lhs = meta.query_advice(advice[0], Rotation::cur()); + let rhs = meta.query_advice(advice[1], Rotation::cur()); + let out = meta.query_advice(advice[2], Rotation::cur()); + let s_mul = meta.query_selector(s_mul); + + // Finally, we return the polynomial expressions that constrain this gate. + // For our multiplication gate, we only need a single polynomial constraint. + // + // The polynomial expressions returned from `create_gate` will be + // constrained by the proving system to equal zero. Our expression + // has the following properties: + // - When s_mul = 0, any value is allowed in lhs, rhs, and out. + // - When s_mul != 0, this constrains lhs * rhs = out. + vec![s_mul * (lhs * rhs - out)] + }); + + FieldConfig { + advice, + instance, + s_mul, + } + } +} +// ANCHOR_END: chip-config + +// ANCHOR: chip-impl +impl Chip for FieldChip { + type Config = FieldConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} +// ANCHOR_END: chip-impl + +// ANCHOR: instructions-impl +/// A variable representing a number. +#[derive(Clone, Debug)] +struct Number(AssignedCell); + +impl NumericInstructions for FieldChip { + type Num = Number; + + fn load_private( + &self, + mut layouter: impl Layouter, + values: &[Value], + ) -> Result, Error> { + let config = self.config(); + + layouter.assign_region( + || "load private", + |mut region| { + values + .iter() + .enumerate() + .map(|(i, value)| { + region + .assign_advice(|| "private input", config.advice[0], i, || *value) + .map(Number) + }) + .collect() + }, + ) + } + + fn mul( + &self, + mut layouter: impl Layouter, + a: &[Self::Num], + b: &[Self::Num], + ) -> Result, Error> { + let config = self.config(); + assert_eq!(a.len(), b.len()); + + layouter.assign_region( + || "mul", + |mut region: Region<'_, F>| { + a.iter() + .zip(b.iter()) + .enumerate() + .map(|(i, (a, b))| { + config.s_mul.enable(&mut region, i)?; + + a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?; + b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?; + + let value = a.0.value().copied() * b.0.value(); + + // Finally, we do the assignment to the output, returning a + // variable to be used in another part of the circuit. + region + .assign_advice(|| "lhs * rhs", config.advice[2], i, || value) + .map(Number) + }) + .collect() + }, + ) + } + + fn expose_public( + &self, + mut layouter: impl Layouter, + num: &Self::Num, + row: usize, + ) -> Result<(), Error> { + let config = self.config(); + + layouter.constrain_instance(num.0.cell(), config.instance, row) + } +} +// ANCHOR_END: instructions-impl + +// ANCHOR: circuit +/// The full circuit implementation. +/// +/// In this struct we store the private input variables. We use `Option` because +/// they won't have any value during key generation. During proving, if any of these +/// were `None` we would get an error. +#[derive(Default)] +struct MyCircuit { + a: Vec>, + b: Vec>, +} + +impl Circuit for MyCircuit { + // Since we are using a single chip for everything, we can just reuse its config. + type Config = FieldConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + // We create the three advice columns that FieldChip uses for I/O. + let advice = [ + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ]; + + // We also need an instance column to store public inputs. + let instance = meta.instance_column(); + + FieldChip::configure(meta, advice, instance) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let field_chip = FieldChip::::construct(config); + + // Load our private values into the circuit. + let a = field_chip.load_private(layouter.namespace(|| "load a"), &self.a)?; + let b = field_chip.load_private(layouter.namespace(|| "load b"), &self.b)?; + + let ab = field_chip.mul(layouter.namespace(|| "a * b"), &a, &b)?; + + for (i, c) in ab.iter().enumerate() { + // Expose the result as a public input to the circuit. + field_chip.expose_public(layouter.namespace(|| "expose c"), c, i)?; + } + Ok(()) + } +} +// ANCHOR_END: circuit + +fn main() { + use halo2_proofs::dev::MockProver; + use halo2curves::pasta::Fp; + + const N: usize = 20000; + // ANCHOR: test-circuit + // The number of rows in our circuit cannot exceed 2^k. Since our example + // circuit is very small, we can pick a very small value here. + let k = 16; + + // Prepare the private and public inputs to the circuit! + let a = [Fp::from(2); N]; + let b = [Fp::from(3); N]; + let c: Vec = a.iter().zip(b.iter()).map(|(&a, b)| a * b).collect(); + + // Instantiate the circuit with the private inputs. + let circuit = MyCircuit { + a: a.iter().map(|&x| Value::known(x)).collect(), + b: b.iter().map(|&x| Value::known(x)).collect(), + }; + + // Arrange the public input. We expose the multiplication result in row 0 + // of the instance column, so we position it there in our public inputs. + let mut public_inputs = c; + + let start = std::time::Instant::now(); + // Given the correct public input, our circuit will verify. + let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + println!("positive test took {:?}", start.elapsed()); + + // If we try some other public input, the proof will fail! + let start = std::time::Instant::now(); + public_inputs[0] += Fp::one(); + let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); + assert!(prover.verify().is_err()); + println!("negative test took {:?}", start.elapsed()); + // ANCHOR_END: test-circuit +} diff --git a/examples/vector-ops-unblinded.rs b/examples/vector-ops-unblinded.rs index ed75fd1149..c8f4f957d1 100644 --- a/examples/vector-ops-unblinded.rs +++ b/examples/vector-ops-unblinded.rs @@ -1,564 +1,552 @@ -fn main() {} - -// /// Here we construct two circuits one for adding two vectors and one for multiplying and we check that their transcripts have the same inputs -// /// by way of the unblinded inputs. -// /// This is a simple example of how to use unblinded inputs to match up circuits that might be proved on different host machines. -// use std::marker::PhantomData; -// -// use ff::{FromUniformBytes}; -// use halo2_proofs::arithmetic::CurveExt; -// use halo2_proofs::helpers::SerdeCurveAffine; -// use halo2_proofs::poly::kzg::params::{KZGCommitmentScheme, ParamsKZG}; -// use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; -// use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; -// use halo2_proofs::{ -// arithmetic::Field, -// circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, -// plonk::*, -// poly::{commitment::ParamsProver, Rotation, VerificationStrategy}, -// transcript::{ -// Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, -// }, -// }; -// use halo2curves::pairing::{Engine, MultiMillerLoop}; -// use rand_core::OsRng; -// -// // ANCHOR: instructions -// trait NumericInstructions: Chip { -// /// Variable representing a number. -// type Num; -// -// /// Loads a number into the circuit as an unblinded input this can be matched to inputs in other circuits ! -// fn load_unblinded( -// &self, -// layouter: impl Layouter, -// a: &[Value], -// ) -> Result, Error>; -// -// /// Returns `c = a * b`. The caller is responsible for ensuring that `a.len() == b.len()`. -// fn mul( -// &self, -// layouter: impl Layouter, -// a: &[Self::Num], -// b: &[Self::Num], -// ) -> Result, Error>; -// -// /// Returns `c = a + b`. The caller is responsible for ensuring that `a.len() == b.len()`. -// fn add( -// &self, -// layouter: impl Layouter, -// a: &[Self::Num], -// b: &[Self::Num], -// ) -> Result, Error>; -// -// /// Exposes a number as a public input to the circuit. -// fn expose_public( -// &self, -// layouter: impl Layouter, -// num: &Self::Num, -// row: usize, -// ) -> Result<(), Error>; -// } -// // ANCHOR_END: instructions -// -// // ANCHOR: chip -// /// The chip that will implement our instructions! Chips store their own -// /// config, as well as type markers if necessary. -// struct MultChip { -// config: FieldConfig, -// _marker: PhantomData, -// } -// -// // ANCHOR: chip -// /// The chip that will implement our instructions! Chips store their own -// /// config, as well as type markers if necessary. -// struct AddChip { -// config: FieldConfig, -// _marker: PhantomData, -// } -// // ANCHOR_END: chip -// -// // ANCHOR: chip-config -// /// Chip state is stored in a config struct. This is generated by the chip -// /// during configuration, and then stored inside the chip. -// #[derive(Clone, Debug)] -// struct FieldConfig { -// advice: [Column; 3], -// instance: Column, -// s: Selector, -// } -// -// impl MultChip { -// fn construct(config: >::Config) -> Self { -// Self { -// config, -// _marker: PhantomData, -// } -// } -// -// fn configure( -// meta: &mut ConstraintSystem, -// advice: [Column; 3], -// instance: Column, -// ) -> >::Config { -// meta.enable_equality(instance); -// for column in &advice { -// meta.enable_equality(*column); -// } -// let s = meta.selector(); -// -// // Define our multiplication gate! -// meta.create_gate("mul", |meta| { -// let lhs = meta.query_advice(advice[0], Rotation::cur()); -// let rhs = meta.query_advice(advice[1], Rotation::cur()); -// let out = meta.query_advice(advice[2], Rotation::cur()); -// let s_mul = meta.query_selector(s); -// -// vec![s_mul * (lhs * rhs - out)] -// }); -// -// FieldConfig { -// advice, -// instance, -// s, -// } -// } -// } -// -// impl AddChip { -// fn construct(config: >::Config) -> Self { -// Self { -// config, -// _marker: PhantomData, -// } -// } -// -// fn configure( -// meta: &mut ConstraintSystem, -// advice: [Column; 3], -// instance: Column, -// ) -> >::Config { -// meta.enable_equality(instance); -// for column in &advice { -// meta.enable_equality(*column); -// } -// let s = meta.selector(); -// -// // Define our multiplication gate! -// meta.create_gate("add", |meta| { -// let lhs = meta.query_advice(advice[0], Rotation::cur()); -// let rhs = meta.query_advice(advice[1], Rotation::cur()); -// let out = meta.query_advice(advice[2], Rotation::cur()); -// let s_add = meta.query_selector(s); -// -// vec![s_add * (lhs + rhs - out)] -// }); -// -// FieldConfig { -// advice, -// instance, -// s, -// } -// } -// } -// // ANCHOR_END: chip-config -// -// // ANCHOR: chip-impl -// impl Chip for MultChip { -// type Config = FieldConfig; -// type Loaded = (); -// -// fn config(&self) -> &Self::Config { -// &self.config -// } -// -// fn loaded(&self) -> &Self::Loaded { -// &() -// } -// } -// // ANCHOR_END: chip-impl -// -// // ANCHOR: chip-impl -// impl Chip for AddChip { -// type Config = FieldConfig; -// type Loaded = (); -// -// fn config(&self) -> &Self::Config { -// &self.config -// } -// -// fn loaded(&self) -> &Self::Loaded { -// &() -// } -// } -// -// // ANCHOR: instructions-impl -// /// A variable representing a number. -// #[derive(Clone, Debug)] -// struct Number(AssignedCell); -// -// impl NumericInstructions for MultChip { -// type Num = Number; -// -// fn load_unblinded( -// &self, -// mut layouter: impl Layouter, -// values: &[Value], -// ) -> Result, Error> { -// let config = self.config(); -// -// layouter.assign_region( -// || "load unblinded", -// |mut region| { -// values -// .iter() -// .enumerate() -// .map(|(i, value)| { -// region -// .assign_advice(|| "unblinded input", config.advice[0], i, || *value) -// .map(Number) -// }) -// .collect() -// }, -// ) -// } -// -// fn add( -// &self, -// _: impl Layouter, -// _: &[Self::Num], -// _: &[Self::Num], -// ) -> Result, Error> { -// panic!("Not implemented") -// } -// -// fn mul( -// &self, -// mut layouter: impl Layouter, -// a: &[Self::Num], -// b: &[Self::Num], -// ) -> Result, Error> { -// let config = self.config(); -// assert_eq!(a.len(), b.len()); -// -// layouter.assign_region( -// || "mul", -// |mut region: Region<'_, F>| { -// a.iter() -// .zip(b.iter()) -// .enumerate() -// .map(|(i, (a, b))| { -// config.s.enable(&mut region, i)?; -// -// a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?; -// b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?; -// -// let value = a.0.value().copied() * b.0.value(); -// -// // Finally, we do the assignment to the output, returning a -// // variable to be used in another part of the circuit. -// region -// .assign_advice(|| "lhs * rhs", config.advice[2], i, || value) -// .map(Number) -// }) -// .collect() -// }, -// ) -// } -// -// fn expose_public( -// &self, -// mut layouter: impl Layouter, -// num: &Self::Num, -// row: usize, -// ) -> Result<(), Error> { -// let config = self.config(); -// -// layouter.constrain_instance(num.0.cell(), config.instance, row) -// } -// } -// // ANCHOR_END: instructions-impl -// -// impl NumericInstructions for AddChip { -// type Num = Number; -// -// fn load_unblinded( -// &self, -// mut layouter: impl Layouter, -// values: &[Value], -// ) -> Result, Error> { -// let config = self.config(); -// -// layouter.assign_region( -// || "load unblinded", -// |mut region| { -// values -// .iter() -// .enumerate() -// .map(|(i, value)| { -// region -// .assign_advice(|| "unblinded input", config.advice[0], i, || *value) -// .map(Number) -// }) -// .collect() -// }, -// ) -// } -// -// fn mul( -// &self, -// _: impl Layouter, -// _: &[Self::Num], -// _: &[Self::Num], -// ) -> Result, Error> { -// panic!("Not implemented") -// } -// -// fn add( -// &self, -// mut layouter: impl Layouter, -// a: &[Self::Num], -// b: &[Self::Num], -// ) -> Result, Error> { -// let config = self.config(); -// assert_eq!(a.len(), b.len()); -// -// layouter.assign_region( -// || "add", -// |mut region: Region<'_, F>| { -// a.iter() -// .zip(b.iter()) -// .enumerate() -// .map(|(i, (a, b))| { -// config.s.enable(&mut region, i)?; -// -// a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?; -// b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?; -// -// let value = a.0.value().copied() + b.0.value(); -// -// // Finally, we do the assignment to the output, returning a -// // variable to be used in another part of the circuit. -// region -// .assign_advice(|| "lhs + rhs", config.advice[2], i, || value) -// .map(Number) -// }) -// .collect() -// }, -// ) -// } -// -// fn expose_public( -// &self, -// mut layouter: impl Layouter, -// num: &Self::Num, -// row: usize, -// ) -> Result<(), Error> { -// let config = self.config(); -// -// layouter.constrain_instance(num.0.cell(), config.instance, row) -// } -// } -// -// #[derive(Default)] -// struct MulCircuit { -// a: Vec>, -// b: Vec>, -// } -// -// impl Circuit for MulCircuit { -// // Since we are using a single chip for everything, we can just reuse its config. -// type Config = FieldConfig; -// type FloorPlanner = SimpleFloorPlanner; -// #[cfg(feature = "circuit-params")] -// type Params = (); -// -// fn without_witnesses(&self) -> Self { -// Self::default() -// } -// -// fn configure(meta: &mut ConstraintSystem) -> Self::Config { -// // We create the three advice columns that FieldChip uses for I/O. -// let advice = [ -// meta.unblinded_advice_column(), -// meta.unblinded_advice_column(), -// meta.unblinded_advice_column(), -// ]; -// -// // We also need an instance column to store public inputs. -// let instance = meta.instance_column(); -// -// MultChip::configure(meta, advice, instance) -// } -// -// fn synthesize( -// &self, -// config: Self::Config, -// mut layouter: impl Layouter, -// ) -> Result<(), Error> { -// let field_chip = MultChip::::construct(config); -// -// // Load our unblinded values into the circuit. -// let a = field_chip.load_unblinded(layouter.namespace(|| "load a"), &self.a)?; -// let b = field_chip.load_unblinded(layouter.namespace(|| "load b"), &self.b)?; -// -// let ab = field_chip.mul(layouter.namespace(|| "a * b"), &a, &b)?; -// -// for (i, c) in ab.iter().enumerate() { -// // Expose the result as a public input to the circuit. -// field_chip.expose_public(layouter.namespace(|| "expose c"), c, i)?; -// } -// Ok(()) -// } -// } -// // ANCHOR_END: circuit -// -// #[derive(Default)] -// struct AddCircuit { -// a: Vec>, -// b: Vec>, -// } -// -// impl Circuit for AddCircuit { -// // Since we are using a single chip for everything, we can just reuse its config. -// type Config = FieldConfig; -// type FloorPlanner = SimpleFloorPlanner; -// #[cfg(feature = "circuit-params")] -// type Params = (); -// -// fn without_witnesses(&self) -> Self { -// Self::default() -// } -// -// fn configure(meta: &mut ConstraintSystem) -> Self::Config { -// // We create the three advice columns that FieldChip uses for I/O. -// let advice = [ -// meta.unblinded_advice_column(), -// meta.unblinded_advice_column(), -// meta.unblinded_advice_column(), -// ]; -// -// // We also need an instance column to store public inputs. -// let instance = meta.instance_column(); -// -// AddChip::configure(meta, advice, instance) -// } -// -// fn synthesize( -// &self, -// config: Self::Config, -// mut layouter: impl Layouter, -// ) -> Result<(), Error> { -// let field_chip = AddChip::::construct(config); -// -// // Load our unblinded values into the circuit. -// let a = field_chip.load_unblinded(layouter.namespace(|| "load a"), &self.a)?; -// let b = field_chip.load_unblinded(layouter.namespace(|| "load b"), &self.b)?; -// -// let ab = field_chip.add(layouter.namespace(|| "a + b"), &a, &b)?; -// -// for (i, c) in ab.iter().enumerate() { -// // Expose the result as a public input to the circuit. -// field_chip.expose_public(layouter.namespace(|| "expose c"), c, i)?; -// } -// Ok(()) -// } -// } -// // ANCHOR_END: circuit -// -// fn test_prover( -// k: u32, -// circuit: impl Circuit, -// expected: bool, -// instances: Vec, -// ) -> Vec -// where -// E::G1Affine: SerdeCurveAffine::Fr, CurveExt = ::G1>, -// E::G1: CurveExt, -// E::G2Affine: SerdeCurveAffine, -// E::Fr: FromUniformBytes<64> + WithSmallOrderMulGroup<3>, -// { -// let params = ParamsKZG::::new(k); -// let vk = keygen_vk(¶ms, &circuit).unwrap(); -// let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); -// -// let proof = { -// let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); -// -// create_proof::, ProverGWC, _, _, _, _>( -// ¶ms, -// &pk, -// &[circuit], -// &[&[&instances]], -// OsRng, -// &mut transcript, -// ) -// .expect("proof generation should not fail"); -// -// transcript.finalize() -// }; -// -// let accepted = { -// let strategy = AccumulatorStrategy::new(¶ms); -// let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); -// -// verify_proof::, VerifierGWC, _, _, AccumulatorStrategy>( -// ¶ms, -// pk.get_vk(), -// strategy, -// &[&[&instances]], -// &mut transcript, -// ) -// .map(|strategy| { -// as VerificationStrategy< -// KZGCommitmentScheme, -// VerifierGWC, -// >>::finalize(strategy) -// }) -// .unwrap_or_default() -// }; -// -// assert_eq!(accepted, expected); -// -// proof -// } -// -// fn main() { -// use halo2curves::bn256::Fr; -// const N: usize = 10; -// // ANCHOR: test-circuit -// // The number of rows in our circuit cannot exceed 2^k. Since our example -// // circuit is very small, we can pick a very small value here. -// let k = 7; -// -// // Prepare the inputs to the circuit! -// let a = [Fr::from(2); N]; -// let b = [Fr::from(3); N]; -// let c_mul: Vec = a.iter().zip(b).map(|(&a, b)| a * b).collect(); -// let c_add: Vec = a.iter().zip(b).map(|(&a, b)| a + b).collect(); -// -// // Instantiate the mul circuit with the inputs. -// let mul_circuit = MulCircuit { -// a: a.iter().map(|&x| Value::known(x)).collect(), -// b: b.iter().map(|&x| Value::known(x)).collect(), -// }; -// -// // Instantiate the add circuit with the inputs. -// let add_circuit = AddCircuit { -// a: a.iter().map(|&x| Value::known(x)).collect(), -// b: b.iter().map(|&x| Value::known(x)).collect(), -// }; -// -// // the commitments will be the first columns of the proof transcript so we can compare them easily -// let proof_1 = test_prover::(k, mul_circuit, true, c_mul); -// // the commitments will be the first columns of the proof transcript so we can compare them easily -// let proof_2 = test_prover::(k, add_circuit, true, c_add); -// -// // the commitments will be the first columns of the proof transcript so we can compare them easily -// // here we compare the first 10 bytes of the commitments -// println!( -// "Commitments are equal: {:?}", -// proof_1[..10] == proof_2[..10] -// ); -// // ANCHOR_END: test-circuit -// } +use blake2b_simd::State; +/// Here we construct two circuits one for adding two vectors and one for multiplying and we check that their transcripts have the same inputs +/// by way of the unblinded inputs. +/// This is a simple example of how to use unblinded inputs to match up circuits that might be proved on different host machines. +use std::marker::PhantomData; + +use ff::{FromUniformBytes, WithSmallOrderMulGroup}; +use halo2_proofs::poly::kzg::{params::ParamsKZG, KZGCommitmentScheme}; +use halo2_proofs::transcript::{CircuitTranscript, Hashable, Sampleable, Transcript}; +use halo2_proofs::{ + arithmetic::Field, + circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, + plonk::*, + poly::Rotation, +}; +use halo2curves::pairing::{Engine, MultiMillerLoop}; +use halo2curves::serde::SerdeObject; +use rand_core::OsRng; + +// ANCHOR: instructions +trait NumericInstructions: Chip { + /// Variable representing a number. + type Num; + + /// Loads a number into the circuit as an unblinded input this can be matched to inputs in other circuits ! + fn load_unblinded( + &self, + layouter: impl Layouter, + a: &[Value], + ) -> Result, Error>; + + /// Returns `c = a * b`. The caller is responsible for ensuring that `a.len() == b.len()`. + fn mul( + &self, + layouter: impl Layouter, + a: &[Self::Num], + b: &[Self::Num], + ) -> Result, Error>; + + /// Returns `c = a + b`. The caller is responsible for ensuring that `a.len() == b.len()`. + fn add( + &self, + layouter: impl Layouter, + a: &[Self::Num], + b: &[Self::Num], + ) -> Result, Error>; + + /// Exposes a number as a public input to the circuit. + fn expose_public( + &self, + layouter: impl Layouter, + num: &Self::Num, + row: usize, + ) -> Result<(), Error>; +} +// ANCHOR_END: instructions + +// ANCHOR: chip +/// The chip that will implement our instructions! Chips store their own +/// config, as well as type markers if necessary. +struct MultChip { + config: FieldConfig, + _marker: PhantomData, +} + +// ANCHOR: chip +/// The chip that will implement our instructions! Chips store their own +/// config, as well as type markers if necessary. +struct AddChip { + config: FieldConfig, + _marker: PhantomData, +} +// ANCHOR_END: chip + +// ANCHOR: chip-config +/// Chip state is stored in a config struct. This is generated by the chip +/// during configuration, and then stored inside the chip. +#[derive(Clone, Debug)] +struct FieldConfig { + advice: [Column; 3], + instance: Column, + s: Selector, +} + +impl MultChip { + fn construct(config: >::Config) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + fn configure( + meta: &mut ConstraintSystem, + advice: [Column; 3], + instance: Column, + ) -> >::Config { + meta.enable_equality(instance); + for column in &advice { + meta.enable_equality(*column); + } + let s = meta.selector(); + + // Define our multiplication gate! + meta.create_gate("mul", |meta| { + let lhs = meta.query_advice(advice[0], Rotation::cur()); + let rhs = meta.query_advice(advice[1], Rotation::cur()); + let out = meta.query_advice(advice[2], Rotation::cur()); + let s_mul = meta.query_selector(s); + + vec![s_mul * (lhs * rhs - out)] + }); + + FieldConfig { + advice, + instance, + s, + } + } +} + +impl AddChip { + fn construct(config: >::Config) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + fn configure( + meta: &mut ConstraintSystem, + advice: [Column; 3], + instance: Column, + ) -> >::Config { + meta.enable_equality(instance); + for column in &advice { + meta.enable_equality(*column); + } + let s = meta.selector(); + + // Define our multiplication gate! + meta.create_gate("add", |meta| { + let lhs = meta.query_advice(advice[0], Rotation::cur()); + let rhs = meta.query_advice(advice[1], Rotation::cur()); + let out = meta.query_advice(advice[2], Rotation::cur()); + let s_add = meta.query_selector(s); + + vec![s_add * (lhs + rhs - out)] + }); + + FieldConfig { + advice, + instance, + s, + } + } +} +// ANCHOR_END: chip-config + +// ANCHOR: chip-impl +impl Chip for MultChip { + type Config = FieldConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} +// ANCHOR_END: chip-impl + +// ANCHOR: chip-impl +impl Chip for AddChip { + type Config = FieldConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + +// ANCHOR: instructions-impl +/// A variable representing a number. +#[derive(Clone, Debug)] +struct Number(AssignedCell); + +impl NumericInstructions for MultChip { + type Num = Number; + + fn load_unblinded( + &self, + mut layouter: impl Layouter, + values: &[Value], + ) -> Result, Error> { + let config = self.config(); + + layouter.assign_region( + || "load unblinded", + |mut region| { + values + .iter() + .enumerate() + .map(|(i, value)| { + region + .assign_advice(|| "unblinded input", config.advice[0], i, || *value) + .map(Number) + }) + .collect() + }, + ) + } + + fn add( + &self, + _: impl Layouter, + _: &[Self::Num], + _: &[Self::Num], + ) -> Result, Error> { + panic!("Not implemented") + } + + fn mul( + &self, + mut layouter: impl Layouter, + a: &[Self::Num], + b: &[Self::Num], + ) -> Result, Error> { + let config = self.config(); + assert_eq!(a.len(), b.len()); + + layouter.assign_region( + || "mul", + |mut region: Region<'_, F>| { + a.iter() + .zip(b.iter()) + .enumerate() + .map(|(i, (a, b))| { + config.s.enable(&mut region, i)?; + + a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?; + b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?; + + let value = a.0.value().copied() * b.0.value(); + + // Finally, we do the assignment to the output, returning a + // variable to be used in another part of the circuit. + region + .assign_advice(|| "lhs * rhs", config.advice[2], i, || value) + .map(Number) + }) + .collect() + }, + ) + } + + fn expose_public( + &self, + mut layouter: impl Layouter, + num: &Self::Num, + row: usize, + ) -> Result<(), Error> { + let config = self.config(); + + layouter.constrain_instance(num.0.cell(), config.instance, row) + } +} +// ANCHOR_END: instructions-impl + +impl NumericInstructions for AddChip { + type Num = Number; + + fn load_unblinded( + &self, + mut layouter: impl Layouter, + values: &[Value], + ) -> Result, Error> { + let config = self.config(); + + layouter.assign_region( + || "load unblinded", + |mut region| { + values + .iter() + .enumerate() + .map(|(i, value)| { + region + .assign_advice(|| "unblinded input", config.advice[0], i, || *value) + .map(Number) + }) + .collect() + }, + ) + } + + fn mul( + &self, + _: impl Layouter, + _: &[Self::Num], + _: &[Self::Num], + ) -> Result, Error> { + panic!("Not implemented") + } + + fn add( + &self, + mut layouter: impl Layouter, + a: &[Self::Num], + b: &[Self::Num], + ) -> Result, Error> { + let config = self.config(); + assert_eq!(a.len(), b.len()); + + layouter.assign_region( + || "add", + |mut region: Region<'_, F>| { + a.iter() + .zip(b.iter()) + .enumerate() + .map(|(i, (a, b))| { + config.s.enable(&mut region, i)?; + + a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?; + b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?; + + let value = a.0.value().copied() + b.0.value(); + + // Finally, we do the assignment to the output, returning a + // variable to be used in another part of the circuit. + region + .assign_advice(|| "lhs + rhs", config.advice[2], i, || value) + .map(Number) + }) + .collect() + }, + ) + } + + fn expose_public( + &self, + mut layouter: impl Layouter, + num: &Self::Num, + row: usize, + ) -> Result<(), Error> { + let config = self.config(); + + layouter.constrain_instance(num.0.cell(), config.instance, row) + } +} + +#[derive(Default)] +struct MulCircuit { + a: Vec>, + b: Vec>, +} + +impl Circuit for MulCircuit { + // Since we are using a single chip for everything, we can just reuse its config. + type Config = FieldConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + // We create the three advice columns that FieldChip uses for I/O. + let advice = [ + meta.unblinded_advice_column(), + meta.unblinded_advice_column(), + meta.unblinded_advice_column(), + ]; + + // We also need an instance column to store public inputs. + let instance = meta.instance_column(); + + MultChip::configure(meta, advice, instance) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let field_chip = MultChip::::construct(config); + + // Load our unblinded values into the circuit. + let a = field_chip.load_unblinded(layouter.namespace(|| "load a"), &self.a)?; + let b = field_chip.load_unblinded(layouter.namespace(|| "load b"), &self.b)?; + + let ab = field_chip.mul(layouter.namespace(|| "a * b"), &a, &b)?; + + for (i, c) in ab.iter().enumerate() { + // Expose the result as a public input to the circuit. + field_chip.expose_public(layouter.namespace(|| "expose c"), c, i)?; + } + Ok(()) + } +} +// ANCHOR_END: circuit + +#[derive(Default)] +struct AddCircuit { + a: Vec>, + b: Vec>, +} + +impl Circuit for AddCircuit { + // Since we are using a single chip for everything, we can just reuse its config. + type Config = FieldConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + // We create the three advice columns that FieldChip uses for I/O. + let advice = [ + meta.unblinded_advice_column(), + meta.unblinded_advice_column(), + meta.unblinded_advice_column(), + ]; + + // We also need an instance column to store public inputs. + let instance = meta.instance_column(); + + AddChip::configure(meta, advice, instance) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let field_chip = AddChip::::construct(config); + + // Load our unblinded values into the circuit. + let a = field_chip.load_unblinded(layouter.namespace(|| "load a"), &self.a)?; + let b = field_chip.load_unblinded(layouter.namespace(|| "load b"), &self.b)?; + + let ab = field_chip.add(layouter.namespace(|| "a + b"), &a, &b)?; + + for (i, c) in ab.iter().enumerate() { + // Expose the result as a public input to the circuit. + field_chip.expose_public(layouter.namespace(|| "expose c"), c, i)?; + } + Ok(()) + } +} +// ANCHOR_END: circuit + +fn test_prover( + k: u32, + circuit: impl Circuit, + expected: bool, + instances: Vec, +) -> Vec +where + E::G1Affine: Default + SerdeObject + Hashable, + E::Fr: WithSmallOrderMulGroup<3> + + FromUniformBytes<64> + + SerdeObject + + Sampleable + + Hashable + + Ord, +{ + let params = ParamsKZG::::new(k); + let vk = keygen_vk(¶ms, &circuit).unwrap(); + let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); + + let proof = { + let mut transcript = CircuitTranscript::::init(); + + create_proof::, _, _>( + ¶ms, + &pk, + &[circuit], + &[&[&instances]], + OsRng, + &mut transcript, + ) + .expect("proof generation should not fail"); + + transcript.finalize() + }; + + let accepted = { + let mut transcript = CircuitTranscript::::parse(&proof[..]); + + verify_proof::, _>( + ¶ms, + pk.get_vk(), + &[&[&instances]], + &mut transcript, + ) + }; + + assert_eq!(accepted.is_ok(), expected); + + proof +} + +fn main() { + use halo2curves::bn256::Fr; + const N: usize = 10; + // ANCHOR: test-circuit + // The number of rows in our circuit cannot exceed 2^k. Since our example + // circuit is very small, we can pick a very small value here. + let k = 7; + + // Prepare the inputs to the circuit! + let a = [Fr::from(2); N]; + let b = [Fr::from(3); N]; + let c_mul: Vec = a.iter().zip(b).map(|(&a, b)| a * b).collect(); + let c_add: Vec = a.iter().zip(b).map(|(&a, b)| a + b).collect(); + + // Instantiate the mul circuit with the inputs. + let mul_circuit = MulCircuit { + a: a.iter().map(|&x| Value::known(x)).collect(), + b: b.iter().map(|&x| Value::known(x)).collect(), + }; + + // Instantiate the add circuit with the inputs. + let add_circuit = AddCircuit { + a: a.iter().map(|&x| Value::known(x)).collect(), + b: b.iter().map(|&x| Value::known(x)).collect(), + }; + + // the commitments will be the first columns of the proof transcript so we can compare them easily + let proof_1 = test_prover::(k, mul_circuit, true, c_mul); + // the commitments will be the first columns of the proof transcript so we can compare them easily + let proof_2 = test_prover::(k, add_circuit, true, c_add); + + // the commitments will be the first columns of the proof transcript so we can compare them easily + // here we compare the first 10 bytes of the commitments + println!( + "Commitments are equal: {:?}", + proof_1[..10] == proof_2[..10] + ); + // ANCHOR_END: test-circuit +} diff --git a/src/plonk/keygen.rs b/src/plonk/keygen.rs index 7c66f0ede6..4ff96c645c 100644 --- a/src/plonk/keygen.rs +++ b/src/plonk/keygen.rs @@ -16,12 +16,7 @@ use crate::circuit::Value; use crate::poly::batch_invert_rational; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; use crate::rational::Rational; -use crate::{ - arithmetic::parallelize, - // circuit::Value, - poly::EvaluationDomain, -}; -// use crate::poly::kzg::commitment::{ParamsKZG, ParamsVerifierKZG}; +use crate::{arithmetic::parallelize, poly::EvaluationDomain}; pub(crate) fn create_domain( k: u32, diff --git a/src/plonk/vanishing/verifier.rs b/src/plonk/vanishing/verifier.rs index 687993303b..184ef0f1b2 100644 --- a/src/plonk/vanishing/verifier.rs +++ b/src/plonk/vanishing/verifier.rs @@ -89,7 +89,7 @@ impl> Evaluated { expressions: impl Iterator, y: F, xn: F, - ) -> Evaluated { + ) -> Result, Error> { let committed_h_eval = self .h_evals .iter() @@ -99,9 +99,11 @@ impl> Evaluated { let expected_h_eval = expressions.fold(F::ZERO, |h_eval, v| h_eval * &y + &v); let expected_h_eval = expected_h_eval * ((xn - F::ONE).invert().unwrap()); - assert_eq!(committed_h_eval, expected_h_eval); + if committed_h_eval != expected_h_eval { + return Err(Error::ConstraintSystemFailure); + } - self + Ok(self) } } diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 9378e9cb4e..417713cc8f 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -262,7 +262,7 @@ where )) }); - vanishing.verify(expressions, y, xn) + vanishing.verify(expressions, y, xn)? }; let queries = advice_commitments From c9171293c9a25616d7e266602ad2874ed2ddc80d Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Thu, 12 Dec 2024 09:22:40 +0100 Subject: [PATCH 10/20] Update to halo2curves 0.7.0 --- Cargo.toml | 13 +- benches/fft.rs | 26 --- benches/plonk.rs | 28 ++-- examples/shuffle.rs | 5 +- examples/vector-mul.rs | 2 +- examples/vector-ops-unblinded.rs | 4 +- rust-toolchain | 2 +- src/arithmetic.rs | 278 +------------------------------ src/helpers.rs | 2 +- src/multicore.rs | 7 +- src/plonk.rs | 4 +- src/poly/domain.rs | 3 +- src/poly/kzg/mod.rs | 13 +- src/poly/kzg/msm.rs | 15 +- src/poly/kzg/params.rs | 8 +- 15 files changed, 64 insertions(+), 346 deletions(-) delete mode 100644 benches/fft.rs diff --git a/Cargo.toml b/Cargo.toml index 1e865a63ae..a12a604279 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,13 +8,12 @@ authors = [ "Jack Grigg ", ] edition = "2021" -rust-version = "1.66.0" +rust-version = "1.76.0" description = """ -Fast PLONK-based zero-knowledge proving system with no trusted setup +Fast PLONK-based zero-knowledge proving system """ license = "MIT OR Apache-2.0" -repository = "https://github.com/zcash/halo2" -documentation = "https://docs.rs/halo2_proofs" +repository = "https://github.com/input-output-hk/halo2" readme = "README.md" categories = ["cryptography"] keywords = ["halo", "proofs", "zkp", "zkSNARKs"] @@ -39,15 +38,11 @@ harness = false name = "dev_lookup" harness = false -[[bench]] -name = "fft" -harness = false - [dependencies] backtrace = { version = "0.3", optional = true } ff = "0.13" group = "0.13" -halo2curves = { version = "0.6.0", default-features = false } +halo2curves = { version = "0.7.0", default-features = false } rand_core = { version = "0.6", default-features = false } tracing = "0.1" blake2b_simd = "1" # MSRV 1.66.0 diff --git a/benches/fft.rs b/benches/fft.rs deleted file mode 100644 index 0de72a0380..0000000000 --- a/benches/fft.rs +++ /dev/null @@ -1,26 +0,0 @@ -#[macro_use] -extern crate criterion; - -use crate::arithmetic::best_fft; -use group::ff::Field; -use halo2_proofs::*; -use halo2curves::pasta::Fp; - -use criterion::{BenchmarkId, Criterion}; -use rand_core::OsRng; - -fn criterion_benchmark(c: &mut Criterion) { - let mut group = c.benchmark_group("fft"); - for k in 3..19 { - group.bench_function(BenchmarkId::new("k", k), |b| { - let mut a = (0..(1 << k)).map(|_| Fp::random(OsRng)).collect::>(); - let omega = Fp::random(OsRng); // would be weird if this mattered - b.iter(|| { - best_fft(&mut a, omega, k as u32); - }); - }); - } -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/benches/plonk.rs b/benches/plonk.rs index 8fc574cdf4..63a18560fe 100644 --- a/benches/plonk.rs +++ b/benches/plonk.rs @@ -4,15 +4,14 @@ extern crate criterion; use group::ff::Field; use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; use halo2_proofs::plonk::*; -use halo2_proofs::poly::{Rotation}; +use halo2_proofs::poly::Rotation; use halo2curves::bn256; use rand_core::OsRng; - use std::marker::PhantomData; use criterion::{BenchmarkId, Criterion}; -use halo2_proofs::poly::kzg::{KZGCommitmentScheme, params::ParamsKZG}; +use halo2_proofs::poly::kzg::{params::ParamsKZG, KZGCommitmentScheme}; use halo2_proofs::rational::Rational; use halo2_proofs::transcript::{CircuitTranscript, Transcript}; @@ -252,7 +251,12 @@ fn criterion_benchmark(c: &mut Criterion) { } } - fn keygen(k: u32) -> (ParamsKZG, ProvingKey>) { + fn keygen( + k: u32, + ) -> ( + ParamsKZG, + ProvingKey>, + ) { let params: ParamsKZG = ParamsKZG::new(k); let empty_circuit: MyCircuit = MyCircuit { a: Value::unknown(), @@ -295,13 +299,15 @@ fn criterion_benchmark(c: &mut Criterion) { proof: &[u8], ) { let mut transcript = CircuitTranscript::parse(proof); - assert!(verify_proof::, _>( - params, - vk, - &[&[]], - &mut transcript - ) - .is_ok()); + assert!( + verify_proof::, _>( + params, + vk, + &[&[]], + &mut transcript + ) + .is_ok() + ); } let k_range = 8..=16; diff --git a/examples/shuffle.rs b/examples/shuffle.rs index 8742ccc267..a6f0f21248 100644 --- a/examples/shuffle.rs +++ b/examples/shuffle.rs @@ -10,7 +10,7 @@ use halo2_proofs::{ }; use halo2curves::pairing::MultiMillerLoop; use halo2curves::serde::SerdeObject; -use halo2curves::{bn256, pairing::Engine}; +use halo2curves::{bn256, pairing::Engine, CurveAffine}; use rand_core::{OsRng, RngCore}; use std::iter; @@ -267,7 +267,8 @@ fn test_prover( circuit: MyCircuit, expected: bool, ) where - E::G1Affine: Default + SerdeObject + Hashable, + E::G1Affine: + Default + SerdeObject + Hashable + CurveAffine, E::Fr: WithSmallOrderMulGroup<3> + FromUniformBytes<64> + SerdeObject diff --git a/examples/vector-mul.rs b/examples/vector-mul.rs index 5a99f57bc2..01728fdf36 100644 --- a/examples/vector-mul.rs +++ b/examples/vector-mul.rs @@ -288,7 +288,7 @@ fn main() { // Prepare the private and public inputs to the circuit! let a = [Fp::from(2); N]; let b = [Fp::from(3); N]; - let c: Vec = a.iter().zip(b.iter()).map(|(&a, b)| a * b).collect(); + let c: Vec = a.iter().zip(b).map(|(&a, b)| a * b).collect(); // Instantiate the circuit with the private inputs. let circuit = MyCircuit { diff --git a/examples/vector-ops-unblinded.rs b/examples/vector-ops-unblinded.rs index c8f4f957d1..adac350c0c 100644 --- a/examples/vector-ops-unblinded.rs +++ b/examples/vector-ops-unblinded.rs @@ -15,6 +15,7 @@ use halo2_proofs::{ }; use halo2curves::pairing::{Engine, MultiMillerLoop}; use halo2curves::serde::SerdeObject; +use halo2curves::CurveAffine; use rand_core::OsRng; // ANCHOR: instructions @@ -467,7 +468,8 @@ fn test_prover( instances: Vec, ) -> Vec where - E::G1Affine: Default + SerdeObject + Hashable, + E::G1Affine: + Default + SerdeObject + Hashable + CurveAffine, E::Fr: WithSmallOrderMulGroup<3> + FromUniformBytes<64> + SerdeObject diff --git a/rust-toolchain b/rust-toolchain index 65ee095984..32a6ce3c71 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.67.0 +1.76.0 diff --git a/src/arithmetic.rs b/src/arithmetic.rs index 6539e4876e..f956e7b3d8 100644 --- a/src/arithmetic.rs +++ b/src/arithmetic.rs @@ -6,10 +6,11 @@ pub use ff::Field; use group::prime::PrimeCurveAffine; use group::{ ff::{BatchInvert, PrimeField}, - Curve, Group, GroupOpsOwned, ScalarMulOwned, + Curve, GroupOpsOwned, ScalarMulOwned, }; use std::fmt::Debug; +use halo2curves::fft::best_fft; pub use halo2curves::{CurveAffine, CurveExt}; /// This represents an element of a group with basic operations that can be @@ -27,269 +28,6 @@ where { } -fn multiexp_serial(coeffs: &[C::Scalar], bases: &[C], acc: &mut C::Curve) { - let coeffs: Vec<_> = coeffs.iter().map(|a| a.to_repr()).collect(); - - let c = if bases.len() < 4 { - 1 - } else if bases.len() < 32 { - 3 - } else { - (f64::from(bases.len() as u32)).ln().ceil() as usize - }; - - fn get_at(segment: usize, c: usize, bytes: &F::Repr) -> usize { - let skip_bits = segment * c; - let skip_bytes = skip_bits / 8; - - if skip_bytes >= (F::NUM_BITS as usize + 7) / 8 { - return 0; - } - - let mut v = [0; 8]; - for (v, o) in v.iter_mut().zip(bytes.as_ref()[skip_bytes..].iter()) { - *v = *o; - } - - let mut tmp = u64::from_le_bytes(v); - tmp >>= skip_bits - (skip_bytes * 8); - tmp %= 1 << c; - - tmp as usize - } - - let segments = (C::Scalar::NUM_BITS as usize / c) + 1; - - for current_segment in (0..segments).rev() { - for _ in 0..c { - *acc = acc.double(); - } - - #[derive(Clone, Copy)] - enum Bucket { - None, - Affine(C), - Projective(C::Curve), - } - - impl Bucket { - fn add_assign(&mut self, other: &C) { - *self = match *self { - Bucket::None => Bucket::Affine(*other), - Bucket::Affine(a) => Bucket::Projective(a.to_curve() + other.to_curve()), - Bucket::Projective(mut a) => { - a += *other; - Bucket::Projective(a) - } - } - } - - fn add(self, mut other: C::Curve) -> C::Curve { - match self { - Bucket::None => other, - Bucket::Affine(a) => { - other += a; - other - } - Bucket::Projective(a) => other + &a, - } - } - } - - let mut buckets: Vec> = vec![Bucket::None; (1 << c) - 1]; - - for (coeff, base) in coeffs.iter().zip(bases.iter()) { - let coeff = get_at::(current_segment, c, coeff); - if coeff != 0 { - buckets[coeff - 1].add_assign(base); - } - } - - // Summation by parts - // e.g. 3a + 2b + 1c = a + - // (a) + b + - // ((a) + b) + c - let mut running_sum = C::Curve::identity(); - for exp in buckets.into_iter().rev() { - running_sum = exp.add(running_sum); - *acc += &running_sum; - } - } -} - -/// Performs a small multi-exponentiation operation. -/// Uses the double-and-add algorithm with doublings shared across points. -pub fn small_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { - let coeffs: Vec<_> = coeffs.iter().map(|a| a.to_repr()).collect(); - let mut acc = C::Curve::identity(); - - // for byte idx - for byte_idx in (0..((C::Scalar::NUM_BITS as usize + 7) / 8)).rev() { - // for bit idx - for bit_idx in (0..8).rev() { - acc = acc.double(); - // for each coeff - for coeff_idx in 0..coeffs.len() { - let byte = coeffs[coeff_idx].as_ref()[byte_idx]; - if ((byte >> bit_idx) & 1) != 0 { - acc += bases[coeff_idx]; - } - } - } - } - - acc -} - -/// Performs a multi-exponentiation operation. -/// -/// This function will panic if coeffs and bases have a different length. -/// -/// This will use multithreading if beneficial. -pub fn best_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { - assert_eq!(coeffs.len(), bases.len()); - - let num_threads = multicore::current_num_threads(); - if coeffs.len() > num_threads { - let chunk = coeffs.len() / num_threads; - let num_chunks = coeffs.chunks(chunk).len(); - let mut results = vec![C::Curve::identity(); num_chunks]; - multicore::scope(|scope| { - let chunk = coeffs.len() / num_threads; - - for ((coeffs, bases), acc) in coeffs - .chunks(chunk) - .zip(bases.chunks(chunk)) - .zip(results.iter_mut()) - { - scope.spawn(move |_| { - multiexp_serial(coeffs, bases, acc); - }); - } - }); - results.iter().fold(C::Curve::identity(), |a, b| a + b) - } else { - let mut acc = C::Curve::identity(); - multiexp_serial(coeffs, bases, &mut acc); - acc - } -} - -/// Performs a radix-$2$ Fast-Fourier Transformation (FFT) on a vector of size -/// $n = 2^k$, when provided `log_n` = $k$ and an element of multiplicative -/// order $n$ called `omega` ($\omega$). The result is that the vector `a`, when -/// interpreted as the coefficients of a polynomial of degree $n - 1$, is -/// transformed into the evaluations of this polynomial at each of the $n$ -/// distinct powers of $\omega$. This transformation is invertible by providing -/// $\omega^{-1}$ in place of $\omega$ and dividing each resulting field element -/// by $n$. -/// -/// This will use multithreading if beneficial. -pub fn best_fft>(a: &mut [G], omega: Scalar, log_n: u32) { - fn bitreverse(mut n: usize, l: usize) -> usize { - let mut r = 0; - for _ in 0..l { - r = (r << 1) | (n & 1); - n >>= 1; - } - r - } - - let threads = multicore::current_num_threads(); - let log_threads = log2_floor(threads); - let n = a.len(); - assert_eq!(n, 1 << log_n); - - for k in 0..n { - let rk = bitreverse(k, log_n as usize); - if k < rk { - a.swap(rk, k); - } - } - - // precompute twiddle factors - let twiddles: Vec<_> = (0..(n / 2)) - .scan(Scalar::ONE, |w, _| { - let tw = *w; - *w *= ω - Some(tw) - }) - .collect(); - - if log_n <= log_threads { - let mut chunk = 2_usize; - let mut twiddle_chunk = n / 2; - for _ in 0..log_n { - a.chunks_mut(chunk).for_each(|coeffs| { - let (left, right) = coeffs.split_at_mut(chunk / 2); - - // case when twiddle factor is one - let (a, left) = left.split_at_mut(1); - let (b, right) = right.split_at_mut(1); - let t = b[0]; - b[0] = a[0]; - a[0] += &t; - b[0] -= &t; - - left.iter_mut() - .zip(right.iter_mut()) - .enumerate() - .for_each(|(i, (a, b))| { - let mut t = *b; - t *= &twiddles[(i + 1) * twiddle_chunk]; - *b = *a; - *a += &t; - *b -= &t; - }); - }); - chunk *= 2; - twiddle_chunk /= 2; - } - } else { - recursive_butterfly_arithmetic(a, n, 1, &twiddles) - } -} - -/// This perform recursive butterfly arithmetic -pub fn recursive_butterfly_arithmetic>( - a: &mut [G], - n: usize, - twiddle_chunk: usize, - twiddles: &[Scalar], -) { - if n == 2 { - let t = a[1]; - a[1] = a[0]; - a[0] += &t; - a[1] -= &t; - } else { - let (left, right) = a.split_at_mut(n / 2); - multicore::join( - || recursive_butterfly_arithmetic(left, n / 2, twiddle_chunk * 2, twiddles), - || recursive_butterfly_arithmetic(right, n / 2, twiddle_chunk * 2, twiddles), - ); - - // case when twiddle factor is one - let (a, left) = left.split_at_mut(1); - let (b, right) = right.split_at_mut(1); - let t = b[0]; - b[0] = a[0]; - a[0] += &t; - b[0] -= &t; - - left.iter_mut() - .zip(right.iter_mut()) - .enumerate() - .for_each(|(i, (a, b))| { - let mut t = *b; - t *= &twiddles[(i + 1) * twiddle_chunk]; - *b = *a; - *a += &t; - *b -= &t; - }); - } -} - /// Convert coefficient bases group elements to lagrange basis by inverse FFT. pub fn g_to_lagrange(g_projective: Vec, k: u32) -> Vec { let n_inv = C::Scalar::TWO_INV.pow_vartime([k as u64, 0, 0, 0]); @@ -435,18 +173,6 @@ pub fn parallelize(v: &mu }); } -fn log2_floor(num: usize) -> u32 { - assert!(num > 0); - - let mut pow = 0; - - while (1 << (pow + 1)) <= num { - pow += 1; - } - - pow -} - /// Returns coefficients of an n - 1 degree polynomial given a set of n points /// and their evaluations. This function will panic if two values in `points` /// are the same. diff --git a/src/helpers.rs b/src/helpers.rs index 5abd0fbe07..dd4e006cc2 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -164,5 +164,5 @@ pub(crate) fn write_polynomial_slice( /// Gets the total number of bytes of a slice of polynomials, assuming all polynomials are the same length pub(crate) fn polynomial_slice_byte_length(slice: &[Polynomial]) -> usize { let field_len = F::default().to_repr().as_ref().len(); - 4 + slice.len() * (4 + field_len * slice.get(0).map(|poly| poly.len()).unwrap_or(0)) + 4 + slice.len() * (4 + field_len * slice.first().map(|poly| poly.len()).unwrap_or(0)) } diff --git a/src/multicore.rs b/src/multicore.rs index 4d30b91a8b..bdbbd714b7 100644 --- a/src/multicore.rs +++ b/src/multicore.rs @@ -1,10 +1,9 @@ pub use rayon::{ current_num_threads, iter::{IndexedParallelIterator, IntoParallelRefIterator}, - iter::{IntoParallelIterator, IntoParallelRefMutIterator, ParallelIterator}, - join, scope, + iter::{IntoParallelIterator, ParallelIterator}, + scope, slice::ParallelSliceMut, - Scope, }; pub trait TryFoldAndReduce { @@ -25,7 +24,7 @@ impl TryFoldAndReduce for I where T: Send + Sync, E: Send + Sync, - I: rayon::iter::ParallelIterator>, + I: ParallelIterator>, { fn try_fold_and_reduce( self, diff --git a/src/plonk.rs b/src/plonk.rs index 2e0df76315..f9b18f289e 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -192,7 +192,7 @@ impl, CS: PolynomialCommitmentScheme> VerifyingK + self.selectors.len() * (self .selectors - .get(0) + .first() .map(|selector| (selector.len() + 7) / 8) .unwrap_or(0)) } @@ -317,7 +317,7 @@ where fn bytes_length(&self, format: SerdeFormat) -> usize { let scalar_len = F::default().to_repr().as_ref().len(); self.vk.bytes_length(format) - + 12 + + 12 // bytes used for encoding the length(u32) of "l0", "l_last" & "l_active_row" polys + scalar_len * (self.l0.len() + self.l_last.len() + self.l_active_row.len()) + polynomial_slice_byte_length(&self.fixed_values) + polynomial_slice_byte_length(&self.fixed_polys) diff --git a/src/poly/domain.rs b/src/poly/domain.rs index ba105badd7..2acba8a308 100644 --- a/src/poly/domain.rs +++ b/src/poly/domain.rs @@ -1,13 +1,14 @@ //! Contains utilities for performing polynomial arithmetic over an evaluation //! domain that is of a suitable size for the application. -use crate::arithmetic::{best_fft, parallelize}; +use crate::arithmetic::parallelize; use super::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation}; use ff::WithSmallOrderMulGroup; use group::ff::{BatchInvert, Field}; use crate::rational::Rational; +use halo2curves::fft::best_fft; use std::marker::PhantomData; /// This structure contains precomputed constants and other details needed for diff --git a/src/poly/kzg/mod.rs b/src/poly/kzg/mod.rs index b2b685d59a..5e35c2095d 100644 --- a/src/poly/kzg/mod.rs +++ b/src/poly/kzg/mod.rs @@ -8,7 +8,7 @@ pub mod params; use std::fmt::Debug; -use crate::arithmetic::{best_multiexp, kate_division, powers, MSM}; +use crate::arithmetic::{kate_division, powers, MSM}; use crate::poly::kzg::msm::{DualMSM, MSMKZG}; use crate::poly::kzg::params::{ParamsKZG, ParamsVerifierKZG}; use crate::poly::query::Query; @@ -19,8 +19,10 @@ use crate::poly::commitment::PolynomialCommitmentScheme; use crate::transcript::{Hashable, Sampleable, Transcript}; use ff::Field; use group::prime::PrimeCurveAffine; +use halo2curves::msm::msm_best; use halo2curves::pairing::MultiMillerLoop; use halo2curves::serde::SerdeObject; +use halo2curves::CurveAffine; #[derive(Clone, Debug)] /// KZG verifier @@ -31,7 +33,7 @@ pub struct KZGCommitmentScheme { impl PolynomialCommitmentScheme for KZGCommitmentScheme where E::Fr: SerdeObject, - E::G1Affine: Default + SerdeObject, + E::G1Affine: Default + SerdeObject + CurveAffine, { type Parameters = ParamsKZG; type VerifierParameters = ParamsVerifierKZG; @@ -51,7 +53,7 @@ where let bases = ¶ms.g; let size = scalars.len(); assert!(bases.len() >= size); - best_multiexp(&scalars, &bases[0..size]).into() + msm_best(&scalars, &bases[0..size]).into() } fn open<'com, T: Transcript, I>( @@ -293,7 +295,10 @@ mod tests { fn create_proof(kzg_params: &ParamsKZG) -> Vec where E::Fr: WithSmallOrderMulGroup<3> + SerdeObject + Hashable + Sampleable, - E::G1Affine: SerdeObject + Hashable + Default, + E::G1Affine: SerdeObject + + Hashable + + Default + + CurveAffine, { let domain = EvaluationDomain::new(1, kzg_params.k); diff --git a/src/poly/kzg/msm.rs b/src/poly/kzg/msm.rs index afbd1be5a1..bcd48282f9 100644 --- a/src/poly/kzg/msm.rs +++ b/src/poly/kzg/msm.rs @@ -1,9 +1,10 @@ use std::fmt::Debug; use super::params::ParamsKZG; +use crate::arithmetic::parallelize; use crate::arithmetic::MSM; -use crate::arithmetic::{best_multiexp, parallelize}; use group::{Curve, Group}; +use halo2curves::msm::msm_best; use halo2curves::{ pairing::{Engine, MillerLoopResult, MultiMillerLoop}, CurveAffine, CurveExt, @@ -38,7 +39,10 @@ impl MSMKZG { } } -impl MSM for MSMKZG { +impl MSM for MSMKZG +where + E::G1Affine: CurveAffine, +{ fn append_term(&mut self, scalar: E::Fr, point: E::G1) { self.scalars.push(scalar); self.bases.push(point); @@ -67,7 +71,7 @@ impl MSM for MSMKZG { use group::prime::PrimeCurveAffine; let mut bases = vec![E::G1Affine::identity(); self.scalars.len()]; E::G1::batch_normalize(&self.bases, &mut bases); - best_multiexp(&self.scalars, &bases) + msm_best(&self.scalars, &bases) } fn bases(&self) -> Vec { @@ -97,7 +101,10 @@ pub struct DualMSM<'a, E: Engine> { pub(crate) right: MSMKZG, } -impl<'a, E: MultiMillerLoop + Debug> DualMSM<'a, E> { +impl<'a, E: MultiMillerLoop + Debug> DualMSM<'a, E> +where + E::G1Affine: CurveAffine, +{ /// Create a new two channel MSM accumulator instance pub fn new(params: &'a ParamsKZG) -> Self { Self { diff --git a/src/poly/kzg/params.rs b/src/poly/kzg/params.rs index 15991592e6..39c9db3947 100644 --- a/src/poly/kzg/params.rs +++ b/src/poly/kzg/params.rs @@ -1,4 +1,4 @@ -use crate::arithmetic::{best_multiexp, g_to_lagrange, parallelize}; +use crate::arithmetic::{g_to_lagrange, parallelize}; use crate::helpers::SerdeCurveAffine; use crate::poly::{LagrangeCoeff, Polynomial}; use crate::SerdeFormat; @@ -11,7 +11,9 @@ use std::fmt::Debug; use crate::poly::commitment::Params; use crate::poly::kzg::KZGCommitmentScheme; +use halo2curves::msm::msm_best; use halo2curves::serde::SerdeObject; +use halo2curves::CurveAffine; use std::io; use super::msm::MSMKZG; @@ -30,7 +32,7 @@ pub struct ParamsKZG { impl Params> for ParamsKZG where E::Fr: SerdeObject, - E::G1Affine: Default + SerdeObject, + E::G1Affine: Default + SerdeObject + CurveAffine, { fn k(&self) -> u32 { self.k @@ -46,7 +48,7 @@ where let bases = &self.g_lagrange; let size = scalars.len(); assert!(bases.len() >= size); - best_multiexp(&scalars, &bases[0..size]).into() + msm_best(&scalars, &bases[0..size]).into() } } From 961e7d22acdc142ac7d913db10cae63f6eb26205 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Thu, 12 Dec 2024 10:43:31 +0100 Subject: [PATCH 11/20] Apply tachyon optimizations https://github.com/privacy-scaling-explorations/halo2/pull/342 --- src/plonk/evaluation.rs | 38 +++++++++++++++++++-------------- src/plonk/permutation/prover.rs | 35 ++++-------------------------- src/plonk/prover.rs | 2 +- src/plonk/vanishing/prover.rs | 4 ++-- 4 files changed, 29 insertions(+), 50 deletions(-) diff --git a/src/plonk/evaluation.rs b/src/plonk/evaluation.rs index fe12c42e37..43aa533a04 100644 --- a/src/plonk/evaluation.rs +++ b/src/plonk/evaluation.rs @@ -357,8 +357,14 @@ impl> Evaluator { let chunk_len = pk.vk.cs.degree() - 2; let delta_start = beta * &pk.vk.domain.g_coset; - let first_set = sets.first().unwrap(); - let last_set = sets.last().unwrap(); + let permutation_product_cosets: Vec> = sets + .iter() + .map(|set| domain.coeff_to_extended(set.permutation_product_poly.clone())) + .collect(); + + let first_set_permutation_product_coset = + permutation_product_cosets.first().unwrap(); + let last_set_permutation_product_coset = permutation_product_cosets.last().unwrap(); // Permutation constraints parallelize(&mut values, |values, start| { @@ -371,22 +377,21 @@ impl> Evaluator { // Enforce only for the first set. // l_0(X) * (1 - z_0(X)) = 0 *value = *value * y - + ((one - first_set.permutation_product_coset[idx]) * l0[idx]); + + ((one - first_set_permutation_product_coset[idx]) * l0[idx]); // Enforce only for the last set. // l_last(X) * (z_l(X)^2 - z_l(X)) = 0 *value = *value * y - + ((last_set.permutation_product_coset[idx] - * last_set.permutation_product_coset[idx] - - last_set.permutation_product_coset[idx]) + + ((last_set_permutation_product_coset[idx] + * last_set_permutation_product_coset[idx] + - last_set_permutation_product_coset[idx]) * l_last[idx]); // Except for the first set, enforce. // l_0(X) * (z_i(X) - z_{i-1}(\omega^(last) X)) = 0 - for (set_idx, set) in sets.iter().enumerate() { + for set_idx in 0..sets.len() { if set_idx != 0 { *value = *value * y - + ((set.permutation_product_coset[idx] - - permutation.sets[set_idx - 1].permutation_product_coset - [r_last]) + + ((permutation_product_cosets[set_idx][idx] + - permutation_product_cosets[set_idx - 1][r_last]) * l0[idx]); } } @@ -396,12 +401,13 @@ impl> Evaluator { // - z_i(X) \prod_j (p(X) + \delta^j \beta X + \gamma) // ) let mut current_delta = delta_start * beta_term; - for ((set, columns), cosets) in sets - .iter() - .zip(p.columns.chunks(chunk_len)) - .zip(pk.permutation.cosets.chunks(chunk_len)) + for ((permutation_product_coset, columns), cosets) in + permutation_product_cosets + .iter() + .zip(p.columns.chunks(chunk_len)) + .zip(pk.permutation.cosets.chunks(chunk_len)) { - let mut left = set.permutation_product_coset[r_next]; + let mut left = permutation_product_coset[r_next]; for (values, permutation) in columns .iter() .map(|&column| match column.column_type() { @@ -414,7 +420,7 @@ impl> Evaluator { left *= values[idx] + beta * permutation[idx] + gamma; } - let mut right = set.permutation_product_coset[idx]; + let mut right = permutation_product_coset[idx]; for values in columns.iter().map(|&column| match column.column_type() { Any::Advice(_) => &advice[column.index()], Any::Fixed => &fixed[column.index()], diff --git a/src/plonk/permutation/prover.rs b/src/plonk/permutation/prover.rs index cdff66c457..78b9ae1693 100644 --- a/src/plonk/permutation/prover.rs +++ b/src/plonk/permutation/prover.rs @@ -11,28 +11,19 @@ use crate::transcript::{Hashable, Transcript}; use crate::{ arithmetic::{eval_polynomial, parallelize}, plonk::{self, Error}, - poly::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, ProverQuery, Rotation}, + poly::{Coeff, LagrangeCoeff, Polynomial, ProverQuery, Rotation}, }; pub(crate) struct CommittedSet { pub(crate) permutation_product_poly: Polynomial, - pub(crate) permutation_product_coset: Polynomial, } pub(crate) struct Committed { pub(crate) sets: Vec>, } -pub struct ConstructedSet { - permutation_product_poly: Polynomial, -} - -pub(crate) struct Constructed { - sets: Vec>, -} - pub(crate) struct Evaluated { - constructed: Constructed, + constructed: Committed, } impl Argument { @@ -162,17 +153,13 @@ impl Argument { last_z = z[params.n() as usize - (blinding_factors + 1)]; let permutation_product_commitment = params.commit_lagrange(&z); - let z = domain.lagrange_to_coeff(z); - let permutation_product_poly = z.clone(); - - let permutation_product_coset = domain.coeff_to_extended(z.clone()); + let permutation_product_poly = domain.lagrange_to_coeff(z); // Hash the permutation product commitment transcript.write(&permutation_product_commitment)?; sets.push(CommittedSet { permutation_product_poly, - permutation_product_coset, }); } @@ -180,20 +167,6 @@ impl Argument { } } -impl Committed { - pub(in crate::plonk) fn construct(self) -> Constructed { - Constructed { - sets: self - .sets - .iter() - .map(|set| ConstructedSet { - permutation_product_poly: set.permutation_product_poly.clone(), - }) - .collect(), - } - } -} - impl super::ProvingKey { pub(in crate::plonk) fn open(&self, x: F) -> impl Iterator> + Clone { self.polys @@ -218,7 +191,7 @@ impl super::ProvingKey { } } -impl> Constructed { +impl> Committed { // TODO: I don't quite like the PCS in the function pub(in crate::plonk) fn evaluate>( self, diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index bf7cb13a36..07bf302768 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -520,7 +520,7 @@ where // Evaluate the permutations, if any, at omega^i x. let permutations: Vec> = permutations .into_iter() - .map(|permutation| -> Result<_, _> { permutation.construct().evaluate(pk, x, transcript) }) + .map(|permutation| -> Result<_, _> { permutation.evaluate(pk, x, transcript) }) .collect::, _>>()?; // Evaluate the lookups, if any, at omega^i x. diff --git a/src/plonk/vanishing/prover.rs b/src/plonk/vanishing/prover.rs index 52f7f7a7b0..5001a6150d 100644 --- a/src/plonk/vanishing/prover.rs +++ b/src/plonk/vanishing/prover.rs @@ -110,8 +110,8 @@ impl> Committed { .collect(); // Hash each h(X) piece - for c in h_commitments.iter() { - transcript.write(c)?; + for c in h_commitments { + transcript.write(&c)?; } Ok(Constructed { From 0092a15ec706c1824e36e853d4679271282bfadf Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Thu, 12 Dec 2024 15:51:36 +0100 Subject: [PATCH 12/20] Cost model update --- examples/proof-size.rs | 2 +- src/dev/cost_model.rs | 124 ++++++++++++++++++++++++++++++----------- 2 files changed, 92 insertions(+), 34 deletions(-) diff --git a/examples/proof-size.rs b/examples/proof-size.rs index 3d5b242fb0..db467b4d24 100644 --- a/examples/proof-size.rs +++ b/examples/proof-size.rs @@ -89,7 +89,7 @@ fn main() { let circuit = TestCircuit {}; let model = from_circuit_to_model_circuit::<_, _, 56, 56>( - K, + Some(K), &circuit, vec![], CommitmentScheme::KZGGWC, diff --git a/src/dev/cost_model.rs b/src/dev/cost_model.rs index 51b3a1ad76..9e2bbaa37c 100644 --- a/src/dev/cost_model.rs +++ b/src/dev/cost_model.rs @@ -2,12 +2,14 @@ //! verification cost, as well as resulting proof size. use std::collections::HashSet; -use std::{iter, num::ParseIntError, str::FromStr}; +use std::panic::AssertUnwindSafe; +use std::{iter, num::ParseIntError, panic, str::FromStr}; use crate::plonk::Circuit; use ff::{Field, FromUniformBytes}; use serde::Deserialize; use serde_derive::Serialize; +use crate::plonk::Any::Fixed; use super::MockProver; @@ -46,11 +48,21 @@ pub struct CostOptions { /// A permutation over N columns. May be repeated. pub permutation: Permutation, - /// A shuffle over N columns with max input degree I and max shuffle degree T. May be repeated. - pub shuffle: Vec, + /// 2^K bound on the number of rows, accounting for ZK, PIs and Lookup tables. + pub min_k: usize, - /// 2^K bound on the number of rows. - pub k: usize, + /// Rows count, not including table rows and not accounting for compression + /// (where multiple regions can use the same rows). + pub rows_count: usize, + + /// Table rows count, not accounting for compression (where multiple regions + /// can use the same rows), but not much if any compression can happen with + /// table rows anyway. + pub table_rows_count: usize, + + /// Compressed rows count, accounting for compression (where multiple + /// regions can use the same rows). + pub compressed_rows_count: usize, } /// Structure holding polynomial related data for benchmarks @@ -110,19 +122,6 @@ impl Permutation { } } -/// Structure holding the [Shuffle] related data for circuit benchmarks. -#[derive(Debug, Clone)] -pub struct Shuffle; - -impl Shuffle { - fn queries(&self) -> impl Iterator { - // Open shuffle product commitment at x and \omega x - let shuffle = "0, 1".parse().unwrap(); - - iter::empty().chain(Some(shuffle)) - } -} - /// High-level specifications of an abstract circuit. #[derive(Debug, Deserialize, Serialize)] pub struct ModelCircuit { @@ -136,8 +135,6 @@ pub struct ModelCircuit { pub lookups: usize, /// Equality constraint enabled columns. pub permutations: usize, - /// Number of shuffle arguments - pub shuffles: usize, /// Number of distinct column queries across all gates. pub column_queries: usize, /// Number of distinct sets of points in the multiopening argument. @@ -160,7 +157,6 @@ impl CostOptions { .cloned() .chain(self.lookup.iter().flat_map(|l| l.queries())) .chain(self.permutation.queries()) - .chain(self.shuffle.iter().flat_map(|s| s.queries())) .chain(iter::repeat("0".parse().unwrap()).take(self.max_degree - 1)) .collect(); @@ -199,7 +195,7 @@ impl CostOptions { // - inner product argument (k rounds * 2 * COMM bytes) // - a (SCALAR bytes) // - xi (SCALAR bytes) - comp_bytes(1 + 2 * self.k, 2) + comp_bytes(1 + 2 * self.min_k, 2) } CommitmentScheme::KZGGWC => { let mut nr_rotations = HashSet::new(); @@ -227,12 +223,11 @@ impl CostOptions { let size = plonk + vanishing + multiopen + polycomm; ModelCircuit { - k: self.k, + k: self.min_k, max_deg: self.max_degree, advice_columns: self.advice.len(), lookups: self.lookup.len(), permutations: self.permutation.columns, - shuffles: self.shuffle.len(), column_queries, point_sets, size, @@ -247,7 +242,7 @@ pub fn from_circuit_to_model_circuit< const COMM: usize, const SCALAR: usize, >( - k: u32, + k: Option, circuit: &C, instances: Vec>, comm_scheme: CommitmentScheme, @@ -256,13 +251,34 @@ pub fn from_circuit_to_model_circuit< options.into_model_circuit::(comm_scheme) } -/// Given a Plonk circuit, this function returns [CostOptions] +fn run_mock_prover_with_fallback, C: Circuit>( + circuit: &C, + instances: Vec>, +) -> MockProver { + (5..25) + .find_map(|k| { + panic::catch_unwind(AssertUnwindSafe(|| { + MockProver::run(k, circuit, instances.clone()).unwrap() + })) + .ok() + }) + .expect("A circuit which can be implemented with at most 2^24 rows.") +} + +/// Given a circuit, this function returns [CostOptions]. If no upper bound for `k` is +/// provided, we iterate until a valid `k` is found (this might delay the computation). pub fn from_circuit_to_cost_model_options, C: Circuit>( - k: u32, + k_upper_bound: Option, circuit: &C, instances: Vec>, ) -> CostOptions { - let prover = MockProver::run(k, circuit, instances).unwrap(); + let instance_len = instances.iter().map(Vec::len).max().unwrap_or(0); + let prover = if let Some(k) = k_upper_bound { + MockProver::run(k, circuit, instances).unwrap() + } else { + run_mock_prover_with_fallback(circuit, instances.clone()) + }; + let cs = prover.cs; let fixed = { @@ -298,8 +314,6 @@ pub fn from_circuit_to_cost_model_options, columns: cs.permutation().get_columns().len(), }; - let shuffle = { cs.shuffles.iter().map(|_| Shuffle).collect::>() }; - let gate_degree = cs .gates .iter() @@ -307,7 +321,49 @@ pub fn from_circuit_to_cost_model_options, .max() .unwrap_or(0); - let k = prover.k.try_into().unwrap(); + // Note that this computation does't assume that `regions` is already in + // order of increasing row indices. + let (rows_count, table_rows_count, compressed_rows_count) = { + let mut rows_count = 0; + let mut table_rows_count = 0; + let mut compressed_rows_count = 0; + for region in prover.regions { + // If `region.rows == None`, then that region has no rows. + if let Some((start, end)) = region.rows { + // Note that `end` is the index of the last column, so when + // counting rows this last column needs to be counted via `end + + // 1`. + + // A region is a _table region_ if all of its columns are `Fixed` + // columns (see that [`plonk::circuit::TableColumn` is a wrapper + // around `Column`]). All of a table region's rows are + // counted towards `table_rows_count.` + if region + .columns + .iter() + .all(|c| *c.column_type() == Fixed) + { + table_rows_count += (end + 1) - start; + } else { + rows_count += (end + 1) - start; + } + compressed_rows_count = std::cmp::max(compressed_rows_count, end + 1); + } + } + (rows_count, table_rows_count, compressed_rows_count) + }; + + let min_k = [ + rows_count + cs.blinding_factors(), + table_rows_count + cs.blinding_factors(), + instance_len, + ] + .into_iter() + .max() + .unwrap(); + if min_k == instance_len { + println!("WARNING: The dominant factor in your circuit's size is the number of public inputs, which causes the verifier to perform linear work."); + } CostOptions { advice, @@ -317,7 +373,9 @@ pub fn from_circuit_to_cost_model_options, max_degree: cs.degree(), lookup, permutation, - shuffle, - k, + min_k, + rows_count, + table_rows_count, + compressed_rows_count, } } From dd00d8ef2aed00edf36097044c8cb99ccde4878f Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Thu, 12 Dec 2024 16:24:56 +0100 Subject: [PATCH 13/20] * Implement PartialEq, Eq, Hash for Cell and AssignedCell * Add table, compressed and normal rows count. * Add rows and table rows to cost model. * Ignore unassigned cells if they are multiplied by zero * Some format values are written as "Scalar(0x..)" The hotfix was to change the stripping rules, but this is probably an incorrect implementation of certain traits for one of the curves. --- src/circuit.rs | 19 +++++- src/circuit/floor_planner/v1/strategy.rs | 14 ----- src/dev.rs | 76 +++++++++++++++++++++++- src/dev/cost_model.rs | 33 ++++++---- src/dev/util.rs | 2 +- src/helpers.rs | 12 ++-- src/plonk.rs | 24 ++++---- src/plonk/keygen.rs | 5 +- src/plonk/permutation/keygen.rs | 28 +++++---- 9 files changed, 152 insertions(+), 61 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index 301ffd2f83..6c0f203ce0 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -50,7 +50,7 @@ pub trait Chip: Sized { } /// Index of a region in a layouter -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct RegionIndex(usize); impl From for RegionIndex { @@ -86,7 +86,7 @@ impl std::ops::Deref for RegionStart { } /// A pointer to a cell within a circuit. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Cell { /// Identifies the region in which this cell resides. pub region_index: RegionIndex, @@ -104,6 +104,21 @@ pub struct AssignedCell { _marker: PhantomData, } +impl PartialEq for AssignedCell { + fn eq(&self, other: &Self) -> bool { + self.cell == other.cell + } +} + +impl Eq for AssignedCell {} + +use std::hash::{Hash, Hasher}; +impl Hash for AssignedCell { + fn hash(&self, state: &mut H) { + self.cell.hash(state) + } +} + impl AssignedCell { /// Returns the value of the [`AssignedCell`]. pub fn value(&self) -> Value<&V> { diff --git a/src/circuit/floor_planner/v1/strategy.rs b/src/circuit/floor_planner/v1/strategy.rs index 71745de245..b0ef9082b8 100644 --- a/src/circuit/floor_planner/v1/strategy.rs +++ b/src/circuit/floor_planner/v1/strategy.rs @@ -213,22 +213,8 @@ pub fn slot_in_biggest_advice_first( advice_cols * shape.row_count() }; - // This used to incorrectly use `sort_unstable_by_key` with non-unique keys, which gave - // output that differed between 32-bit and 64-bit platforms, and potentially between Rust - // versions. - // We now use `sort_by_cached_key` with non-unique keys, and rely on `region_shapes` - // being sorted by region index (which we also rely on below to return `RegionStart`s - // in the correct order). - #[cfg(not(feature = "floor-planner-v1-legacy-pdqsort"))] sorted_regions.sort_by_cached_key(sort_key); - // To preserve compatibility, when the "floor-planner-v1-legacy-pdqsort" feature is enabled, - // we use a copy of the pdqsort implementation from the Rust 1.56.1 standard library, fixed - // to its behaviour on 64-bit platforms. - // https://github.com/rust-lang/rust/blob/1.56.1/library/core/src/slice/mod.rs#L2365-L2402 - #[cfg(feature = "floor-planner-v1-legacy-pdqsort")] - halo2_legacy_pdqsort::sort::quicksort(&mut sorted_regions, |a, b| sort_key(a).lt(&sort_key(b))); - sorted_regions.reverse(); // Lay out the sorted regions. diff --git a/src/dev.rs b/src/dev.rs index c539b4f883..a0cb07bd90 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -47,11 +47,14 @@ pub use tfp::TracingFloorPlanner; #[cfg(feature = "dev-graph")] mod graph; +use crate::plonk::VirtualCell; use crate::rational::Rational; #[cfg(feature = "dev-graph")] #[cfg_attr(docsrs, doc(cfg(feature = "dev-graph")))] pub use graph::{circuit_dot_graph, layout::CircuitLayout}; +use crate::poly::Rotation; + #[derive(Debug)] struct Region { /// The name of the region. Not required to be unique. @@ -820,7 +823,15 @@ impl + Ord> MockProver { } _ => { // Check that it was assigned! - if r.cells.contains_key(&(cell.column, cell_row)) { + if r.cells.contains_key(&(cell.column, cell_row)) + || gate.polynomials().par_iter().all(|expr| { + self.cell_is_irrelevant( + cell, + expr, + gate_row as usize, + ) + }) + { None } else { Some(VerifyFailure::CellNotAssigned { @@ -1124,6 +1135,69 @@ impl + Ord> MockProver { } } + // Checks if the given expression is guaranteed to be constantly zero at the given offset. + fn expr_is_constantly_zero(&self, expr: &Expression, offset: usize) -> bool { + match expr { + Expression::Constant(constant) => constant.is_zero().into(), + Expression::Selector(selector) => !self.selectors[selector.0][offset], + Expression::Fixed(query) => match self.fixed[query.column_index][offset] { + CellValue::Assigned(value) => value.is_zero().into(), + _ => false, + }, + Expression::Scaled(e, factor) => { + factor.is_zero().into() || self.expr_is_constantly_zero(e, offset) + } + Expression::Sum(e1, e2) => { + self.expr_is_constantly_zero(e1, offset) && self.expr_is_constantly_zero(e2, offset) + } + Expression::Product(e1, e2) => { + self.expr_is_constantly_zero(e1, offset) || self.expr_is_constantly_zero(e2, offset) + } + _ => false, + } + } + + // Verify that the value of the given cell within the given expression is + // irrelevant to the evaluation of the expression. This may be because + // the cell is always multiplied by an expression that evaluates to 0, or + // because the cell is not being queried in the expression at all. + fn cell_is_irrelevant(&self, cell: &VirtualCell, expr: &Expression, offset: usize) -> bool { + // Check if a given query (defined by its columnd and rotation, since we + // want this function to support different query types) is equal to `cell`. + let eq_query = |query_column: usize, query_rotation: Rotation, col_type: Any| { + cell.column.index() == query_column + && cell.column.column_type() == &col_type + && query_rotation == cell.rotation + }; + match expr { + Expression::Constant(_) | Expression::Selector(_) => true, + Expression::Fixed(query) => !eq_query(query.column_index, query.rotation(), Any::Fixed), + Expression::Advice(query) => !eq_query( + query.column_index, + query.rotation(), + Any::Advice(Advice::new(query.phase)), + ), + Expression::Instance(query) => { + !eq_query(query.column_index, query.rotation(), Any::Instance) + } + Expression::Challenge(_) => true, + Expression::Negated(e) => self.cell_is_irrelevant(cell, e, offset), + Expression::Sum(e1, e2) => { + self.cell_is_irrelevant(cell, e1, offset) + && self.cell_is_irrelevant(cell, e2, offset) + } + Expression::Product(e1, e2) => { + (self.expr_is_constantly_zero(e1, offset) + || self.expr_is_constantly_zero(e2, offset)) + || (self.cell_is_irrelevant(cell, e1, offset) + && self.cell_is_irrelevant(cell, e2, offset)) + } + Expression::Scaled(e, factor) => { + factor.is_zero().into() || self.cell_is_irrelevant(cell, e, offset) + } + } + } + /// Panics if the circuit being checked by this `MockProver` is not satisfied. /// /// Any verification failures will be pretty-printed to stderr before the function diff --git a/src/dev/cost_model.rs b/src/dev/cost_model.rs index 9e2bbaa37c..3381a501b1 100644 --- a/src/dev/cost_model.rs +++ b/src/dev/cost_model.rs @@ -5,11 +5,11 @@ use std::collections::HashSet; use std::panic::AssertUnwindSafe; use std::{iter, num::ParseIntError, panic, str::FromStr}; +use crate::plonk::Any::Fixed; use crate::plonk::Circuit; use ff::{Field, FromUniformBytes}; use serde::Deserialize; use serde_derive::Serialize; -use crate::plonk::Any::Fixed; use super::MockProver; @@ -88,7 +88,8 @@ impl FromStr for Poly { pub struct Lookup; impl Lookup { - fn queries(&self) -> impl Iterator { + /// Returns the queries of the lookup argument + pub fn queries(&self) -> impl Iterator { // - product commitments at x and \omega x // - input commitments at x and x_inv // - table commitments at x @@ -110,7 +111,8 @@ pub struct Permutation { } impl Permutation { - fn queries(&self) -> impl Iterator { + /// Returns the queries of the Permutation argument + pub fn queries(&self) -> impl Iterator { // - product commitments at x and x_inv // - polynomial commitments at x let product = "0,-1".parse().unwrap(); @@ -120,6 +122,11 @@ impl Permutation { .chain(Some(product)) .chain(iter::repeat(poly).take(self.columns)) } + + /// Returns the number of columns of the Permutation argument + pub fn nr_columns(&self) -> usize { + self.columns + } } /// High-level specifications of an abstract circuit. @@ -127,6 +134,10 @@ impl Permutation { pub struct ModelCircuit { /// Power-of-2 bound on the number of rows in the circuit. pub k: usize, + /// Number of rows in the circuit (not including table rows). + pub rows: usize, + /// Number of table rows in the circuit. + pub table_rows: usize, /// Maximum degree of the circuit. pub max_deg: usize, /// Number of advice columns. @@ -224,6 +235,8 @@ impl CostOptions { ModelCircuit { k: self.min_k, + rows: self.rows_count, + table_rows: self.table_rows_count, max_deg: self.max_degree, advice_columns: self.advice.len(), lookups: self.lookup.len(), @@ -260,7 +273,7 @@ fn run_mock_prover_with_fallback, C: Circu panic::catch_unwind(AssertUnwindSafe(|| { MockProver::run(k, circuit, instances.clone()).unwrap() })) - .ok() + .ok() }) .expect("A circuit which can be implemented with at most 2^24 rows.") } @@ -338,11 +351,7 @@ pub fn from_circuit_to_cost_model_options, // columns (see that [`plonk::circuit::TableColumn` is a wrapper // around `Column`]). All of a table region's rows are // counted towards `table_rows_count.` - if region - .columns - .iter() - .all(|c| *c.column_type() == Fixed) - { + if region.columns.iter().all(|c| *c.column_type() == Fixed) { table_rows_count += (end + 1) - start; } else { rows_count += (end + 1) - start; @@ -358,9 +367,9 @@ pub fn from_circuit_to_cost_model_options, table_rows_count + cs.blinding_factors(), instance_len, ] - .into_iter() - .max() - .unwrap(); + .into_iter() + .max() + .unwrap(); if min_k == instance_len { println!("WARNING: The dominant factor in your circuit's size is the number of public inputs, which causes the verifier to perform linear work."); } diff --git a/src/dev/util.rs b/src/dev/util.rs index a663f9b80b..b019a27640 100644 --- a/src/dev/util.rs +++ b/src/dev/util.rs @@ -65,7 +65,7 @@ pub(super) fn format_value(v: F) -> String { // Format value as hex. let s = format!("{v:?}"); // Remove leading zeroes. - let s = s.strip_prefix("0x").unwrap(); + let s = s.split_once("0x").unwrap().1.split(')').next().unwrap(); let s = s.trim_start_matches('0'); format!("0x{s}") } diff --git a/src/helpers.rs b/src/helpers.rs index dd4e006cc2..8ead1c65b7 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -49,9 +49,9 @@ pub trait SerdeCurveAffine: PrimeCurveAffine + SerdeObject + Default { /// Reads an element from the buffer and parses it according to the `format`: /// - `Processed`: Reads a compressed curve element and decompress it /// - `RawBytes`: Reads an uncompressed curve element with coordinates in Montgomery form. - /// Checks that field elements are less than modulus, and then checks that the point is on the curve. + /// Checks that field elements are less than modulus, and then checks that the point is on the curve. /// - `RawBytesUnchecked`: Reads an uncompressed curve element with coordinates in Montgomery form; - /// does not perform any checks + /// does not perform any checks fn read(reader: &mut R, format: SerdeFormat) -> io::Result { match format { SerdeFormat::Processed => ::read(reader), @@ -83,9 +83,9 @@ impl SerdeCurveAffine for C {} pub trait SerdePrimeField: PrimeField + SerdeObject { /// Reads a field element as bytes from the buffer according to the `format`: /// - `Processed`: Reads a field element in standard form, with endianness specified by the - /// `PrimeField` implementation, and checks that the element is less than the modulus. + /// `PrimeField` implementation, and checks that the element is less than the modulus. /// - `RawBytes`: Reads a field element from raw bytes in its internal Montgomery representations, - /// and checks that the element is less than the modulus. + /// and checks that the element is less than the modulus. /// - `RawBytesUnchecked`: Reads a field element in Montgomery form and performs no checks. fn read(reader: &mut R, format: SerdeFormat) -> io::Result { match format { @@ -103,9 +103,9 @@ pub trait SerdePrimeField: PrimeField + SerdeObject { /// Writes a field element as bytes to the buffer according to the `format`: /// - `Processed`: Writes a field element in standard form, with endianness specified by the - /// `PrimeField` implementation. + /// `PrimeField` implementation. /// - Otherwise: Writes a field element into raw bytes in its internal Montgomery representation, - /// WITHOUT performing the expensive Montgomery reduction. + /// WITHOUT performing the expensive Montgomery reduction. fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> { match format { SerdeFormat::Processed => writer.write_all(self.to_repr().as_ref()), diff --git a/src/plonk.rs b/src/plonk.rs index f9b18f289e..5e21cf306f 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -103,12 +103,12 @@ where /// /// Reads a curve element from the buffer and parses it according to the `format`: /// - `Processed`: Reads a compressed curve element and decompresses it. - /// Reads a field element in standard form, with endianness specified by the - /// `PrimeField` implementation, and checks that the element is less than the modulus. + /// Reads a field element in standard form, with endianness specified by the + /// `PrimeField` implementation, and checks that the element is less than the modulus. /// - `RawBytes`: Reads an uncompressed curve element with coordinates in Montgomery form. - /// Checks that field elements are less than modulus, and then checks that the point is on the curve. + /// Checks that field elements are less than modulus, and then checks that the point is on the curve. /// - `RawBytesUnchecked`: Reads an uncompressed curve element with coordinates in Montgomery form; - /// does not perform any checks + /// does not perform any checks pub fn read>( reader: &mut R, format: SerdeFormat, @@ -334,12 +334,12 @@ where /// /// Writes a curve element according to `format`: /// - `Processed`: Writes a compressed curve element with coordinates in standard form. - /// Writes a field element in standard form, with endianness specified by the + /// Writes a field element in standard form, with endianness specified by the /// `PrimeField` implementation. /// - Otherwise: Writes an uncompressed curve element with coordinates in Montgomery form - /// Writes a field element into raw bytes in its internal Montgomery representation, - /// WITHOUT performing the expensive Montgomery reduction. - /// Does so by first writing the verifying key and then serializing the rest of the data (in the form of field polynomials) + /// Writes a field element into raw bytes in its internal Montgomery representation, + /// WITHOUT performing the expensive Montgomery reduction. + /// Does so by first writing the verifying key and then serializing the rest of the data (in the form of field polynomials) pub fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> { self.vk.write(writer, format)?; self.l0.write(writer, format)?; @@ -357,12 +357,12 @@ where /// /// Reads a curve element from the buffer and parses it according to the `format`: /// - `Processed`: Reads a compressed curve element and decompresses it. - /// Reads a field element in standard form, with endianness specified by the - /// `PrimeField` implementation, and checks that the element is less than the modulus. + /// Reads a field element in standard form, with endianness specified by the + /// `PrimeField` implementation, and checks that the element is less than the modulus. /// - `RawBytes`: Reads an uncompressed curve element with coordinates in Montgomery form. - /// Checks that field elements are less than modulus, and then checks that the point is on the curve. + /// Checks that field elements are less than modulus, and then checks that the point is on the curve. /// - `RawBytesUnchecked`: Reads an uncompressed curve element with coordinates in Montgomery form; - /// does not perform any checks + /// does not perform any checks pub fn read>( reader: &mut R, format: SerdeFormat, diff --git a/src/plonk/keygen.rs b/src/plonk/keygen.rs index 4ff96c645c..a3036a8834 100644 --- a/src/plonk/keygen.rs +++ b/src/plonk/keygen.rs @@ -3,6 +3,7 @@ use std::ops::Range; use ff::{Field, FromUniformBytes, WithSmallOrderMulGroup}; +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use super::{ circuit::{ @@ -312,12 +313,12 @@ where ); let fixed_polys: Vec<_> = fixed - .iter() + .par_iter() .map(|poly| vk.domain.lagrange_to_coeff(poly.clone())) .collect(); let fixed_cosets = fixed_polys - .iter() + .par_iter() .map(|poly| vk.domain.coeff_to_extended(poly.clone())) .collect(); diff --git a/src/plonk/permutation/keygen.rs b/src/plonk/permutation/keygen.rs index 3adb2de769..c3d190c2ea 100644 --- a/src/plonk/permutation/keygen.rs +++ b/src/plonk/permutation/keygen.rs @@ -14,6 +14,7 @@ use crate::multicore::{IndexedParallelIterator, IntoParallelIterator, ParallelIt use crate::multicore::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; +use rayon::iter::IntoParallelRefMutIterator; #[cfg(feature = "thread-safe-region")] use std::collections::{BTreeSet, HashMap}; @@ -355,35 +356,40 @@ pub(crate) fn build_pk, CS: PolynomialCommitmentSch let mut permutations = vec![domain.empty_lagrange(); p.columns.len()]; { parallelize(&mut permutations, |o, start| { - for (x, permutation_poly) in o.iter_mut().enumerate() { - let i = start + x; - for (j, p) in permutation_poly.iter_mut().enumerate() { - let (permuted_i, permuted_j) = mapping(i, j); - *p = deltaomega[permuted_i][permuted_j]; - } - } + o.par_iter_mut() + .enumerate() + .for_each(|(x, permutation_poly)| { + let i = start + x; + permutation_poly + .par_iter_mut() + .enumerate() + .for_each(|(j, p)| { + let (permuted_i, permuted_j) = mapping(i, j); + *p = deltaomega[permuted_i][permuted_j]; + }) + }) }); } let mut polys = vec![domain.empty_coeff(); p.columns.len()]; { parallelize(&mut polys, |o, start| { - for (x, poly) in o.iter_mut().enumerate() { + o.par_iter_mut().enumerate().for_each(|(x, poly)| { let i = start + x; let permutation_poly = permutations[i].clone(); *poly = domain.lagrange_to_coeff(permutation_poly); - } + }) }); } let mut cosets = vec![domain.empty_extended(); p.columns.len()]; { parallelize(&mut cosets, |o, start| { - for (x, coset) in o.iter_mut().enumerate() { + o.par_iter_mut().enumerate().for_each(|(x, coset)| { let i = start + x; let poly = polys[i].clone(); *coset = domain.coeff_to_extended(poly); - } + }) }); } From 25ac945536f68370b5b58ab6d5ec9311d493e9d3 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Thu, 12 Dec 2024 17:26:25 +0100 Subject: [PATCH 14/20] Review comments --- src/arithmetic.rs | 6 ++---- src/plonk/prover.rs | 3 +-- src/plonk/vanishing/prover.rs | 14 ++++---------- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/arithmetic.rs b/src/arithmetic.rs index f956e7b3d8..e831b19f59 100644 --- a/src/arithmetic.rs +++ b/src/arithmetic.rs @@ -236,14 +236,12 @@ pub(crate) fn powers(base: F) -> impl Iterator { } /// Multi scalar multiplication engine -pub trait MSM: Clone + Debug + Send + Sync { +pub trait MSM: Clone + Debug + Send + Sized + Sync { /// Add arbitrary term (the scalar and the point) fn append_term(&mut self, scalar: C::Scalar, point: C::Curve); /// Add another multiexp into this one - fn add_msm(&mut self, other: &Self) - where - Self: Sized; + fn add_msm(&mut self, other: &Self); /// Scale all scalars in the MSM by some scaling factor fn scale(&mut self, factor: C::Scalar); diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 07bf302768..e4a61043b7 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -476,7 +476,6 @@ where let vanishing = vanishing.construct::(params, domain, h_poly, transcript)?; let x: F = transcript.squeeze_challenge(); - let xn = x.pow([params.n()]); // Compute and hash advice evals for each circuit instance for advice in advice.iter() { @@ -512,7 +511,7 @@ where transcript.write(eval)?; } - let vanishing = vanishing.evaluate(x, xn, domain, transcript)?; + let vanishing = vanishing.evaluate(x, transcript)?; // Evaluate common permutation data pk.permutation.evaluate(x, transcript)?; diff --git a/src/plonk/vanishing/prover.rs b/src/plonk/vanishing/prover.rs index 5001a6150d..4a906d4ab1 100644 --- a/src/plonk/vanishing/prover.rs +++ b/src/plonk/vanishing/prover.rs @@ -125,26 +125,20 @@ impl Constructed { pub(in crate::plonk) fn evaluate( self, x: F, - _xn: F, - _domain: &EvaluationDomain, transcript: &mut T, ) -> Result, Error> where F: Hashable + SerdeObject, { - self.h_pieces - .iter() - // .fold(domain.empty_coeff(), |acc, eval| acc * xn + eval); - .try_for_each(|p| { - let random_eval = eval_polynomial(p, x); - transcript.write(&random_eval) - })?; + self.h_pieces.iter().try_for_each(|p| { + let eval = eval_polynomial(p, x); + transcript.write(&eval) + })?; let random_eval = eval_polynomial(&self.committed.random_poly, x); transcript.write(&random_eval)?; Ok(Evaluated { - // h_poly, h_pieces: self.h_pieces, committed: self.committed, }) From a2793a1c720ad9b926bb05fc8a87fe0cc6e7d06d Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Fri, 13 Dec 2024 13:22:52 +0100 Subject: [PATCH 15/20] Just moving things around! Used the same style as we have in midnight-circuits. Modules are defined with a `mod.rs` file inside the folder, instead of with a file at root level with the same name. I've also created a module for utils and one for the transcript. --- benches/commit_zk.rs | 2 +- benches/hashtocurve.rs | 2 +- benches/plonk.rs | 2 +- examples/serialization.rs | 2 +- examples/shuffle.rs | 2 +- examples/simple-example.rs | 2 +- examples/two-chip.rs | 2 +- examples/vector-mul.rs | 2 +- examples/vector-ops-unblinded.rs | 2 +- src/circuit/floor_planner/single_pass.rs | 2 +- src/circuit/floor_planner/v1.rs | 2 +- src/circuit/layouter.rs | 2 +- src/{circuit.rs => circuit/mod.rs} | 12 +- src/circuit/table_layouter.rs | 2 +- src/circuit/value.rs | 2 +- src/dev/cost.rs | 2 +- src/{dev.rs => dev/mod.rs} | 156 +++++++++++------------ src/dev/tfp.rs | 2 +- src/lib.rs | 6 +- src/plonk/circuit.rs | 2 +- src/plonk/evaluation.rs | 4 +- src/plonk/keygen.rs | 4 +- src/plonk/lookup/prover.rs | 2 +- src/{plonk.rs => plonk/mod.rs} | 16 +-- src/plonk/permutation.rs | 6 +- src/plonk/permutation/keygen.rs | 6 +- src/plonk/permutation/prover.rs | 2 +- src/plonk/prover.rs | 4 +- src/plonk/vanishing/prover.rs | 4 +- src/plonk/verifier.rs | 2 +- src/poly/domain.rs | 8 +- src/poly/kzg/mod.rs | 4 +- src/poly/kzg/msm.rs | 4 +- src/poly/kzg/params.rs | 6 +- src/{poly.rs => poly/mod.rs} | 12 +- src/poly/query.rs | 2 +- src/{transcript.rs => transcript/mod.rs} | 0 src/{ => utils}/arithmetic.rs | 0 src/{ => utils}/helpers.rs | 0 src/utils/mod.rs | 8 ++ src/{ => utils}/multicore.rs | 0 src/{ => utils}/rational.rs | 2 +- 42 files changed, 154 insertions(+), 150 deletions(-) rename src/{circuit.rs => circuit/mod.rs} (98%) rename src/{dev.rs => dev/mod.rs} (93%) rename src/{plonk.rs => plonk/mod.rs} (98%) rename src/{poly.rs => poly/mod.rs} (98%) rename src/{transcript.rs => transcript/mod.rs} (100%) rename src/{ => utils}/arithmetic.rs (100%) rename src/{ => utils}/helpers.rs (100%) create mode 100644 src/utils/mod.rs rename src/{ => utils}/multicore.rs (100%) rename src/{ => utils}/rational.rs (99%) diff --git a/benches/commit_zk.rs b/benches/commit_zk.rs index d06bed0f97..873ec9af19 100644 --- a/benches/commit_zk.rs +++ b/benches/commit_zk.rs @@ -2,7 +2,7 @@ extern crate criterion; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use group::ff::Field; -use halo2_proofs::arithmetic::parallelize; +use halo2_proofs::utils::arithmetic::parallelize; use halo2curves::pasta::pallas::Scalar; use rand_chacha::rand_core::RngCore; use rand_chacha::ChaCha20Rng; diff --git a/benches/hashtocurve.rs b/benches/hashtocurve.rs index a3805f3b9e..7bd01d55b8 100644 --- a/benches/hashtocurve.rs +++ b/benches/hashtocurve.rs @@ -2,7 +2,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use halo2_proofs::arithmetic::CurveExt; +use halo2_proofs::utils::arithmetic::CurveExt; use halo2curves::pasta::{pallas, vesta}; fn criterion_benchmark(c: &mut Criterion) { diff --git a/benches/plonk.rs b/benches/plonk.rs index 63a18560fe..9e7cc9c842 100644 --- a/benches/plonk.rs +++ b/benches/plonk.rs @@ -12,7 +12,7 @@ use std::marker::PhantomData; use criterion::{BenchmarkId, Criterion}; use halo2_proofs::poly::kzg::{params::ParamsKZG, KZGCommitmentScheme}; -use halo2_proofs::rational::Rational; +use halo2_proofs::utils::rational::Rational; use halo2_proofs::transcript::{CircuitTranscript, Transcript}; fn criterion_benchmark(c: &mut Criterion) { diff --git a/examples/serialization.rs b/examples/serialization.rs index d588cc5a1d..baf33aff7c 100644 --- a/examples/serialization.rs +++ b/examples/serialization.rs @@ -16,7 +16,7 @@ use halo2_proofs::{ kzg::{params::ParamsKZG, KZGCommitmentScheme}, Rotation, }, - SerdeFormat, + utils::SerdeFormat, }; use halo2curves::bn256::{Bn256, Fr}; use rand_core::OsRng; diff --git a/examples/shuffle.rs b/examples/shuffle.rs index a6f0f21248..1aa608dbac 100644 --- a/examples/shuffle.rs +++ b/examples/shuffle.rs @@ -3,7 +3,7 @@ use ff::{BatchInvert, FromUniformBytes, WithSmallOrderMulGroup}; use halo2_proofs::poly::kzg::{params::ParamsKZG, KZGCommitmentScheme}; use halo2_proofs::transcript::{CircuitTranscript, Hashable, Sampleable, Transcript}; use halo2_proofs::{ - arithmetic::Field, + utils::arithmetic::Field, circuit::{floor_planner::V1, Layouter, Value}, dev::{metadata, FailureLocation, MockProver, VerifyFailure}, plonk::*, diff --git a/examples/simple-example.rs b/examples/simple-example.rs index 242257a692..4666124bf1 100644 --- a/examples/simple-example.rs +++ b/examples/simple-example.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use halo2_proofs::{ - arithmetic::Field, + utils::arithmetic::Field, circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector}, poly::Rotation, diff --git a/examples/two-chip.rs b/examples/two-chip.rs index 336f9c4957..97f37e8cc9 100644 --- a/examples/two-chip.rs +++ b/examples/two-chip.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use halo2_proofs::{ - arithmetic::Field, + utils::arithmetic::Field, circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, poly::Rotation, diff --git a/examples/vector-mul.rs b/examples/vector-mul.rs index 01728fdf36..bfeb3d5fb8 100644 --- a/examples/vector-mul.rs +++ b/examples/vector-mul.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use halo2_proofs::{ - arithmetic::Field, + utils::arithmetic::Field, circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, poly::Rotation, diff --git a/examples/vector-ops-unblinded.rs b/examples/vector-ops-unblinded.rs index adac350c0c..356c0a05ea 100644 --- a/examples/vector-ops-unblinded.rs +++ b/examples/vector-ops-unblinded.rs @@ -8,7 +8,7 @@ use ff::{FromUniformBytes, WithSmallOrderMulGroup}; use halo2_proofs::poly::kzg::{params::ParamsKZG, KZGCommitmentScheme}; use halo2_proofs::transcript::{CircuitTranscript, Hashable, Sampleable, Transcript}; use halo2_proofs::{ - arithmetic::Field, + utils::arithmetic::Field, circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, plonk::*, poly::Rotation, diff --git a/src/circuit/floor_planner/single_pass.rs b/src/circuit/floor_planner/single_pass.rs index a2d76a79c8..06fd2b190b 100644 --- a/src/circuit/floor_planner/single_pass.rs +++ b/src/circuit/floor_planner/single_pass.rs @@ -5,7 +5,7 @@ use std::marker::PhantomData; use ff::Field; -use crate::rational::Rational; +use crate::utils::rational::Rational; use crate::{ circuit::{ layouter::{RegionColumn, RegionLayouter, RegionShape, SyncDeps, TableLayouter}, diff --git a/src/circuit/floor_planner/v1.rs b/src/circuit/floor_planner/v1.rs index d42d407d19..6dd426c5b3 100644 --- a/src/circuit/floor_planner/v1.rs +++ b/src/circuit/floor_planner/v1.rs @@ -2,7 +2,7 @@ use std::fmt; use ff::Field; -use crate::rational::Rational; +use crate::utils::rational::Rational; use crate::{ circuit::{ layouter::{RegionColumn, RegionLayouter, RegionShape, SyncDeps, TableLayouter}, diff --git a/src/circuit/layouter.rs b/src/circuit/layouter.rs index ba1c4fb8a6..17ef6cd271 100644 --- a/src/circuit/layouter.rs +++ b/src/circuit/layouter.rs @@ -9,7 +9,7 @@ use ff::Field; pub use super::table_layouter::TableLayouter; use super::{Cell, RegionIndex, Value}; use crate::plonk::{Advice, Any, Column, Error, Fixed, Instance, Selector}; -use crate::rational::Rational; +use crate::utils::rational::Rational; /// Intermediate trait requirements for [`RegionLayouter`] when thread-safe regions are enabled. #[cfg(feature = "thread-safe-region")] diff --git a/src/circuit.rs b/src/circuit/mod.rs similarity index 98% rename from src/circuit.rs rename to src/circuit/mod.rs index 6c0f203ce0..d89fec0f73 100644 --- a/src/circuit.rs +++ b/src/circuit/mod.rs @@ -15,7 +15,7 @@ pub use floor_planner::single_pass::SimpleFloorPlanner; pub mod layouter; mod table_layouter; -use crate::rational::Rational; +use crate::utils::rational::Rational; pub use table_layouter::{SimpleTableLayouter, TableLayouter}; /// A chip implements a set of instructions that can be used by gadgets. @@ -133,7 +133,7 @@ impl AssignedCell { impl AssignedCell where - for<'v> Rational: From<&'v V>, + for<'v> Rational: From<&'v V>, { /// Returns the field element value of the [`AssignedCell`]. pub fn value_field(&self) -> Value> { @@ -157,7 +157,7 @@ impl AssignedCell, F> { impl AssignedCell where - for<'v> Rational: From<&'v V>, + for<'v> Rational: From<&'v V>, { /// Copies the value to a given advice cell and constrains them to be equal. /// @@ -281,9 +281,9 @@ impl<'r, F: Field> Region<'r, F> { constant: VR, ) -> Result, Error> where - for<'vr> Rational: From<&'vr VR>, - A: Fn() -> AR, - AR: Into, + for<'vr> Rational: From<&'vr VR>, + A: Fn() -> AR, + AR: Into, { let cell = self.region.assign_advice_from_constant( &|| annotation().into(), diff --git a/src/circuit/table_layouter.rs b/src/circuit/table_layouter.rs index dc7aa94ef7..e5e519596f 100644 --- a/src/circuit/table_layouter.rs +++ b/src/circuit/table_layouter.rs @@ -8,7 +8,7 @@ use std::{ use ff::Field; use crate::plonk::{Assignment, Error, TableColumn, TableError}; -use crate::rational::Rational; +use crate::utils::rational::Rational; use super::Value; diff --git a/src/circuit/value.rs b/src/circuit/value.rs index 5c42a08ccd..c63aa55ae8 100644 --- a/src/circuit/value.rs +++ b/src/circuit/value.rs @@ -4,7 +4,7 @@ use std::ops::{Add, Mul, Neg, Sub}; use group::ff::Field; use crate::plonk::Error; -use crate::rational::Rational; +use crate::utils::rational::Rational; /// A value that might exist within a circuit. /// diff --git a/src/dev/cost.rs b/src/dev/cost.rs index 3272315fc6..4a9248fbe9 100644 --- a/src/dev/cost.rs +++ b/src/dev/cost.rs @@ -11,7 +11,7 @@ use std::{ use ff::{Field, PrimeField}; use group::prime::PrimeGroup; -use crate::rational::Rational; +use crate::utils::rational::Rational; use crate::{ circuit::{layouter::RegionColumn, Value}, plonk::{ diff --git a/src/dev.rs b/src/dev/mod.rs similarity index 93% rename from src/dev.rs rename to src/dev/mod.rs index a0cb07bd90..2e1f4d9cfe 100644 --- a/src/dev.rs +++ b/src/dev/mod.rs @@ -20,7 +20,7 @@ use crate::{ }, }; -use crate::multicore::{ +use crate::utils::multicore::{ IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, ParallelIterator, ParallelSliceMut, }; @@ -48,7 +48,7 @@ pub use tfp::TracingFloorPlanner; mod graph; use crate::plonk::VirtualCell; -use crate::rational::Rational; +use crate::utils::rational::Rational; #[cfg(feature = "dev-graph")] #[cfg_attr(docsrs, doc(cfg(feature = "dev-graph")))] pub use graph::{circuit_dot_graph, layout::CircuitLayout}; @@ -151,10 +151,10 @@ impl Mul for Value { // If poison is multiplied by zero, then we treat the poison as unconstrained // and we don't propagate it. (Value::Real(x), Value::Poison) | (Value::Poison, Value::Real(x)) - if x.is_zero_vartime() => - { - Value::Real(F::ZERO) - } + if x.is_zero_vartime() => + { + Value::Real(F::ZERO) + } _ => Value::Poison, } } @@ -683,8 +683,8 @@ impl + Ord> MockProver { hash = blake2b(&hash).as_bytes().try_into().unwrap(); F::from_uniform_bytes(&hash) }) - .take(cs.num_challenges) - .collect() + .take(cs.num_challenges) + .collect() }; let mut prover = MockProver { @@ -825,12 +825,12 @@ impl + Ord> MockProver { // Check that it was assigned! if r.cells.contains_key(&(cell.column, cell_row)) || gate.polynomials().par_iter().all(|expr| { - self.cell_is_irrelevant( - cell, - expr, - gate_row as usize, - ) - }) + self.cell_is_irrelevant( + cell, + expr, + gate_row as usize, + ) + }) { None } else { @@ -871,70 +871,70 @@ impl + Ord> MockProver { .clone() .into_par_iter() .chain(blinding_rows.into_par_iter())) - .flat_map(move |row| { - let row = row as i32 + n; - gate.polynomials() - .iter() - .enumerate() - .filter_map(move |(poly_index, poly)| { - match poly.evaluate_lazy( - &|scalar| Value::Real(scalar), - &|_| panic!("virtual selectors are removed during optimization"), - &util::load(n, row, &self.cs.fixed_queries, &self.fixed), - &util::load(n, row, &self.cs.advice_queries, &self.advice), - &util::load_instance( - n, - row, - &self.cs.instance_queries, - &self.instance, - ), - &|challenge| Value::Real(self.challenges[challenge.index()]), - &|a| -a, - &|a, b| a + b, - &|a, b| a * b, - &|a, scalar| a * scalar, - &Value::Real(F::ZERO), - ) { - Value::Real(x) if x.is_zero_vartime() => None, - Value::Real(_) => Some(VerifyFailure::ConstraintNotSatisfied { - constraint: ( - (gate_index, gate.name()).into(), - poly_index, - gate.constraint_name(poly_index), - ) - .into(), - location: FailureLocation::find_expressions( - &self.cs, - &self.regions, - (row - n) as usize, - Some(poly).into_iter(), + .flat_map(move |row| { + let row = row as i32 + n; + gate.polynomials() + .iter() + .enumerate() + .filter_map(move |(poly_index, poly)| { + match poly.evaluate_lazy( + &|scalar| Value::Real(scalar), + &|_| panic!("virtual selectors are removed during optimization"), + &util::load(n, row, &self.cs.fixed_queries, &self.fixed), + &util::load(n, row, &self.cs.advice_queries, &self.advice), + &util::load_instance( + n, + row, + &self.cs.instance_queries, + &self.instance, ), - cell_values: util::cell_values( - gate, - poly, - &util::load(n, row, &self.cs.fixed_queries, &self.fixed), - &util::load(n, row, &self.cs.advice_queries, &self.advice), - &util::load_instance( - n, - row, - &self.cs.instance_queries, - &self.instance, + &|challenge| Value::Real(self.challenges[challenge.index()]), + &|a| -a, + &|a, b| a + b, + &|a, b| a * b, + &|a, scalar| a * scalar, + &Value::Real(F::ZERO), + ) { + Value::Real(x) if x.is_zero_vartime() => None, + Value::Real(_) => Some(VerifyFailure::ConstraintNotSatisfied { + constraint: ( + (gate_index, gate.name()).into(), + poly_index, + gate.constraint_name(poly_index), + ) + .into(), + location: FailureLocation::find_expressions( + &self.cs, + &self.regions, + (row - n) as usize, + Some(poly).into_iter(), ), - ), - }), - Value::Poison => Some(VerifyFailure::ConstraintPoisoned { - constraint: ( - (gate_index, gate.name()).into(), - poly_index, - gate.constraint_name(poly_index), - ) - .into(), - }), - } - }) - .collect::>() - }) - .collect::>() + cell_values: util::cell_values( + gate, + poly, + &util::load(n, row, &self.cs.fixed_queries, &self.fixed), + &util::load(n, row, &self.cs.advice_queries, &self.advice), + &util::load_instance( + n, + row, + &self.cs.instance_queries, + &self.instance, + ), + ), + }), + Value::Poison => Some(VerifyFailure::ConstraintPoisoned { + constraint: ( + (gate_index, gate.name()).into(), + poly_index, + gate.constraint_name(poly_index), + ) + .into(), + }), + } + }) + .collect::>() + }) + .collect::>() }); let load = |expression: &Expression, row| { @@ -1190,7 +1190,7 @@ impl + Ord> MockProver { (self.expr_is_constantly_zero(e1, offset) || self.expr_is_constantly_zero(e2, offset)) || (self.cell_is_irrelevant(cell, e1, offset) - && self.cell_is_irrelevant(cell, e2, offset)) + && self.cell_is_irrelevant(cell, e2, offset)) } Expression::Scaled(e, factor) => { factor.is_zero().into() || self.cell_is_irrelevant(cell, e, offset) @@ -1537,7 +1537,7 @@ mod tests { Fp::from(6u64), ]], ) - .unwrap(); + .unwrap(); assert_eq!( prover.verify(), Err(vec![VerifyFailure::Lookup { diff --git a/src/dev/tfp.rs b/src/dev/tfp.rs index 804b70dd82..29df58f15b 100644 --- a/src/dev/tfp.rs +++ b/src/dev/tfp.rs @@ -3,7 +3,7 @@ use std::{fmt, marker::PhantomData}; use ff::Field; use tracing::{debug, debug_span, span::EnteredSpan}; -use crate::rational::Rational; +use crate::utils::rational::Rational; use crate::{ circuit::{ layouter::{RegionLayouter, SyncDeps}, diff --git a/src/lib.rs b/src/lib.rs index d4fa50517b..c57687a2d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,16 +8,12 @@ #![deny(missing_docs)] #![deny(unsafe_code)] -pub mod arithmetic; pub mod circuit; pub use halo2curves; -mod multicore; pub mod plonk; pub mod poly; pub mod transcript; -pub mod rational; pub mod dev; -pub mod helpers; -pub use helpers::SerdeFormat; +pub mod utils; diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index 3fc414d2e9..70b295f2a8 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -1,7 +1,7 @@ use super::{lookup, permutation, Error}; use crate::circuit::layouter::SyncDeps; use crate::dev::metadata; -use crate::rational::Rational; +use crate::utils::rational::Rational; use crate::{ circuit::{Layouter, Region, Value}, poly::Rotation, diff --git a/src/plonk/evaluation.rs b/src/plonk/evaluation.rs index 43aa533a04..5ff7ee5f2f 100644 --- a/src/plonk/evaluation.rs +++ b/src/plonk/evaluation.rs @@ -1,9 +1,9 @@ -use crate::multicore; +use crate::utils::multicore; use crate::plonk::{lookup, permutation, Any, ProvingKey}; use crate::poly::commitment::PolynomialCommitmentScheme; use crate::poly::Basis; use crate::{ - arithmetic::parallelize, + utils::arithmetic::parallelize, poly::{Coeff, ExtendedLagrangeCoeff, Polynomial, Rotation}, }; use ff::{PrimeField, WithSmallOrderMulGroup}; diff --git a/src/plonk/keygen.rs b/src/plonk/keygen.rs index a3036a8834..14c3d091e4 100644 --- a/src/plonk/keygen.rs +++ b/src/plonk/keygen.rs @@ -16,8 +16,8 @@ use super::{ use crate::circuit::Value; use crate::poly::batch_invert_rational; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; -use crate::rational::Rational; -use crate::{arithmetic::parallelize, poly::EvaluationDomain}; +use crate::utils::rational::Rational; +use crate::{utils::arithmetic::parallelize, poly::EvaluationDomain}; pub(crate) fn create_domain( k: u32, diff --git a/src/plonk/lookup/prover.rs b/src/plonk/lookup/prover.rs index 82f34e4bb1..f8bf2df23b 100644 --- a/src/plonk/lookup/prover.rs +++ b/src/plonk/lookup/prover.rs @@ -4,7 +4,7 @@ use crate::plonk::evaluation::evaluate; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; use crate::transcript::{Hashable, Transcript}; use crate::{ - arithmetic::{eval_polynomial, parallelize}, + utils::arithmetic::{eval_polynomial, parallelize}, poly::{Coeff, EvaluationDomain, LagrangeCoeff, Polynomial, ProverQuery, Rotation}, }; use ff::{PrimeField, WithSmallOrderMulGroup}; diff --git a/src/plonk.rs b/src/plonk/mod.rs similarity index 98% rename from src/plonk.rs rename to src/plonk/mod.rs index 5e21cf306f..88933526e5 100644 --- a/src/plonk.rs +++ b/src/plonk/mod.rs @@ -8,7 +8,7 @@ use blake2b_simd::Params as Blake2bParams; use group::ff::FromUniformBytes; -use crate::helpers::{ +use crate::utils::helpers::{ byte_length, polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, SerdePrimeField, }; @@ -17,7 +17,7 @@ use crate::poly::{ Polynomial, }; use crate::transcript::{Hashable, Transcript}; -use crate::SerdeFormat; +use crate::utils::SerdeFormat; mod circuit; mod error; @@ -93,7 +93,7 @@ where for selector in &self.selectors { // since `selector` is filled with `bool`, we pack them 8 at a time into bytes and then write for bits in selector.chunks(8) { - writer.write_all(&[crate::helpers::pack(bits)])?; + writer.write_all(&[crate::utils::helpers::pack(bits)])?; } } Ok(()) @@ -190,11 +190,11 @@ impl, CS: PolynomialCommitmentScheme> VerifyingK 10 + (self.fixed_commitments.len() * byte_length::(format)) + self.permutation.bytes_length(format) + self.selectors.len() - * (self - .selectors - .first() - .map(|selector| (selector.len() + 7) / 8) - .unwrap_or(0)) + * (self + .selectors + .first() + .map(|selector| (selector.len() + 7) / 8) + .unwrap_or(0)) } fn from_parts( diff --git a/src/plonk/permutation.rs b/src/plonk/permutation.rs index 7d569295c8..6fd99c21a8 100644 --- a/src/plonk/permutation.rs +++ b/src/plonk/permutation.rs @@ -2,11 +2,11 @@ use super::circuit::{Any, Column}; use crate::{ - helpers::{ + utils::helpers::{ polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, SerdePrimeField, }, poly::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial}, - SerdeFormat, + utils::SerdeFormat, }; pub(crate) mod keygen; @@ -15,7 +15,7 @@ pub(crate) mod verifier; pub use keygen::Assembly; -use crate::helpers::byte_length; +use crate::utils::helpers::byte_length; use crate::poly::commitment::PolynomialCommitmentScheme; use ff::PrimeField; use halo2curves::serde::SerdeObject; diff --git a/src/plonk/permutation/keygen.rs b/src/plonk/permutation/keygen.rs index c3d190c2ea..dc45e1c407 100644 --- a/src/plonk/permutation/keygen.rs +++ b/src/plonk/permutation/keygen.rs @@ -2,16 +2,16 @@ use ff::WithSmallOrderMulGroup; use super::{Argument, ProvingKey, VerifyingKey}; use crate::{ - arithmetic::parallelize, + utils::arithmetic::parallelize, plonk::{Any, Column, Error}, poly::EvaluationDomain, }; #[cfg(feature = "thread-safe-region")] -use crate::multicore::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; +use crate::utils::multicore::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; #[cfg(not(feature = "thread-safe-region"))] -use crate::multicore::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; +use crate::utils::multicore::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; use rayon::iter::IntoParallelRefMutIterator; diff --git a/src/plonk/permutation/prover.rs b/src/plonk/permutation/prover.rs index 78b9ae1693..65152e00ac 100644 --- a/src/plonk/permutation/prover.rs +++ b/src/plonk/permutation/prover.rs @@ -9,7 +9,7 @@ use super::{Argument, ProvingKey}; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; use crate::transcript::{Hashable, Transcript}; use crate::{ - arithmetic::{eval_polynomial, parallelize}, + utils::arithmetic::{eval_polynomial, parallelize}, plonk::{self, Error}, poly::{Coeff, LagrangeCoeff, Polynomial, ProverQuery, Rotation}, }; diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index e4a61043b7..34d1111c60 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -14,7 +14,7 @@ use super::{ }; use crate::{ - arithmetic::eval_polynomial, + utils::arithmetic::eval_polynomial, // circuit::Value, // plonk::Assigned, poly::{Basis, Coeff, LagrangeCoeff, Polynomial, ProverQuery}, @@ -23,7 +23,7 @@ use crate::{ use crate::circuit::Value; use crate::poly::batch_invert_rational; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; -use crate::rational::Rational; +use crate::utils::rational::Rational; use crate::transcript::{Hashable, Sampleable, Transcript}; use halo2curves::serde::SerdeObject; diff --git a/src/plonk/vanishing/prover.rs b/src/plonk/vanishing/prover.rs index 4a906d4ab1..c750fdc8fc 100644 --- a/src/plonk/vanishing/prover.rs +++ b/src/plonk/vanishing/prover.rs @@ -9,8 +9,8 @@ use super::Argument; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; use crate::transcript::{Hashable, Transcript}; use crate::{ - arithmetic::{eval_polynomial, parallelize}, - multicore::current_num_threads, + utils::arithmetic::{eval_polynomial, parallelize}, + utils::multicore::current_num_threads, plonk::Error, poly::{Coeff, EvaluationDomain, ExtendedLagrangeCoeff, Polynomial, ProverQuery}, }; diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 417713cc8f..8d0edc61ff 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -3,7 +3,7 @@ use halo2curves::serde::SerdeObject; use std::iter; use super::{vanishing, Error, VerifyingKey}; -use crate::arithmetic::compute_inner_product; +use crate::utils::arithmetic::compute_inner_product; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; use crate::poly::VerifierQuery; use crate::transcript::{read_n, Hashable, Sampleable, Transcript}; diff --git a/src/poly/domain.rs b/src/poly/domain.rs index 2acba8a308..bc181baa6d 100644 --- a/src/poly/domain.rs +++ b/src/poly/domain.rs @@ -1,13 +1,13 @@ //! Contains utilities for performing polynomial arithmetic over an evaluation //! domain that is of a suitable size for the application. -use crate::arithmetic::parallelize; +use crate::utils::arithmetic::parallelize; use super::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation}; use ff::WithSmallOrderMulGroup; use group::ff::{BatchInvert, Field}; -use crate::rational::Rational; +use crate::utils::rational::Rational; use halo2curves::fft::best_fft; use std::marker::PhantomData; @@ -486,7 +486,7 @@ pub struct PinnedEvaluationDomain<'a, F: Field> { fn test_rotate() { use rand_core::OsRng; - use crate::arithmetic::eval_polynomial; + use crate::utils::arithmetic::eval_polynomial; use halo2curves::pasta::pallas::Scalar; let domain = EvaluationDomain::::new(1, 3); @@ -527,7 +527,7 @@ fn test_rotate() { fn test_l_i() { use rand_core::OsRng; - use crate::arithmetic::{eval_polynomial, lagrange_interpolate}; + use crate::utils::arithmetic::{eval_polynomial, lagrange_interpolate}; use halo2curves::pasta::pallas::Scalar; let domain = EvaluationDomain::::new(1, 3); diff --git a/src/poly/kzg/mod.rs b/src/poly/kzg/mod.rs index 5e35c2095d..ba5c20622d 100644 --- a/src/poly/kzg/mod.rs +++ b/src/poly/kzg/mod.rs @@ -8,7 +8,7 @@ pub mod params; use std::fmt::Debug; -use crate::arithmetic::{kate_division, powers, MSM}; +use crate::utils::arithmetic::{kate_division, powers, MSM}; use crate::poly::kzg::msm::{DualMSM, MSMKZG}; use crate::poly::kzg::params::{ParamsKZG, ParamsVerifierKZG}; use crate::poly::query::Query; @@ -212,7 +212,7 @@ where #[cfg(test)] mod tests { - use crate::arithmetic::eval_polynomial; + use crate::utils::arithmetic::eval_polynomial; use crate::poly::commitment::PolynomialCommitmentScheme; use crate::poly::kzg::params::{ParamsKZG, ParamsVerifierKZG}; use crate::poly::kzg::KZGCommitmentScheme; diff --git a/src/poly/kzg/msm.rs b/src/poly/kzg/msm.rs index bcd48282f9..84bf039efb 100644 --- a/src/poly/kzg/msm.rs +++ b/src/poly/kzg/msm.rs @@ -1,8 +1,8 @@ use std::fmt::Debug; use super::params::ParamsKZG; -use crate::arithmetic::parallelize; -use crate::arithmetic::MSM; +use crate::utils::arithmetic::parallelize; +use crate::utils::arithmetic::MSM; use group::{Curve, Group}; use halo2curves::msm::msm_best; use halo2curves::{ diff --git a/src/poly/kzg/params.rs b/src/poly/kzg/params.rs index 39c9db3947..93502190e5 100644 --- a/src/poly/kzg/params.rs +++ b/src/poly/kzg/params.rs @@ -1,7 +1,7 @@ -use crate::arithmetic::{g_to_lagrange, parallelize}; -use crate::helpers::SerdeCurveAffine; +use crate::utils::arithmetic::{g_to_lagrange, parallelize}; +use crate::utils::helpers::SerdeCurveAffine; use crate::poly::{LagrangeCoeff, Polynomial}; -use crate::SerdeFormat; +use crate::utils::SerdeFormat; use ff::{Field, PrimeField}; use group::{prime::PrimeCurveAffine, Curve, Group}; diff --git a/src/poly.rs b/src/poly/mod.rs similarity index 98% rename from src/poly.rs rename to src/poly/mod.rs index bd042c632d..5f68d89fe4 100644 --- a/src/poly.rs +++ b/src/poly/mod.rs @@ -2,10 +2,10 @@ //! various forms, including computing commitments to them and provably opening //! the committed polynomials at arbitrary points. -use crate::arithmetic::parallelize; -use crate::helpers::SerdePrimeField; +use crate::utils::arithmetic::parallelize; +use crate::utils::helpers::SerdePrimeField; // use crate::plonk::Assigned; -use crate::SerdeFormat; +use crate::utils::SerdeFormat; use ff::BatchInvert; use group::ff::Field; @@ -25,7 +25,7 @@ pub mod kzg; pub mod commitment; -use crate::rational::Rational; +use crate::utils::rational::Rational; pub use domain::*; pub use query::{ProverQuery, VerifierQuery}; // pub use strategy::{Guard, VerificationStrategy}; @@ -144,7 +144,7 @@ impl Polynomial { } impl Polynomial { - /// Reads polynomial from buffer using `SerdePrimeField::read`. + /// Reads polynomial from buffer using `SerdePrimeField::read`. pub(crate) fn read(reader: &mut R, format: SerdeFormat) -> io::Result { let mut poly_len = [0u8; 4]; reader.read_exact(&mut poly_len)?; @@ -159,7 +159,7 @@ impl Polynomial { }) } - /// Writes polynomial to buffer using `SerdePrimeField::write`. + /// Writes polynomial to buffer using `SerdePrimeField::write`. pub(crate) fn write( &self, writer: &mut W, diff --git a/src/poly/query.rs b/src/poly/query.rs index 7f022175cc..0b6fd16f1e 100644 --- a/src/poly/query.rs +++ b/src/poly/query.rs @@ -3,7 +3,7 @@ use std::fmt::Debug; use crate::poly::commitment::PolynomialCommitmentScheme; use crate::{ - arithmetic::eval_polynomial, + utils::arithmetic::eval_polynomial, poly::{Coeff, Polynomial}, }; diff --git a/src/transcript.rs b/src/transcript/mod.rs similarity index 100% rename from src/transcript.rs rename to src/transcript/mod.rs diff --git a/src/arithmetic.rs b/src/utils/arithmetic.rs similarity index 100% rename from src/arithmetic.rs rename to src/utils/arithmetic.rs diff --git a/src/helpers.rs b/src/utils/helpers.rs similarity index 100% rename from src/helpers.rs rename to src/utils/helpers.rs diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000000..0658aaf47c --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,8 @@ +//! The utils module contains small, reusable functions + +pub mod arithmetic; +pub mod helpers; +pub(crate) mod multicore; +pub mod rational; + +pub use helpers::SerdeFormat; \ No newline at end of file diff --git a/src/multicore.rs b/src/utils/multicore.rs similarity index 100% rename from src/multicore.rs rename to src/utils/multicore.rs diff --git a/src/rational.rs b/src/utils/rational.rs similarity index 99% rename from src/rational.rs rename to src/utils/rational.rs index e1cc0c7a3e..1a4166be64 100644 --- a/src/rational.rs +++ b/src/utils/rational.rs @@ -449,7 +449,7 @@ mod proptests { ops::{Add, Mul, Neg, Sub}, }; - use crate::rational::Rational; + use crate::utils::rational::Rational; use group::ff::Field; use halo2curves::pasta::Fp; use proptest::{collection::vec, prelude::*, sample::select}; From dd887606ab0530a1ba325322da354d14f8f4e316 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Thu, 19 Dec 2024 10:44:46 +0100 Subject: [PATCH 16/20] Remove issue template --- .github/ISSUE_TEMPLATE/eli15.md | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/eli15.md diff --git a/.github/ISSUE_TEMPLATE/eli15.md b/.github/ISSUE_TEMPLATE/eli15.md deleted file mode 100644 index d9fb8fe550..0000000000 --- a/.github/ISSUE_TEMPLATE/eli15.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -name: ELI15 improvement request -about: Let us know how the Halo 2 book could be improved! -title: 'ELI15: ' -labels: 'ELI15' -assignees: '' - ---- - -## Which section of the Halo 2 book were you reading? - -## What was unclear? - -## What would help to make it clearer to you? - From d9ad7a57d42fd24fa877f33171af86c6e5d1b6b1 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Thu, 19 Dec 2024 10:54:44 +0100 Subject: [PATCH 17/20] Review comments --- benches/plonk.rs | 2 +- examples/serialization.rs | 2 +- examples/shuffle.rs | 2 +- examples/vector-ops-unblinded.rs | 2 +- src/plonk/lookup/verifier.rs | 1 - src/plonk/permutation/prover.rs | 2 -- src/plonk/permutation/verifier.rs | 2 -- src/poly/kzg/mod.rs | 2 +- src/transcript/mod.rs | 17 +++++++---------- src/utils/helpers.rs | 1 - 10 files changed, 12 insertions(+), 21 deletions(-) diff --git a/benches/plonk.rs b/benches/plonk.rs index 9e7cc9c842..ed419421a1 100644 --- a/benches/plonk.rs +++ b/benches/plonk.rs @@ -298,7 +298,7 @@ fn criterion_benchmark(c: &mut Criterion) { vk: &VerifyingKey>, proof: &[u8], ) { - let mut transcript = CircuitTranscript::parse(proof); + let mut transcript = CircuitTranscript::init_from_bytes(proof); assert!( verify_proof::, _>( params, diff --git a/examples/serialization.rs b/examples/serialization.rs index baf33aff7c..287aa061b8 100644 --- a/examples/serialization.rs +++ b/examples/serialization.rs @@ -164,7 +164,7 @@ fn main() { let proof = transcript.finalize(); - let mut transcript = CircuitTranscript::::parse(&proof[..]); + let mut transcript = CircuitTranscript::::init_from_bytes(&proof[..]); assert!(verify_proof::, _>( ¶ms, diff --git a/examples/shuffle.rs b/examples/shuffle.rs index 1aa608dbac..8a594da643 100644 --- a/examples/shuffle.rs +++ b/examples/shuffle.rs @@ -296,7 +296,7 @@ fn test_prover( transcript.finalize() }; - let mut transcript = CircuitTranscript::::parse(&proof[..]); + let mut transcript = CircuitTranscript::::init_from_bytes(&proof[..]); let verifier = verify_proof::, _>( ¶ms, diff --git a/examples/vector-ops-unblinded.rs b/examples/vector-ops-unblinded.rs index 356c0a05ea..72ae05a063 100644 --- a/examples/vector-ops-unblinded.rs +++ b/examples/vector-ops-unblinded.rs @@ -498,7 +498,7 @@ where }; let accepted = { - let mut transcript = CircuitTranscript::::parse(&proof[..]); + let mut transcript = CircuitTranscript::::init_from_bytes(&proof[..]); verify_proof::, _>( ¶ms, diff --git a/src/plonk/lookup/verifier.rs b/src/plonk/lookup/verifier.rs index 92bf9fb21a..411293eae2 100644 --- a/src/plonk/lookup/verifier.rs +++ b/src/plonk/lookup/verifier.rs @@ -172,7 +172,6 @@ impl, CS: PolynomialCommitmentScheme> Evaluated< )) } - // todo: Removing trick from MSMs here pub(in crate::plonk) fn queries( &self, vk: &VerifyingKey, diff --git a/src/plonk/permutation/prover.rs b/src/plonk/permutation/prover.rs index 65152e00ac..ebea470f90 100644 --- a/src/plonk/permutation/prover.rs +++ b/src/plonk/permutation/prover.rs @@ -192,7 +192,6 @@ impl super::ProvingKey { } impl> Committed { - // TODO: I don't quite like the PCS in the function pub(in crate::plonk) fn evaluate>( self, pk: &plonk::ProvingKey, @@ -243,7 +242,6 @@ impl> Committed { } impl> Evaluated { - // todo: ditto - I don't like the PCS here pub(in crate::plonk) fn open<'a, CS: PolynomialCommitmentScheme>( &'a self, pk: &'a plonk::ProvingKey, diff --git a/src/plonk/permutation/verifier.rs b/src/plonk/permutation/verifier.rs index 5b2eac24cb..5afb03db30 100644 --- a/src/plonk/permutation/verifier.rs +++ b/src/plonk/permutation/verifier.rs @@ -212,7 +212,6 @@ impl, CS: PolynomialCommitmentScheme> Evaluated< ) } - // todo: Removing the MSM tricks here. Bring them back pub(in crate::plonk) fn queries<'r>( &'r self, vk: &'r plonk::VerifyingKey, @@ -252,7 +251,6 @@ impl, CS: PolynomialCommitmentScheme> Evaluated< } impl CommonEvaluated { - // todo: MSM trick here pub(in crate::plonk) fn queries<'r, CS: PolynomialCommitmentScheme>( &'r self, vkey: &'r VerifyingKey, diff --git a/src/poly/kzg/mod.rs b/src/poly/kzg/mod.rs index ba5c20622d..c3b17b5d98 100644 --- a/src/poly/kzg/mod.rs +++ b/src/poly/kzg/mod.rs @@ -253,7 +253,7 @@ mod tests { + SerdeObject + Hashable, { - let mut transcript = T::parse(proof); + let mut transcript = T::init_from_bytes(proof); let a: E::G1Affine = transcript.read().unwrap(); let b: E::G1Affine = transcript.read().unwrap(); diff --git a/src/transcript/mod.rs b/src/transcript/mod.rs index 23a73d952e..b43d0642b4 100644 --- a/src/transcript/mod.rs +++ b/src/transcript/mod.rs @@ -23,7 +23,6 @@ means that we need to have a trait for something that is hashable. */ /// Hash function that can be used for transcript -/// todo: This looks pretty similar to our sponge instructions in halo2 circuits. Check it out. pub trait TranscriptHash { /// Input type of the hash function type Input; @@ -59,22 +58,20 @@ pub trait Transcript { fn init() -> Self; /// Parses an existing transcript - /// // todo: call from_bytes? - fn parse(bytes: &[u8]) -> Self; + fn init_from_bytes(bytes: &[u8]) -> Self; - /// Squeeze a challenge of type `T` - /// todo: clarify type `T` in this and other functions + /// Squeeze a challenge of type `T`, which only needs to be `Sampleable` with + /// the corresponding hash function. fn squeeze_challenge>(&mut self) -> T; - /// Writing a commitment to the transcript without writing it to the proof, + /// Writing a hashable element `T` to the transcript without writing it to the proof, /// treating it as a common commitment. fn common>(&mut self, input: &T) -> io::Result<()>; - /// Read a value from the prover. - /// FIXME: Do we want to rely on `SerdeObject`? + /// Read a hashable element `T` from the prover. fn read + SerdeObject>(&mut self) -> io::Result; - /// Write a value to the proof and the transcript. + /// Write a hashable element `T` to the proof and the transcript. fn write + SerdeObject>(&mut self, input: &T) -> io::Result<()>; /// Returns the buffer with the proof @@ -98,7 +95,7 @@ impl Transcript for CircuitTranscript { } } - fn parse(bytes: &[u8]) -> Self { + fn init_from_bytes(bytes: &[u8]) -> Self { Self { state: H::init(), buffer: Cursor::new(bytes.to_vec()), diff --git a/src/utils/helpers.rs b/src/utils/helpers.rs index 8ead1c65b7..9c6f6c6f88 100644 --- a/src/utils/helpers.rs +++ b/src/utils/helpers.rs @@ -1,5 +1,4 @@ //! HELPER FUNCTIONS -//! TODO: CHECK IF WE ACTUALLY WANT TO EXPOSE THIS use crate::poly::Polynomial; use ff::PrimeField; From 428128c44bb80199bbb09e9bcc237bbb5d857ef4 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Thu, 19 Dec 2024 15:34:54 +0100 Subject: [PATCH 18/20] Bring back using the SerdeFormat for keys --- src/circuit/value.rs | 2 +- src/plonk/mod.rs | 29 +++++------- src/plonk/permutation.rs | 22 ++++----- src/poly/commitment.rs | 4 +- src/poly/kzg/params.rs | 22 ++++----- src/poly/mod.rs | 17 +++---- src/utils/helpers.rs | 98 ++++++++++++++-------------------------- 7 files changed, 77 insertions(+), 117 deletions(-) diff --git a/src/circuit/value.rs b/src/circuit/value.rs index c63aa55ae8..3a5508514d 100644 --- a/src/circuit/value.rs +++ b/src/circuit/value.rs @@ -647,7 +647,7 @@ impl Value { /// ``` /// # use halo2curves::pasta::pallas::Base as F; /// use halo2_proofs::{circuit::Value}; - /// use halo2_proofs::rational::Rational; + /// use halo2_proofs::utils::rational::Rational; /// /// let v = Value::known(F::from(2)); /// let v: Value> = v.into(); diff --git a/src/plonk/mod.rs b/src/plonk/mod.rs index 88933526e5..2d7193db95 100644 --- a/src/plonk/mod.rs +++ b/src/plonk/mod.rs @@ -8,10 +8,7 @@ use blake2b_simd::Params as Blake2bParams; use group::ff::FromUniformBytes; -use crate::utils::helpers::{ - byte_length, polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, - SerdePrimeField, -}; +use crate::utils::helpers::{byte_length, polynomial_slice_byte_length, ProcessedSerdeObject, read_polynomial_vec, write_polynomial_slice}; use crate::poly::{ Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, PinnedEvaluationDomain, Polynomial, @@ -62,7 +59,7 @@ const VERSION: u8 = 0x03; impl VerifyingKey where - F: WithSmallOrderMulGroup<3> + SerdePrimeField + FromUniformBytes<64> + SerdeObject, + F: WithSmallOrderMulGroup<3> + SerdeObject + FromUniformBytes<64>, CS: PolynomialCommitmentScheme, CS::Commitment: SerdeObject, { @@ -84,8 +81,7 @@ where writer.write_all(&[*k as u8])?; writer.write_all(&(self.fixed_commitments.len() as u32).to_le_bytes())?; for commitment in &self.fixed_commitments { - // TODO: writting raw here - do we maybe want a wrapper like we had? - commitment.write_raw(writer)?; + commitment.write(writer, format)?; } self.permutation.write(writer, format)?; @@ -143,8 +139,7 @@ where let num_fixed_columns = u32::from_le_bytes(num_fixed_columns); let fixed_commitments: Vec<_> = (0..num_fixed_columns) - // TODO: Fix FORMAT - wrapper like before? - .map(|_| CS::Commitment::read_raw(reader)) + .map(|_| CS::Commitment::read(reader, format)) .collect::>()?; let permutation = permutation::VerifyingKey::read(reader, &cs.permutation, format)?; @@ -328,7 +323,7 @@ where impl, CS: PolynomialCommitmentScheme> ProvingKey where - F: SerdePrimeField + FromUniformBytes<64>, + F: PrimeField + FromUniformBytes<64> + SerdeObject, { /// Writes a proving key to a buffer. /// @@ -342,13 +337,13 @@ where /// Does so by first writing the verifying key and then serializing the rest of the data (in the form of field polynomials) pub fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> { self.vk.write(writer, format)?; - self.l0.write(writer, format)?; - self.l_last.write(writer, format)?; - self.l_active_row.write(writer, format)?; - write_polynomial_slice(&self.fixed_values, writer, format)?; - write_polynomial_slice(&self.fixed_polys, writer, format)?; - write_polynomial_slice(&self.fixed_cosets, writer, format)?; - self.permutation.write(writer, format)?; + self.l0.write(writer)?; + self.l_last.write(writer)?; + self.l_active_row.write(writer)?; + write_polynomial_slice(&self.fixed_values, writer)?; + write_polynomial_slice(&self.fixed_polys, writer)?; + write_polynomial_slice(&self.fixed_cosets, writer)?; + self.permutation.write(writer)?; Ok(()) } diff --git a/src/plonk/permutation.rs b/src/plonk/permutation.rs index 6fd99c21a8..8555d7bb0a 100644 --- a/src/plonk/permutation.rs +++ b/src/plonk/permutation.rs @@ -3,7 +3,7 @@ use super::circuit::{Any, Column}; use crate::{ utils::helpers::{ - polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, SerdePrimeField, + polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, }, poly::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial}, utils::SerdeFormat, @@ -15,7 +15,7 @@ pub(crate) mod verifier; pub use keygen::Assembly; -use crate::utils::helpers::byte_length; +use crate::utils::helpers::{byte_length, ProcessedSerdeObject}; use crate::poly::commitment::PolynomialCommitmentScheme; use ff::PrimeField; use halo2curves::serde::SerdeObject; @@ -95,13 +95,12 @@ impl> VerifyingKey { &self.commitments } - pub(crate) fn write(&self, writer: &mut W, _format: SerdeFormat) -> io::Result<()> + pub(crate) fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> where CS::Commitment: SerdeObject, { - // todo: How to handle formats? for commitment in &self.commitments { - commitment.write_raw(writer)?; + commitment.write(writer, format)?; } Ok(()) } @@ -109,13 +108,13 @@ impl> VerifyingKey { pub(crate) fn read( reader: &mut R, argument: &Argument, - _format: SerdeFormat, + format: SerdeFormat, ) -> io::Result where CS::Commitment: SerdeObject, { let commitments = (0..argument.columns.len()) - .map(|_| CS::Commitment::read_raw(reader)) + .map(|_| CS::Commitment::read(reader, format)) .collect::, _>>()?; Ok(VerifyingKey { commitments }) } @@ -136,7 +135,7 @@ pub(crate) struct ProvingKey { pub(super) cosets: Vec>, } -impl ProvingKey { +impl ProvingKey { /// Reads proving key for a single permutation argument from buffer using `Polynomial::read`. pub(super) fn read(reader: &mut R, format: SerdeFormat) -> io::Result { let permutations = read_polynomial_vec(reader, format)?; @@ -153,11 +152,10 @@ impl ProvingKey { pub(super) fn write( &self, writer: &mut W, - format: SerdeFormat, ) -> io::Result<()> { - write_polynomial_slice(&self.permutations, writer, format)?; - write_polynomial_slice(&self.polys, writer, format)?; - write_polynomial_slice(&self.cosets, writer, format)?; + write_polynomial_slice(&self.permutations, writer)?; + write_polynomial_slice(&self.polys, writer)?; + write_polynomial_slice(&self.cosets, writer)?; Ok(()) } } diff --git a/src/poly/commitment.rs b/src/poly/commitment.rs index a69de276be..66be2076e0 100644 --- a/src/poly/commitment.rs +++ b/src/poly/commitment.rs @@ -2,8 +2,8 @@ use crate::poly::{Coeff, Error, LagrangeCoeff, Polynomial, ProverQuery, VerifierQuery}; use crate::transcript::{Hashable, Sampleable, Transcript}; use ff::PrimeField; -use halo2curves::serde::SerdeObject; use std::fmt::Debug; +use crate::utils::helpers::ProcessedSerdeObject; /// Public interface for a Polynomial Commitment Scheme (PCS) pub trait PolynomialCommitmentScheme: Clone + Debug { @@ -14,7 +14,7 @@ pub trait PolynomialCommitmentScheme: Clone + Debug { type VerifierParameters: Params; /// Type of a committed polynomial - type Commitment: Clone + Copy + Debug + Default + PartialEq + SerdeObject + Send + Sync; + type Commitment: Clone + Copy + Debug + Default + PartialEq + ProcessedSerdeObject + Send + Sync; /// Setup the parameters for the PCS fn setup(k: u32) -> Self::Parameters; diff --git a/src/poly/kzg/params.rs b/src/poly/kzg/params.rs index 93502190e5..6758461a36 100644 --- a/src/poly/kzg/params.rs +++ b/src/poly/kzg/params.rs @@ -1,5 +1,4 @@ use crate::utils::arithmetic::{g_to_lagrange, parallelize}; -use crate::utils::helpers::SerdeCurveAffine; use crate::poly::{LagrangeCoeff, Polynomial}; use crate::utils::SerdeFormat; @@ -15,6 +14,7 @@ use halo2curves::msm::msm_best; use halo2curves::serde::SerdeObject; use halo2curves::CurveAffine; use std::io; +use crate::utils::helpers::ProcessedSerdeObject; use super::msm::MSMKZG; @@ -160,8 +160,8 @@ impl ParamsKZG { /// Writes parameters to buffer pub fn write_custom(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> where - E::G1Affine: SerdeCurveAffine, - E::G2Affine: SerdeCurveAffine, + E::G1Affine: CurveAffine + ProcessedSerdeObject, + E::G2Affine: CurveAffine + ProcessedSerdeObject, { writer.write_all(&self.k.to_le_bytes())?; for el in self.g.iter() { @@ -178,8 +178,8 @@ impl ParamsKZG { /// Reads params from a buffer. pub fn read_custom(reader: &mut R, format: SerdeFormat) -> io::Result where - E::G1Affine: SerdeCurveAffine, - E::G2Affine: SerdeCurveAffine, + E::G1Affine: CurveAffine + ProcessedSerdeObject, + E::G2Affine: CurveAffine + ProcessedSerdeObject, { let mut k = [0u8; 4]; reader.read_exact(&mut k[..])?; @@ -230,20 +230,20 @@ impl ParamsKZG { } SerdeFormat::RawBytes => { let g = (0..n) - .map(|_| ::read(reader, format)) + .map(|_| ::read(reader, format)) .collect::, _>>()?; let g_lagrange = (0..n) - .map(|_| ::read(reader, format)) + .map(|_| ::read(reader, format)) .collect::, _>>()?; (g, g_lagrange) } SerdeFormat::RawBytesUnchecked => { // avoid try branching for performance let g = (0..n) - .map(|_| ::read(reader, format).unwrap()) + .map(|_| ::read(reader, format).unwrap()) .collect::>(); let g_lagrange = (0..n) - .map(|_| ::read(reader, format).unwrap()) + .map(|_| ::read(reader, format).unwrap()) .collect::>(); (g, g_lagrange) } @@ -270,8 +270,8 @@ pub type ParamsVerifierKZG = ParamsKZG; impl<'params, E: Engine + Debug> ParamsKZG where - E::G1Affine: SerdeCurveAffine, - E::G2Affine: SerdeCurveAffine, + E::G1Affine: CurveAffine + ProcessedSerdeObject, + E::G2Affine: CurveAffine + ProcessedSerdeObject, { /// TODO pub fn k(&self) -> u32 { diff --git a/src/poly/mod.rs b/src/poly/mod.rs index 5f68d89fe4..8bf2e625c0 100644 --- a/src/poly/mod.rs +++ b/src/poly/mod.rs @@ -3,22 +3,18 @@ //! the committed polynomials at arbitrary points. use crate::utils::arithmetic::parallelize; -use crate::utils::helpers::SerdePrimeField; -// use crate::plonk::Assigned; use crate::utils::SerdeFormat; -use ff::BatchInvert; +use ff::{BatchInvert, PrimeField}; use group::ff::Field; use std::fmt::Debug; use std::io; use std::marker::PhantomData; use std::ops::{Add, Deref, DerefMut, Index, IndexMut, Mul, RangeFrom, RangeFull, Sub}; +use halo2curves::serde::SerdeObject; -// /// Generic commitment scheme structures -// pub mod commitment; mod domain; mod query; -// mod strategy; /// KZG commitment scheme pub mod kzg; @@ -28,7 +24,7 @@ pub mod commitment; use crate::utils::rational::Rational; pub use domain::*; pub use query::{ProverQuery, VerifierQuery}; -// pub use strategy::{Guard, VerificationStrategy}; +use crate::utils::helpers::{read_f}; /// This is an error that could occur during proving or circuit synthesis. // TODO: these errors need to be cleaned up @@ -143,7 +139,7 @@ impl Polynomial { } } -impl Polynomial { +impl Polynomial { /// Reads polynomial from buffer using `SerdePrimeField::read`. pub(crate) fn read(reader: &mut R, format: SerdeFormat) -> io::Result { let mut poly_len = [0u8; 4]; @@ -151,7 +147,7 @@ impl Polynomial { let poly_len = u32::from_be_bytes(poly_len); (0..poly_len) - .map(|_| F::read(reader, format)) + .map(|_| read_f(reader, format)) .collect::>>() .map(|values| Self { values, @@ -163,11 +159,10 @@ impl Polynomial { pub(crate) fn write( &self, writer: &mut W, - format: SerdeFormat, ) -> io::Result<()> { writer.write_all(&(self.values.len() as u32).to_be_bytes())?; for value in self.values.iter() { - value.write(writer, format)?; + value.write_raw(writer)?; } Ok(()) } diff --git a/src/utils/helpers.rs b/src/utils/helpers.rs index 9c6f6c6f88..3b98514adf 100644 --- a/src/utils/helpers.rs +++ b/src/utils/helpers.rs @@ -5,6 +5,7 @@ use ff::PrimeField; use group::prime::PrimeCurveAffine; use halo2curves::serde::SerdeObject; use std::io; +use group::GroupEncoding; /// This enum specifies how various types are serialized and deserialized. #[derive(Clone, Copy, Debug)] @@ -22,29 +23,41 @@ pub enum SerdeFormat { RawBytesUnchecked, } +/// Interface for Serde objects that can be represented in compressed form. +pub trait ProcessedSerdeObject: SerdeObject + Default { + /// Reads an element from the buffer and parses it according to the `format`: + /// - `Processed`: Reads a compressed element and decompress it + /// - `RawBytes`: Reads an uncompressed element and checks its correctness + /// - `RawBytesUnchecked`: Reads an uncompressed element without performing any checks + fn read(reader: &mut R, format: SerdeFormat) -> io::Result; + + /// Writes an element according to `format`: + /// - `Processed`: Writes a compressed element + /// - Otherwise: Writes an uncompressed element + fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()>; +} + /// Byte length of an affine curve element according to `format`. -pub fn byte_length(format: SerdeFormat) -> usize { +pub fn byte_length(format: SerdeFormat) -> usize { match format { SerdeFormat::Processed => T::default().to_raw_bytes().len(), _ => T::default().to_raw_bytes().len() * 2, } } -// Keep this trait for compatibility with IPA serialization -pub(crate) trait CurveRead: PrimeCurveAffine { - /// Reads a compressed element from the buffer and attempts to parse it - /// using `from_bytes`. - fn read(reader: &mut R) -> io::Result { - let mut compressed = Self::Repr::default(); - reader.read_exact(compressed.as_mut())?; - Option::from(Self::from_bytes(&compressed)) - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid point encoding in proof")) - } +/// Helper function to read a field element with a serde format. There is no way to compress field +/// elements, so `Processed` and `RawBytes` act equivalently. +pub(crate) fn read_f(reader: &mut R, format: SerdeFormat) -> io::Result { + match format { + SerdeFormat::Processed => {::read_raw(reader)} + SerdeFormat::RawBytes => {::read_raw(reader)} + SerdeFormat::RawBytesUnchecked => {Ok(::read_raw_unchecked(reader))} + } } -impl CurveRead for C {} /// Trait for serialising SerdeObjects -pub trait SerdeCurveAffine: PrimeCurveAffine + SerdeObject + Default { +impl ProcessedSerdeObject for C +{ /// Reads an element from the buffer and parses it according to the `format`: /// - `Processed`: Reads a compressed curve element and decompress it /// - `RawBytes`: Reads an uncompressed curve element with coordinates in Montgomery form. @@ -53,7 +66,12 @@ pub trait SerdeCurveAffine: PrimeCurveAffine + SerdeObject + Default { /// does not perform any checks fn read(reader: &mut R, format: SerdeFormat) -> io::Result { match format { - SerdeFormat::Processed => ::read(reader), + SerdeFormat::Processed => { + let mut compressed = ::Repr::default(); + reader.read_exact(compressed.as_mut())?; + Option::from(Self::from_bytes(&compressed)) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid point encoding in proof")) + }, SerdeFormat::RawBytes => ::read_raw(reader), SerdeFormat::RawBytesUnchecked => Ok(::read_raw_unchecked(reader)), } @@ -67,52 +85,7 @@ pub trait SerdeCurveAffine: PrimeCurveAffine + SerdeObject + Default { _ => self.write_raw(writer), } } - - /// Byte length of an affine curve element according to `format`. - fn byte_length(format: SerdeFormat) -> usize { - match format { - SerdeFormat::Processed => Self::default().to_bytes().as_ref().len(), - _ => Self::Repr::default().as_ref().len() * 2, - } - } -} -impl SerdeCurveAffine for C {} - -/// Trait for implementing field SerdeObjects -pub trait SerdePrimeField: PrimeField + SerdeObject { - /// Reads a field element as bytes from the buffer according to the `format`: - /// - `Processed`: Reads a field element in standard form, with endianness specified by the - /// `PrimeField` implementation, and checks that the element is less than the modulus. - /// - `RawBytes`: Reads a field element from raw bytes in its internal Montgomery representations, - /// and checks that the element is less than the modulus. - /// - `RawBytesUnchecked`: Reads a field element in Montgomery form and performs no checks. - fn read(reader: &mut R, format: SerdeFormat) -> io::Result { - match format { - SerdeFormat::Processed => { - let mut compressed = Self::Repr::default(); - reader.read_exact(compressed.as_mut())?; - Option::from(Self::from_repr(compressed)).ok_or_else(|| { - io::Error::new(io::ErrorKind::Other, "Invalid prime field point encoding") - }) - } - SerdeFormat::RawBytes => ::read_raw(reader), - SerdeFormat::RawBytesUnchecked => Ok(::read_raw_unchecked(reader)), - } - } - - /// Writes a field element as bytes to the buffer according to the `format`: - /// - `Processed`: Writes a field element in standard form, with endianness specified by the - /// `PrimeField` implementation. - /// - Otherwise: Writes a field element into raw bytes in its internal Montgomery representation, - /// WITHOUT performing the expensive Montgomery reduction. - fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> { - match format { - SerdeFormat::Processed => writer.write_all(self.to_repr().as_ref()), - _ => self.write_raw(writer), - } - } } -impl SerdePrimeField for F {} /// Convert a slice of `bool` into a `u8`. /// @@ -134,7 +107,7 @@ pub fn unpack(byte: u8, bits: &mut [bool]) { } /// Reads a vector of polynomials from buffer -pub(crate) fn read_polynomial_vec( +pub(crate) fn read_polynomial_vec( reader: &mut R, format: SerdeFormat, ) -> io::Result>> { @@ -148,14 +121,13 @@ pub(crate) fn read_polynomial_vec( } /// Writes a slice of polynomials to buffer -pub(crate) fn write_polynomial_slice( +pub(crate) fn write_polynomial_slice( slice: &[Polynomial], writer: &mut W, - format: SerdeFormat, ) -> io::Result<()> { writer.write_all(&(slice.len() as u32).to_be_bytes())?; for poly in slice.iter() { - poly.write(writer, format)?; + poly.write(writer)?; } Ok(()) } From ddcb294d2f6e81732450c6e139f00fb7db0bec14 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Thu, 19 Dec 2024 16:16:41 +0100 Subject: [PATCH 19/20] Address review comments --- benches/plonk.rs | 2 +- examples/proof-size.rs | 2 +- examples/shuffle.rs | 346 ---------- examples/simple-example.rs | 2 +- examples/two-chip.rs | 2 +- examples/vector-mul.rs | 2 +- examples/vector-ops-unblinded.rs | 2 +- src/circuit/mod.rs | 10 +- src/dev/mod.rs | 152 ++-- src/lib.rs | 1 - src/plonk/evaluation.rs | 4 +- src/plonk/keygen.rs | 2 +- src/plonk/lookup/prover.rs | 2 +- src/plonk/mod.rs | 15 +- src/plonk/permutation.rs | 11 +- src/plonk/permutation/keygen.rs | 2 +- src/plonk/permutation/prover.rs | 2 +- src/plonk/prover.rs | 4 +- src/plonk/vanishing/prover.rs | 4 +- src/plonk/verifier.rs | 2 +- src/poly/commitment.rs | 2 +- src/poly/kzg/mod.rs | 4 +- src/poly/kzg/params.rs | 4 +- src/poly/mod.rs | 9 +- src/poly/query.rs | 24 +- src/utils/helpers.rs | 27 +- src/utils/mod.rs | 2 +- tests/plonk_api.rs | 1107 +++++++++++++++--------------- 28 files changed, 684 insertions(+), 1064 deletions(-) delete mode 100644 examples/shuffle.rs diff --git a/benches/plonk.rs b/benches/plonk.rs index ed419421a1..7f1d191142 100644 --- a/benches/plonk.rs +++ b/benches/plonk.rs @@ -12,8 +12,8 @@ use std::marker::PhantomData; use criterion::{BenchmarkId, Criterion}; use halo2_proofs::poly::kzg::{params::ParamsKZG, KZGCommitmentScheme}; -use halo2_proofs::utils::rational::Rational; use halo2_proofs::transcript::{CircuitTranscript, Transcript}; +use halo2_proofs::utils::rational::Rational; fn criterion_benchmark(c: &mut Criterion) { /// This represents an advice column at a certain row in the ConstraintSystem diff --git a/examples/proof-size.rs b/examples/proof-size.rs index db467b4d24..9b92a3b4e3 100644 --- a/examples/proof-size.rs +++ b/examples/proof-size.rs @@ -88,7 +88,7 @@ const K: u32 = 11; fn main() { let circuit = TestCircuit {}; - let model = from_circuit_to_model_circuit::<_, _, 56, 56>( + let model = from_circuit_to_model_circuit::<_, _, 32, 32>( Some(K), &circuit, vec![], diff --git a/examples/shuffle.rs b/examples/shuffle.rs deleted file mode 100644 index 8a594da643..0000000000 --- a/examples/shuffle.rs +++ /dev/null @@ -1,346 +0,0 @@ -use blake2b_simd::State; -use ff::{BatchInvert, FromUniformBytes, WithSmallOrderMulGroup}; -use halo2_proofs::poly::kzg::{params::ParamsKZG, KZGCommitmentScheme}; -use halo2_proofs::transcript::{CircuitTranscript, Hashable, Sampleable, Transcript}; -use halo2_proofs::{ - utils::arithmetic::Field, - circuit::{floor_planner::V1, Layouter, Value}, - dev::{metadata, FailureLocation, MockProver, VerifyFailure}, - plonk::*, -}; -use halo2curves::pairing::MultiMillerLoop; -use halo2curves::serde::SerdeObject; -use halo2curves::{bn256, pairing::Engine, CurveAffine}; -use rand_core::{OsRng, RngCore}; -use std::iter; - -fn rand_2d_array(rng: &mut R) -> [[F; H]; W] { - [(); W].map(|_| [(); H].map(|_| F::random(&mut *rng))) -} - -fn shuffled( - original: [[F; H]; W], - rng: &mut R, -) -> [[F; H]; W] { - let mut shuffled = original; - - for row in (1..H).rev() { - let rand_row = (rng.next_u32() as usize) % row; - for column in shuffled.iter_mut() { - column.swap(row, rand_row); - } - } - - shuffled -} - -#[derive(Clone)] -struct MyConfig { - q_shuffle: Selector, - q_first: Selector, - q_last: Selector, - original: [Column; W], - shuffled: [Column; W], - theta: Challenge, - gamma: Challenge, - z: Column, -} - -impl MyConfig { - fn configure(meta: &mut ConstraintSystem) -> Self { - let [q_shuffle, q_first, q_last] = [(); 3].map(|_| meta.selector()); - // First phase - let original = [(); W].map(|_| meta.advice_column_in(FirstPhase)); - let shuffled = [(); W].map(|_| meta.advice_column_in(FirstPhase)); - let [theta, gamma] = [(); 2].map(|_| meta.challenge_usable_after(FirstPhase)); - // Second phase - let z = meta.advice_column_in(SecondPhase); - - meta.create_gate("z should start with 1", |_| { - let one = Expression::Constant(F::ONE); - - vec![q_first.expr() * (one - z.cur())] - }); - - meta.create_gate("z should end with 1", |_| { - let one = Expression::Constant(F::ONE); - - vec![q_last.expr() * (one - z.cur())] - }); - - meta.create_gate("z should have valid transition", |_| { - let q_shuffle = q_shuffle.expr(); - let original = original.map(|advice| advice.cur()); - let shuffled = shuffled.map(|advice| advice.cur()); - let [theta, gamma] = [theta, gamma].map(|challenge| challenge.expr()); - - // Compress - let original = original - .iter() - .cloned() - .reduce(|acc, a| acc * theta.clone() + a) - .unwrap(); - let shuffled = shuffled - .iter() - .cloned() - .reduce(|acc, a| acc * theta.clone() + a) - .unwrap(); - - vec![q_shuffle * (z.cur() * (original + gamma.clone()) - z.next() * (shuffled + gamma))] - }); - - Self { - q_shuffle, - q_first, - q_last, - original, - shuffled, - theta, - gamma, - z, - } - } -} - -#[derive(Clone, Default)] -struct MyCircuit { - original: Value<[[F; H]; W]>, - shuffled: Value<[[F; H]; W]>, -} - -impl MyCircuit { - fn rand(rng: &mut R) -> Self { - let original = rand_2d_array::(rng); - let shuffled = shuffled(original, rng); - - Self { - original: Value::known(original), - shuffled: Value::known(shuffled), - } - } -} - -impl Circuit for MyCircuit { - type Config = MyConfig; - type FloorPlanner = V1; - #[cfg(feature = "circuit-params")] - type Params = (); - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - MyConfig::configure(meta) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let theta = layouter.get_challenge(config.theta); - let gamma = layouter.get_challenge(config.gamma); - - layouter.assign_region( - || "Shuffle original into shuffled", - |mut region| { - // Keygen - config.q_first.enable(&mut region, 0)?; - config.q_last.enable(&mut region, H)?; - for offset in 0..H { - config.q_shuffle.enable(&mut region, offset)?; - } - - // First phase - for (idx, (&column, values)) in config - .original - .iter() - .zip(self.original.transpose_array().iter()) - .enumerate() - { - for (offset, &value) in values.transpose_array().iter().enumerate() { - region.assign_advice( - || format!("original[{idx}][{offset}]"), - column, - offset, - || value, - )?; - } - } - for (idx, (&column, values)) in config - .shuffled - .iter() - .zip(self.shuffled.transpose_array().iter()) - .enumerate() - { - for (offset, &value) in values.transpose_array().iter().enumerate() { - region.assign_advice( - || format!("shuffled[{idx}][{offset}]"), - column, - offset, - || value, - )?; - } - } - - // Second phase - let z = self.original.zip(self.shuffled).zip(theta).zip(gamma).map( - |(((original, shuffled), theta), gamma)| { - let mut product = vec![F::ZERO; H]; - for (idx, product) in product.iter_mut().enumerate() { - let mut compressed = F::ZERO; - for value in shuffled.iter() { - compressed *= theta; - compressed += value[idx]; - } - - *product = compressed + gamma - } - - product.iter_mut().batch_invert(); - - for (idx, product) in product.iter_mut().enumerate() { - let mut compressed = F::ZERO; - for value in original.iter() { - compressed *= theta; - compressed += value[idx]; - } - - *product *= compressed + gamma - } - - #[allow(clippy::let_and_return)] - let z = iter::once(F::ONE) - .chain(product) - .scan(F::ONE, |state, cur| { - *state *= &cur; - Some(*state) - }) - .collect::>(); - - #[cfg(feature = "sanity-checks")] - assert_eq!(F::ONE, *z.last().unwrap()); - - z - }, - ); - for (offset, value) in z.transpose_vec(H + 1).into_iter().enumerate() { - region.assign_advice(|| format!("z[{offset}]"), config.z, offset, || value)?; - } - - Ok(()) - }, - ) - } -} - -fn test_mock_prover, const W: usize, const H: usize>( - k: u32, - circuit: MyCircuit, - expected: Result<(), Vec<(metadata::Constraint, FailureLocation)>>, -) { - let prover = MockProver::run(k, &circuit, vec![]).unwrap(); - match (prover.verify(), expected) { - (Ok(_), Ok(_)) => {} - (Err(err), Err(expected)) => { - assert_eq!( - err.into_iter() - .map(|failure| match failure { - VerifyFailure::ConstraintNotSatisfied { - constraint, - location, - .. - } => (constraint, location), - _ => panic!("MockProver::verify has result unmatching expected"), - }) - .collect::>(), - expected - ) - } - (_, _) => panic!("MockProver::verify has result unmatching expected"), - }; -} - -fn test_prover( - k: u32, - circuit: MyCircuit, - expected: bool, -) where - E::G1Affine: - Default + SerdeObject + Hashable + CurveAffine, - E::Fr: WithSmallOrderMulGroup<3> - + FromUniformBytes<64> - + SerdeObject - + Sampleable - + Hashable - + Ord, -{ - let params = ParamsKZG::::new(k); - let vk = keygen_vk(¶ms, &circuit).unwrap(); - let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); - - let proof = { - let mut transcript = CircuitTranscript::::init(); - - create_proof::, _, _>( - ¶ms, - &pk, - &[circuit], - &[&[]], - OsRng, - &mut transcript, - ) - .expect("proof generation should not fail"); - - transcript.finalize() - }; - - let mut transcript = CircuitTranscript::::init_from_bytes(&proof[..]); - - let verifier = verify_proof::, _>( - ¶ms, - pk.get_vk(), - &[&[]], - &mut transcript, - ); - - assert_eq!(verifier.is_ok(), expected); -} - -fn main() { - const W: usize = 4; - const H: usize = 32; - const K: u32 = 8; - - let circuit = &MyCircuit::<_, W, H>::rand(&mut OsRng); - - { - test_mock_prover(K, circuit.clone(), Ok(())); - test_prover::(K, circuit.clone(), true); - } - - #[cfg(not(feature = "sanity-checks"))] - { - use std::ops::IndexMut; - - let mut circuit = circuit.clone(); - circuit.shuffled = circuit.shuffled.map(|mut shuffled| { - shuffled.index_mut(0).swap(0, 1); - shuffled - }); - - test_mock_prover( - K, - circuit.clone(), - Err(vec![( - ((1, "z should end with 1").into(), 0, "").into(), - FailureLocation::InRegion { - region: (0, "Shuffle original into shuffled").into(), - offset: 32, - }, - )]), - ); - test_prover::(K, circuit, false); - } -} diff --git a/examples/simple-example.rs b/examples/simple-example.rs index 4666124bf1..45ab6675b7 100644 --- a/examples/simple-example.rs +++ b/examples/simple-example.rs @@ -1,10 +1,10 @@ use std::marker::PhantomData; use halo2_proofs::{ - utils::arithmetic::Field, circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector}, poly::Rotation, + utils::arithmetic::Field, }; // ANCHOR: instructions diff --git a/examples/two-chip.rs b/examples/two-chip.rs index 97f37e8cc9..60dac6bb12 100644 --- a/examples/two-chip.rs +++ b/examples/two-chip.rs @@ -1,10 +1,10 @@ use std::marker::PhantomData; use halo2_proofs::{ - utils::arithmetic::Field, circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, poly::Rotation, + utils::arithmetic::Field, }; // ANCHOR: field-instructions diff --git a/examples/vector-mul.rs b/examples/vector-mul.rs index bfeb3d5fb8..791a52c37b 100644 --- a/examples/vector-mul.rs +++ b/examples/vector-mul.rs @@ -1,10 +1,10 @@ use std::marker::PhantomData; use halo2_proofs::{ - utils::arithmetic::Field, circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, poly::Rotation, + utils::arithmetic::Field, }; // ANCHOR: instructions diff --git a/examples/vector-ops-unblinded.rs b/examples/vector-ops-unblinded.rs index 72ae05a063..136c34b3c3 100644 --- a/examples/vector-ops-unblinded.rs +++ b/examples/vector-ops-unblinded.rs @@ -8,10 +8,10 @@ use ff::{FromUniformBytes, WithSmallOrderMulGroup}; use halo2_proofs::poly::kzg::{params::ParamsKZG, KZGCommitmentScheme}; use halo2_proofs::transcript::{CircuitTranscript, Hashable, Sampleable, Transcript}; use halo2_proofs::{ - utils::arithmetic::Field, circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, plonk::*, poly::Rotation, + utils::arithmetic::Field, }; use halo2curves::pairing::{Engine, MultiMillerLoop}; use halo2curves::serde::SerdeObject; diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index d89fec0f73..29c8f856f9 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -133,7 +133,7 @@ impl AssignedCell { impl AssignedCell where - for<'v> Rational: From<&'v V>, + for<'v> Rational: From<&'v V>, { /// Returns the field element value of the [`AssignedCell`]. pub fn value_field(&self) -> Value> { @@ -157,7 +157,7 @@ impl AssignedCell, F> { impl AssignedCell where - for<'v> Rational: From<&'v V>, + for<'v> Rational: From<&'v V>, { /// Copies the value to a given advice cell and constrains them to be equal. /// @@ -281,9 +281,9 @@ impl<'r, F: Field> Region<'r, F> { constant: VR, ) -> Result, Error> where - for<'vr> Rational: From<&'vr VR>, - A: Fn() -> AR, - AR: Into, + for<'vr> Rational: From<&'vr VR>, + A: Fn() -> AR, + AR: Into, { let cell = self.region.assign_advice_from_constant( &|| annotation().into(), diff --git a/src/dev/mod.rs b/src/dev/mod.rs index 2e1f4d9cfe..a7093472a2 100644 --- a/src/dev/mod.rs +++ b/src/dev/mod.rs @@ -151,10 +151,10 @@ impl Mul for Value { // If poison is multiplied by zero, then we treat the poison as unconstrained // and we don't propagate it. (Value::Real(x), Value::Poison) | (Value::Poison, Value::Real(x)) - if x.is_zero_vartime() => - { - Value::Real(F::ZERO) - } + if x.is_zero_vartime() => + { + Value::Real(F::ZERO) + } _ => Value::Poison, } } @@ -683,8 +683,8 @@ impl + Ord> MockProver { hash = blake2b(&hash).as_bytes().try_into().unwrap(); F::from_uniform_bytes(&hash) }) - .take(cs.num_challenges) - .collect() + .take(cs.num_challenges) + .collect() }; let mut prover = MockProver { @@ -825,12 +825,12 @@ impl + Ord> MockProver { // Check that it was assigned! if r.cells.contains_key(&(cell.column, cell_row)) || gate.polynomials().par_iter().all(|expr| { - self.cell_is_irrelevant( - cell, - expr, - gate_row as usize, - ) - }) + self.cell_is_irrelevant( + cell, + expr, + gate_row as usize, + ) + }) { None } else { @@ -871,70 +871,70 @@ impl + Ord> MockProver { .clone() .into_par_iter() .chain(blinding_rows.into_par_iter())) - .flat_map(move |row| { - let row = row as i32 + n; - gate.polynomials() - .iter() - .enumerate() - .filter_map(move |(poly_index, poly)| { - match poly.evaluate_lazy( - &|scalar| Value::Real(scalar), - &|_| panic!("virtual selectors are removed during optimization"), - &util::load(n, row, &self.cs.fixed_queries, &self.fixed), - &util::load(n, row, &self.cs.advice_queries, &self.advice), - &util::load_instance( - n, - row, - &self.cs.instance_queries, - &self.instance, + .flat_map(move |row| { + let row = row as i32 + n; + gate.polynomials() + .iter() + .enumerate() + .filter_map(move |(poly_index, poly)| { + match poly.evaluate_lazy( + &|scalar| Value::Real(scalar), + &|_| panic!("virtual selectors are removed during optimization"), + &util::load(n, row, &self.cs.fixed_queries, &self.fixed), + &util::load(n, row, &self.cs.advice_queries, &self.advice), + &util::load_instance( + n, + row, + &self.cs.instance_queries, + &self.instance, + ), + &|challenge| Value::Real(self.challenges[challenge.index()]), + &|a| -a, + &|a, b| a + b, + &|a, b| a * b, + &|a, scalar| a * scalar, + &Value::Real(F::ZERO), + ) { + Value::Real(x) if x.is_zero_vartime() => None, + Value::Real(_) => Some(VerifyFailure::ConstraintNotSatisfied { + constraint: ( + (gate_index, gate.name()).into(), + poly_index, + gate.constraint_name(poly_index), + ) + .into(), + location: FailureLocation::find_expressions( + &self.cs, + &self.regions, + (row - n) as usize, + Some(poly).into_iter(), ), - &|challenge| Value::Real(self.challenges[challenge.index()]), - &|a| -a, - &|a, b| a + b, - &|a, b| a * b, - &|a, scalar| a * scalar, - &Value::Real(F::ZERO), - ) { - Value::Real(x) if x.is_zero_vartime() => None, - Value::Real(_) => Some(VerifyFailure::ConstraintNotSatisfied { - constraint: ( - (gate_index, gate.name()).into(), - poly_index, - gate.constraint_name(poly_index), - ) - .into(), - location: FailureLocation::find_expressions( - &self.cs, - &self.regions, - (row - n) as usize, - Some(poly).into_iter(), - ), - cell_values: util::cell_values( - gate, - poly, - &util::load(n, row, &self.cs.fixed_queries, &self.fixed), - &util::load(n, row, &self.cs.advice_queries, &self.advice), - &util::load_instance( - n, - row, - &self.cs.instance_queries, - &self.instance, - ), + cell_values: util::cell_values( + gate, + poly, + &util::load(n, row, &self.cs.fixed_queries, &self.fixed), + &util::load(n, row, &self.cs.advice_queries, &self.advice), + &util::load_instance( + n, + row, + &self.cs.instance_queries, + &self.instance, ), - }), - Value::Poison => Some(VerifyFailure::ConstraintPoisoned { - constraint: ( - (gate_index, gate.name()).into(), - poly_index, - gate.constraint_name(poly_index), - ) - .into(), - }), - } - }) - .collect::>() - }) - .collect::>() + ), + }), + Value::Poison => Some(VerifyFailure::ConstraintPoisoned { + constraint: ( + (gate_index, gate.name()).into(), + poly_index, + gate.constraint_name(poly_index), + ) + .into(), + }), + } + }) + .collect::>() + }) + .collect::>() }); let load = |expression: &Expression, row| { @@ -1190,7 +1190,7 @@ impl + Ord> MockProver { (self.expr_is_constantly_zero(e1, offset) || self.expr_is_constantly_zero(e2, offset)) || (self.cell_is_irrelevant(cell, e1, offset) - && self.cell_is_irrelevant(cell, e2, offset)) + && self.cell_is_irrelevant(cell, e2, offset)) } Expression::Scaled(e, factor) => { factor.is_zero().into() || self.cell_is_irrelevant(cell, e, offset) @@ -1537,7 +1537,7 @@ mod tests { Fp::from(6u64), ]], ) - .unwrap(); + .unwrap(); assert_eq!( prover.verify(), Err(vec![VerifyFailure::Lookup { diff --git a/src/lib.rs b/src/lib.rs index c57687a2d4..3ab96252c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,5 @@ pub mod plonk; pub mod poly; pub mod transcript; - pub mod dev; pub mod utils; diff --git a/src/plonk/evaluation.rs b/src/plonk/evaluation.rs index 5ff7ee5f2f..e8c2b86d96 100644 --- a/src/plonk/evaluation.rs +++ b/src/plonk/evaluation.rs @@ -1,10 +1,10 @@ -use crate::utils::multicore; use crate::plonk::{lookup, permutation, Any, ProvingKey}; use crate::poly::commitment::PolynomialCommitmentScheme; use crate::poly::Basis; +use crate::utils::multicore; use crate::{ - utils::arithmetic::parallelize, poly::{Coeff, ExtendedLagrangeCoeff, Polynomial, Rotation}, + utils::arithmetic::parallelize, }; use ff::{PrimeField, WithSmallOrderMulGroup}; use group::ff::Field; diff --git a/src/plonk/keygen.rs b/src/plonk/keygen.rs index 14c3d091e4..fa57ada327 100644 --- a/src/plonk/keygen.rs +++ b/src/plonk/keygen.rs @@ -17,7 +17,7 @@ use crate::circuit::Value; use crate::poly::batch_invert_rational; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; use crate::utils::rational::Rational; -use crate::{utils::arithmetic::parallelize, poly::EvaluationDomain}; +use crate::{poly::EvaluationDomain, utils::arithmetic::parallelize}; pub(crate) fn create_domain( k: u32, diff --git a/src/plonk/lookup/prover.rs b/src/plonk/lookup/prover.rs index f8bf2df23b..3e40a56c4c 100644 --- a/src/plonk/lookup/prover.rs +++ b/src/plonk/lookup/prover.rs @@ -4,8 +4,8 @@ use crate::plonk::evaluation::evaluate; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; use crate::transcript::{Hashable, Transcript}; use crate::{ - utils::arithmetic::{eval_polynomial, parallelize}, poly::{Coeff, EvaluationDomain, LagrangeCoeff, Polynomial, ProverQuery, Rotation}, + utils::arithmetic::{eval_polynomial, parallelize}, }; use ff::{PrimeField, WithSmallOrderMulGroup}; use group::ff::BatchInvert; diff --git a/src/plonk/mod.rs b/src/plonk/mod.rs index 2d7193db95..318493557b 100644 --- a/src/plonk/mod.rs +++ b/src/plonk/mod.rs @@ -8,12 +8,15 @@ use blake2b_simd::Params as Blake2bParams; use group::ff::FromUniformBytes; -use crate::utils::helpers::{byte_length, polynomial_slice_byte_length, ProcessedSerdeObject, read_polynomial_vec, write_polynomial_slice}; use crate::poly::{ Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, PinnedEvaluationDomain, Polynomial, }; use crate::transcript::{Hashable, Transcript}; +use crate::utils::helpers::{ + byte_length, polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, + ProcessedSerdeObject, +}; use crate::utils::SerdeFormat; mod circuit; @@ -185,11 +188,11 @@ impl, CS: PolynomialCommitmentScheme> VerifyingK 10 + (self.fixed_commitments.len() * byte_length::(format)) + self.permutation.bytes_length(format) + self.selectors.len() - * (self - .selectors - .first() - .map(|selector| (selector.len() + 7) / 8) - .unwrap_or(0)) + * (self + .selectors + .first() + .map(|selector| (selector.len() + 7) / 8) + .unwrap_or(0)) } fn from_parts( diff --git a/src/plonk/permutation.rs b/src/plonk/permutation.rs index 8555d7bb0a..f0614ba59a 100644 --- a/src/plonk/permutation.rs +++ b/src/plonk/permutation.rs @@ -2,10 +2,8 @@ use super::circuit::{Any, Column}; use crate::{ - utils::helpers::{ - polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, - }, poly::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial}, + utils::helpers::{polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice}, utils::SerdeFormat, }; @@ -15,8 +13,8 @@ pub(crate) mod verifier; pub use keygen::Assembly; -use crate::utils::helpers::{byte_length, ProcessedSerdeObject}; use crate::poly::commitment::PolynomialCommitmentScheme; +use crate::utils::helpers::{byte_length, ProcessedSerdeObject}; use ff::PrimeField; use halo2curves::serde::SerdeObject; use std::io; @@ -149,10 +147,7 @@ impl ProvingKey { } /// Writes proving key for a single permutation argument to buffer using `Polynomial::write`. - pub(super) fn write( - &self, - writer: &mut W, - ) -> io::Result<()> { + pub(super) fn write(&self, writer: &mut W) -> io::Result<()> { write_polynomial_slice(&self.permutations, writer)?; write_polynomial_slice(&self.polys, writer)?; write_polynomial_slice(&self.cosets, writer)?; diff --git a/src/plonk/permutation/keygen.rs b/src/plonk/permutation/keygen.rs index dc45e1c407..93b8118dd6 100644 --- a/src/plonk/permutation/keygen.rs +++ b/src/plonk/permutation/keygen.rs @@ -2,9 +2,9 @@ use ff::WithSmallOrderMulGroup; use super::{Argument, ProvingKey, VerifyingKey}; use crate::{ - utils::arithmetic::parallelize, plonk::{Any, Column, Error}, poly::EvaluationDomain, + utils::arithmetic::parallelize, }; #[cfg(feature = "thread-safe-region")] diff --git a/src/plonk/permutation/prover.rs b/src/plonk/permutation/prover.rs index ebea470f90..2e6e3e9707 100644 --- a/src/plonk/permutation/prover.rs +++ b/src/plonk/permutation/prover.rs @@ -9,9 +9,9 @@ use super::{Argument, ProvingKey}; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; use crate::transcript::{Hashable, Transcript}; use crate::{ - utils::arithmetic::{eval_polynomial, parallelize}, plonk::{self, Error}, poly::{Coeff, LagrangeCoeff, Polynomial, ProverQuery, Rotation}, + utils::arithmetic::{eval_polynomial, parallelize}, }; pub(crate) struct CommittedSet { diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 34d1111c60..18fea4c94a 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -14,17 +14,17 @@ use super::{ }; use crate::{ - utils::arithmetic::eval_polynomial, // circuit::Value, // plonk::Assigned, poly::{Basis, Coeff, LagrangeCoeff, Polynomial, ProverQuery}, + utils::arithmetic::eval_polynomial, }; use crate::circuit::Value; use crate::poly::batch_invert_rational; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; -use crate::utils::rational::Rational; use crate::transcript::{Hashable, Sampleable, Transcript}; +use crate::utils::rational::Rational; use halo2curves::serde::SerdeObject; /// This creates a proof for the provided `circuit` when given the public diff --git a/src/plonk/vanishing/prover.rs b/src/plonk/vanishing/prover.rs index c750fdc8fc..b284d99117 100644 --- a/src/plonk/vanishing/prover.rs +++ b/src/plonk/vanishing/prover.rs @@ -9,10 +9,10 @@ use super::Argument; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; use crate::transcript::{Hashable, Transcript}; use crate::{ - utils::arithmetic::{eval_polynomial, parallelize}, - utils::multicore::current_num_threads, plonk::Error, poly::{Coeff, EvaluationDomain, ExtendedLagrangeCoeff, Polynomial, ProverQuery}, + utils::arithmetic::{eval_polynomial, parallelize}, + utils::multicore::current_num_threads, }; pub(in crate::plonk) struct Committed { diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 8d0edc61ff..974a0a1ccb 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -3,10 +3,10 @@ use halo2curves::serde::SerdeObject; use std::iter; use super::{vanishing, Error, VerifyingKey}; -use crate::utils::arithmetic::compute_inner_product; use crate::poly::commitment::{Params, PolynomialCommitmentScheme}; use crate::poly::VerifierQuery; use crate::transcript::{read_n, Hashable, Sampleable, Transcript}; +use crate::utils::arithmetic::compute_inner_product; /// Returns a boolean indicating whether or not the proof is valid pub fn verify_proof< diff --git a/src/poly/commitment.rs b/src/poly/commitment.rs index 66be2076e0..fc05ecc799 100644 --- a/src/poly/commitment.rs +++ b/src/poly/commitment.rs @@ -1,9 +1,9 @@ //! Trait for a commitment scheme use crate::poly::{Coeff, Error, LagrangeCoeff, Polynomial, ProverQuery, VerifierQuery}; use crate::transcript::{Hashable, Sampleable, Transcript}; +use crate::utils::helpers::ProcessedSerdeObject; use ff::PrimeField; use std::fmt::Debug; -use crate::utils::helpers::ProcessedSerdeObject; /// Public interface for a Polynomial Commitment Scheme (PCS) pub trait PolynomialCommitmentScheme: Clone + Debug { diff --git a/src/poly/kzg/mod.rs b/src/poly/kzg/mod.rs index c3b17b5d98..3b8af9d471 100644 --- a/src/poly/kzg/mod.rs +++ b/src/poly/kzg/mod.rs @@ -8,12 +8,12 @@ pub mod params; use std::fmt::Debug; -use crate::utils::arithmetic::{kate_division, powers, MSM}; use crate::poly::kzg::msm::{DualMSM, MSMKZG}; use crate::poly::kzg::params::{ParamsKZG, ParamsVerifierKZG}; use crate::poly::query::Query; use crate::poly::query::VerifierQuery; use crate::poly::{Coeff, Error, Polynomial, ProverQuery}; +use crate::utils::arithmetic::{kate_division, powers, MSM}; use crate::poly::commitment::PolynomialCommitmentScheme; use crate::transcript::{Hashable, Sampleable, Transcript}; @@ -212,7 +212,6 @@ where #[cfg(test)] mod tests { - use crate::utils::arithmetic::eval_polynomial; use crate::poly::commitment::PolynomialCommitmentScheme; use crate::poly::kzg::params::{ParamsKZG, ParamsVerifierKZG}; use crate::poly::kzg::KZGCommitmentScheme; @@ -221,6 +220,7 @@ mod tests { Error, EvaluationDomain, }; use crate::transcript::{CircuitTranscript, Hashable, Sampleable, Transcript}; + use crate::utils::arithmetic::eval_polynomial; use blake2b_simd::State as Blake2bState; use ff::WithSmallOrderMulGroup; use halo2curves::pairing::{Engine, MultiMillerLoop}; diff --git a/src/poly/kzg/params.rs b/src/poly/kzg/params.rs index 6758461a36..31ecb14ad6 100644 --- a/src/poly/kzg/params.rs +++ b/src/poly/kzg/params.rs @@ -1,5 +1,5 @@ -use crate::utils::arithmetic::{g_to_lagrange, parallelize}; use crate::poly::{LagrangeCoeff, Polynomial}; +use crate::utils::arithmetic::{g_to_lagrange, parallelize}; use crate::utils::SerdeFormat; use ff::{Field, PrimeField}; @@ -10,11 +10,11 @@ use std::fmt::Debug; use crate::poly::commitment::Params; use crate::poly::kzg::KZGCommitmentScheme; +use crate::utils::helpers::ProcessedSerdeObject; use halo2curves::msm::msm_best; use halo2curves::serde::SerdeObject; use halo2curves::CurveAffine; use std::io; -use crate::utils::helpers::ProcessedSerdeObject; use super::msm::MSMKZG; diff --git a/src/poly/mod.rs b/src/poly/mod.rs index 8bf2e625c0..d6ded960d4 100644 --- a/src/poly/mod.rs +++ b/src/poly/mod.rs @@ -7,11 +7,11 @@ use crate::utils::SerdeFormat; use ff::{BatchInvert, PrimeField}; use group::ff::Field; +use halo2curves::serde::SerdeObject; use std::fmt::Debug; use std::io; use std::marker::PhantomData; use std::ops::{Add, Deref, DerefMut, Index, IndexMut, Mul, RangeFrom, RangeFull, Sub}; -use halo2curves::serde::SerdeObject; mod domain; mod query; @@ -21,10 +21,10 @@ pub mod kzg; pub mod commitment; +use crate::utils::helpers::read_f; use crate::utils::rational::Rational; pub use domain::*; pub use query::{ProverQuery, VerifierQuery}; -use crate::utils::helpers::{read_f}; /// This is an error that could occur during proving or circuit synthesis. // TODO: these errors need to be cleaned up @@ -156,10 +156,7 @@ impl Polynomial { } /// Writes polynomial to buffer using `SerdePrimeField::write`. - pub(crate) fn write( - &self, - writer: &mut W, - ) -> io::Result<()> { + pub(crate) fn write(&self, writer: &mut W) -> io::Result<()> { writer.write_all(&(self.values.len() as u32).to_be_bytes())?; for value in self.values.iter() { value.write_raw(writer)?; diff --git a/src/poly/query.rs b/src/poly/query.rs index 0b6fd16f1e..7e7fe8dd3b 100644 --- a/src/poly/query.rs +++ b/src/poly/query.rs @@ -3,8 +3,8 @@ use std::fmt::Debug; use crate::poly::commitment::PolynomialCommitmentScheme; use crate::{ - utils::arithmetic::eval_polynomial, poly::{Coeff, Polynomial}, + utils::arithmetic::eval_polynomial, }; pub trait Query: Sized + Clone + Send + Sync { @@ -108,28 +108,6 @@ where } } -// #[allow(clippy::upper_case_acronyms)] -// #[derive(Clone, Debug)] -// pub enum CommitmentReference<'r, C: CurveAffine, M: MSM> { -// Commitment(&'r C), -// MSM(&'r M), -// } -// -// impl<'r, C: CurveAffine, M: MSM> Copy for CommitmentReference<'r, C, M> {} -// -// impl<'r, C: CurveAffine, M: MSM> PartialEq for CommitmentReference<'r, C, M> { -// #![allow(clippy::vtable_address_comparisons)] -// fn eq(&self, other: &Self) -> bool { -// match (self, other) { -// (&CommitmentReference::Commitment(a), &CommitmentReference::Commitment(b)) => { -// std::ptr::eq(a, b) -// } -// (&CommitmentReference::MSM(a), &CommitmentReference::MSM(b)) => std::ptr::eq(a, b), -// _ => false, -// } -// } -// } - impl> Query for VerifierQuery { type Commitment = CS::Commitment; type Eval = F; diff --git a/src/utils/helpers.rs b/src/utils/helpers.rs index 3b98514adf..57ef1cdc91 100644 --- a/src/utils/helpers.rs +++ b/src/utils/helpers.rs @@ -3,9 +3,9 @@ use crate::poly::Polynomial; use ff::PrimeField; use group::prime::PrimeCurveAffine; +use group::GroupEncoding; use halo2curves::serde::SerdeObject; use std::io; -use group::GroupEncoding; /// This enum specifies how various types are serialized and deserialized. #[derive(Clone, Copy, Debug)] @@ -47,17 +47,19 @@ pub fn byte_length(format: SerdeFormat) -> us /// Helper function to read a field element with a serde format. There is no way to compress field /// elements, so `Processed` and `RawBytes` act equivalently. -pub(crate) fn read_f(reader: &mut R, format: SerdeFormat) -> io::Result { - match format { - SerdeFormat::Processed => {::read_raw(reader)} - SerdeFormat::RawBytes => {::read_raw(reader)} - SerdeFormat::RawBytesUnchecked => {Ok(::read_raw_unchecked(reader))} - } +pub(crate) fn read_f( + reader: &mut R, + format: SerdeFormat, +) -> io::Result { + match format { + SerdeFormat::Processed => ::read_raw(reader), + SerdeFormat::RawBytes => ::read_raw(reader), + SerdeFormat::RawBytesUnchecked => Ok(::read_raw_unchecked(reader)), + } } /// Trait for serialising SerdeObjects -impl ProcessedSerdeObject for C -{ +impl ProcessedSerdeObject for C { /// Reads an element from the buffer and parses it according to the `format`: /// - `Processed`: Reads a compressed curve element and decompress it /// - `RawBytes`: Reads an uncompressed curve element with coordinates in Montgomery form. @@ -69,9 +71,10 @@ impl ProcessedSerdeObject for C SerdeFormat::Processed => { let mut compressed = ::Repr::default(); reader.read_exact(compressed.as_mut())?; - Option::from(Self::from_bytes(&compressed)) - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid point encoding in proof")) - }, + Option::from(Self::from_bytes(&compressed)).ok_or_else(|| { + io::Error::new(io::ErrorKind::Other, "Invalid point encoding in proof") + }) + } SerdeFormat::RawBytes => ::read_raw(reader), SerdeFormat::RawBytesUnchecked => Ok(::read_raw_unchecked(reader)), } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 0658aaf47c..ab861289b0 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -5,4 +5,4 @@ pub mod helpers; pub(crate) mod multicore; pub mod rational; -pub use helpers::SerdeFormat; \ No newline at end of file +pub use helpers::SerdeFormat; diff --git a/tests/plonk_api.rs b/tests/plonk_api.rs index 5b9e64d86a..13dd3ca302 100644 --- a/tests/plonk_api.rs +++ b/tests/plonk_api.rs @@ -1,558 +1,549 @@ -// #![allow(clippy::many_single_char_names)] -// #![allow(clippy::op_ref)] -// -// use assert_matches::assert_matches; -// use ff::{FromUniformBytes}; -// use halo2_proofs::arithmetic::Field; -// use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; -// use halo2_proofs::dev::MockProver; -// use halo2_proofs::plonk::{ -// create_proof as create_plonk_proof, keygen_pk, keygen_vk, verify_proof as verify_plonk_proof, -// Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, ProvingKey, TableColumn, -// VerifyingKey, -// }; -// use halo2_proofs::poly::commitment::{CommitmentScheme, ParamsProver, Prover, Verifier}; -// use halo2_proofs::poly::Rotation; -// use halo2_proofs::poly::VerificationStrategy; -// use halo2_proofs::transcript::{ -// Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge, TranscriptReadBuffer, -// TranscriptWriterBuffer, -// }; -// use rand_core::{OsRng, RngCore}; -// use std::marker::PhantomData; -// -// #[test] -// fn plonk_api() { -// const K: u32 = 5; -// -// /// This represents an advice column at a certain row in the ConstraintSystem -// #[derive(Copy, Clone, Debug)] -// pub struct Variable(Column, usize); -// -// #[derive(Clone)] -// struct PlonkConfig { -// a: Column, -// b: Column, -// c: Column, -// d: Column, -// e: Column, -// -// sa: Column, -// sb: Column, -// sc: Column, -// sm: Column, -// sp: Column, -// sl: TableColumn, -// } -// -// #[allow(clippy::type_complexity)] -// trait StandardCs { -// fn raw_multiply( -// &self, -// layouter: &mut impl Layouter, -// f: F, -// ) -> Result<(Cell, Cell, Cell), Error> -// where -// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; -// fn raw_add( -// &self, -// layouter: &mut impl Layouter, -// f: F, -// ) -> Result<(Cell, Cell, Cell), Error> -// where -// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; -// fn copy(&self, layouter: &mut impl Layouter, a: Cell, b: Cell) -> Result<(), Error>; -// fn public_input(&self, layouter: &mut impl Layouter, f: F) -> Result -// where -// F: FnMut() -> Value; -// fn lookup_table( -// &self, -// layouter: &mut impl Layouter, -// values: &[FF], -// ) -> Result<(), Error>; -// } -// -// #[derive(Clone)] -// struct MyCircuit { -// a: Value, -// lookup_table: Vec, -// } -// -// struct StandardPlonk { -// config: PlonkConfig, -// _marker: PhantomData, -// } -// -// impl StandardPlonk { -// fn new(config: PlonkConfig) -> Self { -// StandardPlonk { -// config, -// _marker: PhantomData, -// } -// } -// } -// -// impl StandardCs for StandardPlonk { -// fn raw_multiply( -// &self, -// layouter: &mut impl Layouter, -// mut f: F, -// ) -> Result<(Cell, Cell, Cell), Error> -// where -// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, -// { -// layouter.assign_region( -// || "raw_multiply", -// |mut region| { -// let mut value = None; -// let lhs = region.assign_advice( -// || "lhs", -// self.config.a, -// 0, -// || { -// value = Some(f()); -// value.unwrap().map(|v| v.0) -// }, -// )?; -// region.assign_advice( -// || "lhs^4", -// self.config.d, -// 0, -// || value.unwrap().map(|v| v.0).square().square(), -// )?; -// let rhs = region.assign_advice( -// || "rhs", -// self.config.b, -// 0, -// || value.unwrap().map(|v| v.1), -// )?; -// region.assign_advice( -// || "rhs^4", -// self.config.e, -// 0, -// || value.unwrap().map(|v| v.1).square().square(), -// )?; -// let out = region.assign_advice( -// || "out", -// self.config.c, -// 0, -// || value.unwrap().map(|v| v.2), -// )?; -// -// region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ZERO))?; -// region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ZERO))?; -// region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; -// region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; -// Ok((lhs.cell(), rhs.cell(), out.cell())) -// }, -// ) -// } -// fn raw_add( -// &self, -// layouter: &mut impl Layouter, -// mut f: F, -// ) -> Result<(Cell, Cell, Cell), Error> -// where -// F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, -// { -// layouter.assign_region( -// || "raw_add", -// |mut region| { -// let mut value = None; -// let lhs = region.assign_advice( -// || "lhs", -// self.config.a, -// 0, -// || { -// value = Some(f()); -// value.unwrap().map(|v| v.0) -// }, -// )?; -// region.assign_advice( -// || "lhs^4", -// self.config.d, -// 0, -// || value.unwrap().map(|v| v.0).square().square(), -// )?; -// let rhs = region.assign_advice( -// || "rhs", -// self.config.b, -// 0, -// || value.unwrap().map(|v| v.1), -// )?; -// region.assign_advice( -// || "rhs^4", -// self.config.e, -// 0, -// || value.unwrap().map(|v| v.1).square().square(), -// )?; -// let out = region.assign_advice( -// || "out", -// self.config.c, -// 0, -// || value.unwrap().map(|v| v.2), -// )?; -// -// region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ONE))?; -// region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ONE))?; -// region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; -// region.assign_fixed( -// || "a * b", -// self.config.sm, -// 0, -// || Value::known(FF::ZERO), -// )?; -// Ok((lhs.cell(), rhs.cell(), out.cell())) -// }, -// ) -// } -// fn copy( -// &self, -// layouter: &mut impl Layouter, -// left: Cell, -// right: Cell, -// ) -> Result<(), Error> { -// layouter.assign_region( -// || "copy", -// |mut region| { -// region.constrain_equal(left, right)?; -// region.constrain_equal(left, right) -// }, -// ) -// } -// fn public_input(&self, layouter: &mut impl Layouter, mut f: F) -> Result -// where -// F: FnMut() -> Value, -// { -// layouter.assign_region( -// || "public_input", -// |mut region| { -// let value = region.assign_advice(|| "value", self.config.a, 0, &mut f)?; -// region.assign_fixed( -// || "public", -// self.config.sp, -// 0, -// || Value::known(FF::ONE), -// )?; -// -// Ok(value.cell()) -// }, -// ) -// } -// fn lookup_table( -// &self, -// layouter: &mut impl Layouter, -// values: &[FF], -// ) -> Result<(), Error> { -// layouter.assign_table( -// || "", -// |mut table| { -// for (index, &value) in values.iter().enumerate() { -// table.assign_cell( -// || "table col", -// self.config.sl, -// index, -// || Value::known(value), -// )?; -// } -// Ok(()) -// }, -// )?; -// Ok(()) -// } -// } -// -// impl Circuit for MyCircuit { -// type Config = PlonkConfig; -// type FloorPlanner = SimpleFloorPlanner; -// #[cfg(feature = "circuit-params")] -// type Params = (); -// -// fn without_witnesses(&self) -> Self { -// Self { -// a: Value::unknown(), -// lookup_table: self.lookup_table.clone(), -// } -// } -// -// fn configure(meta: &mut ConstraintSystem) -> PlonkConfig { -// let e = meta.advice_column(); -// let a = meta.advice_column(); -// let b = meta.advice_column(); -// let sf = meta.fixed_column(); -// let c = meta.advice_column(); -// let d = meta.advice_column(); -// let p = meta.instance_column(); -// -// meta.enable_equality(a); -// meta.enable_equality(b); -// meta.enable_equality(c); -// -// let sm = meta.fixed_column(); -// let sa = meta.fixed_column(); -// let sb = meta.fixed_column(); -// let sc = meta.fixed_column(); -// let sp = meta.fixed_column(); -// let sl = meta.lookup_table_column(); -// -// /* -// * A B ... sl -// * [ -// * instance 0 ... 0 -// * a a ... 0 -// * a a^2 ... 0 -// * a a ... 0 -// * a a^2 ... 0 -// * ... ... ... ... -// * ... ... ... instance -// * ... ... ... a -// * ... ... ... a -// * ... ... ... 0 -// * ] -// */ -// -// meta.lookup("lookup", |meta| { -// let a_ = meta.query_any(a, Rotation::cur()); -// vec![(a_, sl)] -// }); -// -// meta.create_gate("Combined add-mult", |meta| { -// let d = meta.query_advice(d, Rotation::next()); -// let a = meta.query_advice(a, Rotation::cur()); -// let sf = meta.query_fixed(sf, Rotation::cur()); -// let e = meta.query_advice(e, Rotation::prev()); -// let b = meta.query_advice(b, Rotation::cur()); -// let c = meta.query_advice(c, Rotation::cur()); -// -// let sa = meta.query_fixed(sa, Rotation::cur()); -// let sb = meta.query_fixed(sb, Rotation::cur()); -// let sc = meta.query_fixed(sc, Rotation::cur()); -// let sm = meta.query_fixed(sm, Rotation::cur()); -// -// vec![a.clone() * sa + b.clone() * sb + a * b * sm - (c * sc) + sf * (d * e)] -// }); -// -// meta.create_gate("Public input", |meta| { -// let a = meta.query_advice(a, Rotation::cur()); -// let p = meta.query_instance(p, Rotation::cur()); -// let sp = meta.query_fixed(sp, Rotation::cur()); -// -// vec![sp * (a - p)] -// }); -// -// meta.enable_equality(sf); -// meta.enable_equality(e); -// meta.enable_equality(d); -// meta.enable_equality(p); -// meta.enable_equality(sm); -// meta.enable_equality(sa); -// meta.enable_equality(sb); -// meta.enable_equality(sc); -// meta.enable_equality(sp); -// -// PlonkConfig { -// a, -// b, -// c, -// d, -// e, -// sa, -// sb, -// sc, -// sm, -// sp, -// sl, -// } -// } -// -// fn synthesize( -// &self, -// config: PlonkConfig, -// mut layouter: impl Layouter, -// ) -> Result<(), Error> { -// let cs = StandardPlonk::new(config); -// -// let _ = cs.public_input(&mut layouter, || Value::known(F::ONE + F::ONE))?; -// -// for _ in 0..10 { -// let a: Value> = self.a.into(); -// let mut a_squared = Value::unknown(); -// let (a0, _, c0) = cs.raw_multiply(&mut layouter, || { -// a_squared = a.square(); -// a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) -// })?; -// let (a1, b1, _) = cs.raw_add(&mut layouter, || { -// let fin = a_squared + a; -// a.zip(a_squared) -// .zip(fin) -// .map(|((a, a_squared), fin)| (a, a_squared, fin)) -// })?; -// cs.copy(&mut layouter, a0, a1)?; -// cs.copy(&mut layouter, b1, c0)?; -// } -// -// cs.lookup_table(&mut layouter, &self.lookup_table)?; -// -// Ok(()) -// } -// } -// -// macro_rules! common { -// ($scheme:ident) => {{ -// let a = <$scheme as CommitmentScheme>::Scalar::from(2834758237) -// * <$scheme as CommitmentScheme>::Scalar::ZETA; -// let instance = <$scheme as CommitmentScheme>::Scalar::ONE -// + <$scheme as CommitmentScheme>::Scalar::ONE; -// let lookup_table = vec![instance, a, a, <$scheme as CommitmentScheme>::Scalar::ZERO]; -// (a, instance, lookup_table) -// }}; -// } -// -// macro_rules! bad_keys { -// ($scheme:ident) => {{ -// let (_, _, lookup_table) = common!($scheme); -// let empty_circuit: MyCircuit<<$scheme as CommitmentScheme>::Scalar> = MyCircuit { -// a: Value::unknown(), -// lookup_table: lookup_table.clone(), -// }; -// -// // Check that we get an error if we try to initialize the proving key with a value of -// // k that is too small for the minimum required number of rows. -// let much_too_small_params= <$scheme as CommitmentScheme>::ParamsProver::new(1); -// assert_matches!( -// keygen_vk(&much_too_small_params, &empty_circuit), -// Err(Error::NotEnoughRowsAvailable { -// current_k, -// }) if current_k == 1 -// ); -// -// // Check that we get an error if we try to initialize the proving key with a value of -// // k that is too small for the number of rows the circuit uses. -// let slightly_too_small_params = <$scheme as CommitmentScheme>::ParamsProver::new(K-1); -// assert_matches!( -// keygen_vk(&slightly_too_small_params, &empty_circuit), -// Err(Error::NotEnoughRowsAvailable { -// current_k, -// }) if current_k == K - 1 -// ); -// }}; -// } -// -// fn keygen(params: &Scheme::ParamsProver) -> ProvingKey -// where -// Scheme::Scalar: FromUniformBytes<64> + WithSmallOrderMulGroup<3>, -// { -// let (_, _, lookup_table) = common!(Scheme); -// let empty_circuit: MyCircuit = MyCircuit { -// a: Value::unknown(), -// lookup_table, -// }; -// -// // Initialize the proving key -// let vk = keygen_vk(params, &empty_circuit).expect("keygen_vk should not fail"); -// -// keygen_pk(params, vk, &empty_circuit).expect("keygen_pk should not fail") -// } -// -// fn create_proof< -// 'params, -// Scheme: CommitmentScheme, -// P: Prover<'params, Scheme>, -// E: EncodedChallenge, -// R: RngCore, -// T: TranscriptWriterBuffer, Scheme::Curve, E>, -// >( -// rng: R, -// params: &'params Scheme::ParamsProver, -// pk: &ProvingKey, -// ) -> Vec -// where -// Scheme::Scalar: Ord + WithSmallOrderMulGroup<3> + FromUniformBytes<64>, -// { -// let (a, instance, lookup_table) = common!(Scheme); -// -// let circuit: MyCircuit = MyCircuit { -// a: Value::known(a), -// lookup_table, -// }; -// -// let mut transcript = T::init(vec![]); -// -// create_plonk_proof::( -// params, -// pk, -// &[circuit.clone(), circuit.clone()], -// &[&[&[instance]], &[&[instance]]], -// rng, -// &mut transcript, -// ) -// .expect("proof generation should not fail"); -// -// // Check this circuit is satisfied. -// let prover = match MockProver::run(K, &circuit, vec![vec![instance]]) { -// Ok(prover) => prover, -// Err(e) => panic!("{e:?}"), -// }; -// assert_eq!(prover.verify(), Ok(())); -// -// transcript.finalize() -// } -// -// fn verify_proof< -// 'a, -// 'params, -// Scheme: CommitmentScheme, -// V: Verifier<'params, Scheme>, -// E: EncodedChallenge, -// T: TranscriptReadBuffer<&'a [u8], Scheme::Curve, E>, -// Strategy: VerificationStrategy<'params, Scheme, V, Output = Strategy>, -// >( -// params_verifier: &'params Scheme::ParamsVerifier, -// vk: &VerifyingKey, -// proof: &'a [u8], -// ) where -// Scheme::Scalar: Ord + WithSmallOrderMulGroup<3> + FromUniformBytes<64>, -// { -// let (_, instance, _) = common!(Scheme); -// let pubinputs = [instance]; -// -// let mut transcript = T::init(proof); -// -// let strategy = Strategy::new(params_verifier); -// let strategy = verify_plonk_proof( -// params_verifier, -// vk, -// strategy, -// &[&[&pubinputs[..]], &[&pubinputs[..]]], -// &mut transcript, -// ) -// .unwrap(); -// -// assert!(strategy.finalize()); -// } -// -// use halo2_proofs::poly::kzg::params::{KZGCommitmentScheme, ParamsKZG}; -// use halo2_proofs::poly::kzg::gwc::{ProverGWC, VerifierGWC}; -// use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; -// use halo2curves::bn256::Bn256; -// -// type Scheme = KZGCommitmentScheme; -// bad_keys!(Scheme); -// -// let params = ParamsKZG::::new(K); -// let rng = OsRng; -// -// let pk = keygen::>(¶ms); -// -// let proof = create_proof::<_, ProverGWC<_>, _, _, Blake2bWrite<_, _, Challenge255<_>>>( -// rng, ¶ms, &pk, -// ); -// -// let verifier_params = params.verifier_params(); -// -// verify_proof::<_, VerifierGWC<_>, _, Blake2bRead<_, _, Challenge255<_>>, AccumulatorStrategy<_>>( -// verifier_params, -// pk.get_vk(), -// &proof[..], -// ); -// } +#![allow(clippy::many_single_char_names)] +#![allow(clippy::op_ref)] + +use assert_matches::assert_matches; +use blake2b_simd::State; +use ff::{FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; +use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; +use halo2_proofs::dev::MockProver; +use halo2_proofs::plonk::{ + create_proof as create_plonk_proof, keygen_pk, keygen_vk, verify_proof as verify_plonk_proof, + Advice, Circuit, Column, ConstraintSystem, Error, Fixed, ProvingKey, TableColumn, VerifyingKey, +}; +use halo2_proofs::poly::commitment::PolynomialCommitmentScheme; +use halo2_proofs::poly::kzg::params::ParamsKZG; +use halo2_proofs::poly::kzg::KZGCommitmentScheme; +use halo2_proofs::poly::Rotation; +use halo2_proofs::transcript::{CircuitTranscript, Hashable, Sampleable, Transcript}; +use halo2_proofs::utils::arithmetic::Field; +use halo2_proofs::utils::rational::Rational; +use halo2curves::serde::SerdeObject; +use rand_core::{CryptoRng, OsRng, RngCore}; +use std::marker::PhantomData; + +#[test] +fn plonk_api() { + const K: u32 = 5; + + /// This represents an advice column at a certain row in the ConstraintSystem + #[derive(Copy, Clone, Debug)] + pub struct Variable(Column, usize); + + #[derive(Clone)] + struct PlonkConfig { + a: Column, + b: Column, + c: Column, + d: Column, + e: Column, + + sa: Column, + sb: Column, + sc: Column, + sm: Column, + sp: Column, + sl: TableColumn, + } + + #[allow(clippy::type_complexity)] + trait StandardCs { + fn raw_multiply( + &self, + layouter: &mut impl Layouter, + f: F, + ) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Rational, Rational, Rational)>; + fn raw_add( + &self, + layouter: &mut impl Layouter, + f: F, + ) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Rational, Rational, Rational)>; + fn copy(&self, layouter: &mut impl Layouter, a: Cell, b: Cell) -> Result<(), Error>; + fn public_input(&self, layouter: &mut impl Layouter, f: F) -> Result + where + F: FnMut() -> Value; + fn lookup_table( + &self, + layouter: &mut impl Layouter, + values: &[FF], + ) -> Result<(), Error>; + } + + #[derive(Clone)] + struct MyCircuit { + a: Value, + lookup_table: Vec, + } + + struct StandardPlonk { + config: PlonkConfig, + _marker: PhantomData, + } + + impl StandardPlonk { + fn new(config: PlonkConfig) -> Self { + StandardPlonk { + config, + _marker: PhantomData, + } + } + } + + impl StandardCs for StandardPlonk { + fn raw_multiply( + &self, + layouter: &mut impl Layouter, + mut f: F, + ) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Rational, Rational, Rational)>, + { + layouter.assign_region( + || "raw_multiply", + |mut region| { + let mut value = None; + let lhs = region.assign_advice( + || "lhs", + self.config.a, + 0, + || { + value = Some(f()); + value.unwrap().map(|v| v.0) + }, + )?; + region.assign_advice( + || "lhs^4", + self.config.d, + 0, + || value.unwrap().map(|v| v.0).square().square(), + )?; + let rhs = region.assign_advice( + || "rhs", + self.config.b, + 0, + || value.unwrap().map(|v| v.1), + )?; + region.assign_advice( + || "rhs^4", + self.config.e, + 0, + || value.unwrap().map(|v| v.1).square().square(), + )?; + let out = region.assign_advice( + || "out", + self.config.c, + 0, + || value.unwrap().map(|v| v.2), + )?; + + region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ZERO))?; + region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ZERO))?; + region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; + region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::ONE))?; + Ok((lhs.cell(), rhs.cell(), out.cell())) + }, + ) + } + fn raw_add( + &self, + layouter: &mut impl Layouter, + mut f: F, + ) -> Result<(Cell, Cell, Cell), Error> + where + F: FnMut() -> Value<(Rational, Rational, Rational)>, + { + layouter.assign_region( + || "raw_add", + |mut region| { + let mut value = None; + let lhs = region.assign_advice( + || "lhs", + self.config.a, + 0, + || { + value = Some(f()); + value.unwrap().map(|v| v.0) + }, + )?; + region.assign_advice( + || "lhs^4", + self.config.d, + 0, + || value.unwrap().map(|v| v.0).square().square(), + )?; + let rhs = region.assign_advice( + || "rhs", + self.config.b, + 0, + || value.unwrap().map(|v| v.1), + )?; + region.assign_advice( + || "rhs^4", + self.config.e, + 0, + || value.unwrap().map(|v| v.1).square().square(), + )?; + let out = region.assign_advice( + || "out", + self.config.c, + 0, + || value.unwrap().map(|v| v.2), + )?; + + region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::ONE))?; + region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::ONE))?; + region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::ONE))?; + region.assign_fixed( + || "a * b", + self.config.sm, + 0, + || Value::known(FF::ZERO), + )?; + Ok((lhs.cell(), rhs.cell(), out.cell())) + }, + ) + } + fn copy( + &self, + layouter: &mut impl Layouter, + left: Cell, + right: Cell, + ) -> Result<(), Error> { + layouter.assign_region( + || "copy", + |mut region| { + region.constrain_equal(left, right)?; + region.constrain_equal(left, right) + }, + ) + } + fn public_input(&self, layouter: &mut impl Layouter, mut f: F) -> Result + where + F: FnMut() -> Value, + { + layouter.assign_region( + || "public_input", + |mut region| { + let value = region.assign_advice(|| "value", self.config.a, 0, &mut f)?; + region.assign_fixed( + || "public", + self.config.sp, + 0, + || Value::known(FF::ONE), + )?; + + Ok(value.cell()) + }, + ) + } + fn lookup_table( + &self, + layouter: &mut impl Layouter, + values: &[FF], + ) -> Result<(), Error> { + layouter.assign_table( + || "", + |mut table| { + for (index, &value) in values.iter().enumerate() { + table.assign_cell( + || "table col", + self.config.sl, + index, + || Value::known(value), + )?; + } + Ok(()) + }, + )?; + Ok(()) + } + } + + impl Circuit for MyCircuit { + type Config = PlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self { + a: Value::unknown(), + lookup_table: self.lookup_table.clone(), + } + } + + fn configure(meta: &mut ConstraintSystem) -> PlonkConfig { + let e = meta.advice_column(); + let a = meta.advice_column(); + let b = meta.advice_column(); + let sf = meta.fixed_column(); + let c = meta.advice_column(); + let d = meta.advice_column(); + let p = meta.instance_column(); + + meta.enable_equality(a); + meta.enable_equality(b); + meta.enable_equality(c); + + let sm = meta.fixed_column(); + let sa = meta.fixed_column(); + let sb = meta.fixed_column(); + let sc = meta.fixed_column(); + let sp = meta.fixed_column(); + let sl = meta.lookup_table_column(); + + /* + * A B ... sl + * [ + * instance 0 ... 0 + * a a ... 0 + * a a^2 ... 0 + * a a ... 0 + * a a^2 ... 0 + * ... ... ... ... + * ... ... ... instance + * ... ... ... a + * ... ... ... a + * ... ... ... 0 + * ] + */ + + meta.lookup("lookup", |meta| { + let a_ = meta.query_any(a, Rotation::cur()); + vec![(a_, sl)] + }); + + meta.create_gate("Combined add-mult", |meta| { + let d = meta.query_advice(d, Rotation::next()); + let a = meta.query_advice(a, Rotation::cur()); + let sf = meta.query_fixed(sf, Rotation::cur()); + let e = meta.query_advice(e, Rotation::prev()); + let b = meta.query_advice(b, Rotation::cur()); + let c = meta.query_advice(c, Rotation::cur()); + + let sa = meta.query_fixed(sa, Rotation::cur()); + let sb = meta.query_fixed(sb, Rotation::cur()); + let sc = meta.query_fixed(sc, Rotation::cur()); + let sm = meta.query_fixed(sm, Rotation::cur()); + + vec![a.clone() * sa + b.clone() * sb + a * b * sm - (c * sc) + sf * (d * e)] + }); + + meta.create_gate("Public input", |meta| { + let a = meta.query_advice(a, Rotation::cur()); + let p = meta.query_instance(p, Rotation::cur()); + let sp = meta.query_fixed(sp, Rotation::cur()); + + vec![sp * (a - p)] + }); + + meta.enable_equality(sf); + meta.enable_equality(e); + meta.enable_equality(d); + meta.enable_equality(p); + meta.enable_equality(sm); + meta.enable_equality(sa); + meta.enable_equality(sb); + meta.enable_equality(sc); + meta.enable_equality(sp); + + PlonkConfig { + a, + b, + c, + d, + e, + sa, + sb, + sc, + sm, + sp, + sl, + } + } + + fn synthesize( + &self, + config: PlonkConfig, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let cs = StandardPlonk::new(config); + + let _ = cs.public_input(&mut layouter, || Value::known(F::ONE + F::ONE))?; + + for _ in 0..10 { + let a: Value> = self.a.into(); + let mut a_squared = Value::unknown(); + let (a0, _, c0) = cs.raw_multiply(&mut layouter, || { + a_squared = a.square(); + a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) + })?; + let (a1, b1, _) = cs.raw_add(&mut layouter, || { + let fin = a_squared + a; + a.zip(a_squared) + .zip(fin) + .map(|((a, a_squared), fin)| (a, a_squared, fin)) + })?; + cs.copy(&mut layouter, a0, a1)?; + cs.copy(&mut layouter, b1, c0)?; + } + + cs.lookup_table(&mut layouter, &self.lookup_table)?; + + Ok(()) + } + } + + macro_rules! common { + ($field:ident) => {{ + let a = $field::from(2834758237) * $field::ZETA; + let instance = $field::ONE + $field::ONE; + let lookup_table = vec![instance, a, a, $field::ZERO]; + (a, instance, lookup_table) + }}; + } + + macro_rules! bad_keys { + ($field: ident, $scheme:ident) => {{ + let (_, _, lookup_table) = common!($field); + let empty_circuit: MyCircuit<$field> = MyCircuit { + a: Value::unknown(), + lookup_table: lookup_table.clone(), + }; + + // Check that we get an error if we try to initialize the proving key with a value of + // k that is too small for the minimum required number of rows. + let much_too_small_params= <$scheme as PolynomialCommitmentScheme<$field>>::Parameters::new(1); + assert_matches!( + keygen_vk::<_, $scheme, _>(&much_too_small_params, &empty_circuit), + Err(Error::NotEnoughRowsAvailable { + current_k, + }) if current_k == 1 + ); + + // Check that we get an error if we try to initialize the proving key with a value of + // k that is too small for the number of rows the circuit uses. + let slightly_too_small_params = <$scheme as PolynomialCommitmentScheme<$field>>::Parameters::new(K-1); + assert_matches!( + keygen_vk::<_, $scheme, _>(&slightly_too_small_params, &empty_circuit), + Err(Error::NotEnoughRowsAvailable { + current_k, + }) if current_k == K - 1 + ); + }}; + } + + fn keygen>( + params: &Scheme::Parameters, + params_vk: &Scheme::VerifierParameters, + ) -> ProvingKey + where + F: FromUniformBytes<64> + WithSmallOrderMulGroup<3>, + { + let (_, _, lookup_table) = common!(F); + let empty_circuit: MyCircuit = MyCircuit { + a: Value::unknown(), + lookup_table, + }; + + // Initialize the proving key + let vk = keygen_vk(params_vk, &empty_circuit).expect("keygen_vk should not fail"); + + keygen_pk(params, vk, &empty_circuit).expect("keygen_pk should not fail") + } + + fn create_proof< + F: PrimeField, + Scheme: PolynomialCommitmentScheme, + T: Transcript, + R: RngCore + CryptoRng, + >( + rng: R, + params: &Scheme::Parameters, + pk: &ProvingKey, + ) -> Vec + where + F: Ord + + WithSmallOrderMulGroup<3> + + FromUniformBytes<64> + + Sampleable + + Hashable + + SerdeObject, + Scheme::Commitment: Hashable, + { + let (a, instance, lookup_table) = common!(F); + + let circuit: MyCircuit = MyCircuit { + a: Value::known(a), + lookup_table, + }; + + let mut transcript = T::init(); + + create_plonk_proof::( + params, + pk, + &[circuit.clone(), circuit.clone()], + &[&[&[instance]], &[&[instance]]], + rng, + &mut transcript, + ) + .expect("proof generation should not fail"); + + // Check this circuit is satisfied. + let prover = match MockProver::run(K, &circuit, vec![vec![instance]]) { + Ok(prover) => prover, + Err(e) => panic!("{e:?}"), + }; + assert_eq!(prover.verify(), Ok(())); + + transcript.finalize() + } + + fn verify_proof, T: Transcript>( + params_verifier: &Scheme::VerifierParameters, + vk: &VerifyingKey, + proof: &[u8], + ) where + F: Ord + + WithSmallOrderMulGroup<3> + + FromUniformBytes<64> + + Sampleable + + Hashable + + SerdeObject, + Scheme::Commitment: Hashable, + { + let (_, instance, _) = common!(F); + let pubinputs = [instance]; + + let mut transcript = T::init_from_bytes(proof); + + let verifier = verify_plonk_proof( + params_verifier, + vk, + &[&[&pubinputs[..]], &[&pubinputs[..]]], + &mut transcript, + ); + + assert!(verifier.is_ok()); + } + + use halo2curves::bn256::{Bn256, Fr}; + + type Scheme = KZGCommitmentScheme; + bad_keys!(Fr, Scheme); + + let params = ParamsKZG::::new(K); + let rng = OsRng; + + let pk = keygen::(¶ms, params.verifier_params()); + + let proof = create_proof::, _>(rng, ¶ms, &pk); + + let verifier_params = params.verifier_params(); + + verify_proof::<_, _, CircuitTranscript>(verifier_params, pk.get_vk(), &proof[..]); +} From 1e2a10ad184175cefee9b738ef8f1b3799d7c8f3 Mon Sep 17 00:00:00 2001 From: iquerejeta Date: Fri, 20 Dec 2024 10:32:05 +0100 Subject: [PATCH 20/20] Leftover docs --- src/plonk/vanishing.rs | 1 - src/poly/commitment.rs | 1 - src/transcript/mod.rs | 8 -------- 3 files changed, 10 deletions(-) diff --git a/src/plonk/vanishing.rs b/src/plonk/vanishing.rs index c1cc09b3fd..6e7b7c5faa 100644 --- a/src/plonk/vanishing.rs +++ b/src/plonk/vanishing.rs @@ -7,7 +7,6 @@ mod prover; mod verifier; /// A vanishing argument. -/// todo: do we need to have the PCS here? pub(crate) struct Argument> { _marker: PhantomData<(F, CS)>, } diff --git a/src/poly/commitment.rs b/src/poly/commitment.rs index fc05ecc799..c21c53fa35 100644 --- a/src/poly/commitment.rs +++ b/src/poly/commitment.rs @@ -23,7 +23,6 @@ pub trait PolynomialCommitmentScheme: Clone + Debug { fn commit(params: &Self::Parameters, polynomial: &Polynomial) -> Self::Commitment; /// Create an opening proof at a specific query - /// FIXME: We are not writing the queries to the transcript fn open<'com, T: Transcript, I>( params: &Self::Parameters, prover_query: I, diff --git a/src/transcript/mod.rs b/src/transcript/mod.rs index b43d0642b4..0850f373af 100644 --- a/src/transcript/mod.rs +++ b/src/transcript/mod.rs @@ -11,17 +11,9 @@ use std::io::{self, Cursor}; /// Prefix to a prover's message soliciting a challenge const BLAKE2B_PREFIX_CHALLENGE: u8 = 0; -// TODO: BEFORE WE HAD DIFFERENT PREFIXES FOR DIFFERENT TYPES /// Prefix to a prover's message const BLAKE2B_PREFIX_COMMON: u8 = 1; -/* -/// NOTE ///// -Why do we need the below three traits? The reason is that we cannot implement -Into for an arbitrary type due to the orphan rule. This -means that we need to have a trait for something that is hashable. - */ - /// Hash function that can be used for transcript pub trait TranscriptHash { /// Input type of the hash function